osborn 0.9.155 β 0.9.159
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/dist/claude-llm.js +8 -1
- package/dist/config.js +1 -1
- package/dist/index.js +103 -9
- package/package.json +1 -1
package/dist/claude-llm.js
CHANGED
|
@@ -140,10 +140,17 @@ function ensureCompactionSettings() {
|
|
|
140
140
|
else {
|
|
141
141
|
mkdirSync(claudeDir, { recursive: true });
|
|
142
142
|
}
|
|
143
|
+
let dirty = false;
|
|
143
144
|
if (settings.autoCompactWindow !== 1_000_000) {
|
|
144
145
|
settings.autoCompactWindow = 1_000_000;
|
|
146
|
+
dirty = true;
|
|
147
|
+
}
|
|
148
|
+
if (settings.cleanupPeriodDays !== 3650) {
|
|
149
|
+
settings.cleanupPeriodDays = 3650;
|
|
150
|
+
dirty = true;
|
|
151
|
+
}
|
|
152
|
+
if (dirty) {
|
|
145
153
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
146
|
-
console.log('πͺ autoCompactWindow set to 1,000,000 in', settingsPath);
|
|
147
154
|
}
|
|
148
155
|
}
|
|
149
156
|
catch (err) {
|
package/dist/config.js
CHANGED
|
@@ -595,7 +595,7 @@ async function extractCwd(filePath) {
|
|
|
595
595
|
*
|
|
596
596
|
* @param limit - Max sessions to return (default 100, sorted by recency)
|
|
597
597
|
*/
|
|
598
|
-
export async function listAllClaudeSessions(limit =
|
|
598
|
+
export async function listAllClaudeSessions(limit = 2000) {
|
|
599
599
|
const projectsDir = getClaudeProjectsDir();
|
|
600
600
|
if (!existsSync(projectsDir))
|
|
601
601
|
return [];
|
package/dist/index.js
CHANGED
|
@@ -267,6 +267,42 @@ let codeServerStartHook = null;
|
|
|
267
267
|
// code-server runs on 127.0.0.1:8300; the agent proxies it through its own
|
|
268
268
|
// public HTTP server so no Cloudflare tunnel is needed.
|
|
269
269
|
const IDE_TARGET = 'http://127.0.0.1:8300';
|
|
270
|
+
const OSBORN_API_PORT = parseInt(process.env.OSBORN_API_PORT || '8741', 10);
|
|
271
|
+
// Dev-domain wildcard subdomain routing.
|
|
272
|
+
// When DEV_DOMAIN is set (e.g. "dev.voice-native.com"), requests arriving with
|
|
273
|
+
// Host: PORT-APPNAME.dev.voice-native.com are proxied to localhost:PORT at root.
|
|
274
|
+
// The fleet router (apps/dev-router) handles TLS + fly-replay to reach this machine;
|
|
275
|
+
// the agent only needs to detect the pattern and forward.
|
|
276
|
+
const DEV_DOMAIN = process.env.DEV_DOMAIN || '';
|
|
277
|
+
function parseDevSubdomainPort(req) {
|
|
278
|
+
if (!DEV_DOMAIN)
|
|
279
|
+
return null;
|
|
280
|
+
// Primary: Host header β preserved through fly-replay by Fly
|
|
281
|
+
const hostname = (req.headers.host || '').split(':')[0].toLowerCase();
|
|
282
|
+
const suffix = '.' + DEV_DOMAIN;
|
|
283
|
+
if (hostname.endsWith(suffix)) {
|
|
284
|
+
const label = hostname.slice(0, -suffix.length); // e.g. "3000-osborn-abc"
|
|
285
|
+
const dashIdx = label.indexOf('-');
|
|
286
|
+
if (dashIdx > 0) {
|
|
287
|
+
const port = parseInt(label.slice(0, dashIdx), 10);
|
|
288
|
+
if (!isNaN(port) && port > 1024 && port < 65536 && port !== 8300 && port !== OSBORN_API_PORT) {
|
|
289
|
+
return port;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Fallback: fly-replay-src state field β fleet router encodes "state=port:PORT"
|
|
294
|
+
const replaySrc = req.headers['fly-replay-src'];
|
|
295
|
+
if (replaySrc) {
|
|
296
|
+
const m = replaySrc.match(/state=port:(\d+)/);
|
|
297
|
+
if (m) {
|
|
298
|
+
const port = parseInt(m[1], 10);
|
|
299
|
+
if (!isNaN(port) && port > 1024 && port < 65536 && port !== 8300 && port !== OSBORN_API_PORT) {
|
|
300
|
+
return port;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
270
306
|
// Route prefixes that belong to the agent itself. The proxy fall-through checks
|
|
271
307
|
// this list FIRST so it never intercepts an agent API path.
|
|
272
308
|
const AGENT_ROUTE_PREFIXES = [
|
|
@@ -1498,21 +1534,44 @@ function startApiServer(workingDir, port) {
|
|
|
1498
1534
|
// 3 s until ready.
|
|
1499
1535
|
if (url.pathname === '/editor' && req.method === 'GET') {
|
|
1500
1536
|
// AUTH GATE (future): validate key / signed session before minting the cookie
|
|
1537
|
+
// Optional ?folder= param: must be an absolute path under /workspace with no
|
|
1538
|
+
// directory-traversal sequences. If absent or invalid, fall back to bare '/'.
|
|
1539
|
+
const rawFolder = url.searchParams.get('folder') ?? '';
|
|
1540
|
+
const folderParam = (() => {
|
|
1541
|
+
if (!rawFolder)
|
|
1542
|
+
return '';
|
|
1543
|
+
// Must be absolute, start with /workspace, no traversal sequences
|
|
1544
|
+
if (!rawFolder.startsWith('/workspace') ||
|
|
1545
|
+
rawFolder.includes('..') ||
|
|
1546
|
+
rawFolder.includes('\0'))
|
|
1547
|
+
return '';
|
|
1548
|
+
// Normalise and re-check (catches encoded or doubled slashes resolving outside /workspace)
|
|
1549
|
+
const normalised = rawFolder.replace(/\/+/g, '/');
|
|
1550
|
+
if (!normalised.startsWith('/workspace'))
|
|
1551
|
+
return '';
|
|
1552
|
+
return normalised;
|
|
1553
|
+
})();
|
|
1501
1554
|
if (ideProxyEnabled && ideSessionToken) {
|
|
1502
|
-
// code-server is up β set cookie and redirect
|
|
1555
|
+
// code-server is up β set cookie and redirect into the requested folder (if valid)
|
|
1556
|
+
// or to root if no valid folder was supplied.
|
|
1557
|
+
const location = folderParam ? `/?folder=${encodeURIComponent(folderParam)}` : '/';
|
|
1503
1558
|
res.writeHead(302, {
|
|
1504
1559
|
'Set-Cookie': `osborn_ide=${ideSessionToken}; Path=/; HttpOnly; SameSite=Lax`,
|
|
1505
|
-
'Location':
|
|
1560
|
+
'Location': location,
|
|
1506
1561
|
});
|
|
1507
1562
|
res.end();
|
|
1508
1563
|
}
|
|
1509
1564
|
else {
|
|
1510
|
-
// Not yet running β kick off start in the background, show "Startingβ¦" page
|
|
1565
|
+
// Not yet running β kick off start in the background, show "Startingβ¦" page.
|
|
1566
|
+
// Preserve ?folder= in the refresh URL so it survives the polling loop.
|
|
1511
1567
|
if (codeServerStartHook) {
|
|
1512
1568
|
codeServerStartHook().catch((err) => {
|
|
1513
1569
|
console.error('β startCodeServer() from /editor failed:', err);
|
|
1514
1570
|
});
|
|
1515
1571
|
}
|
|
1572
|
+
const refreshTarget = folderParam
|
|
1573
|
+
? `/editor?folder=${encodeURIComponent(folderParam)}`
|
|
1574
|
+
: '/editor';
|
|
1516
1575
|
res.writeHead(200, {
|
|
1517
1576
|
'Content-Type': 'text/html; charset=utf-8',
|
|
1518
1577
|
'Cache-Control': 'no-store, must-revalidate',
|
|
@@ -1520,7 +1579,7 @@ function startApiServer(workingDir, port) {
|
|
|
1520
1579
|
});
|
|
1521
1580
|
res.end(`<!DOCTYPE html><html><head>
|
|
1522
1581
|
<meta charset="utf-8">
|
|
1523
|
-
<meta http-equiv="refresh" content="3;url
|
|
1582
|
+
<meta http-equiv="refresh" content="3;url=${refreshTarget}">
|
|
1524
1583
|
<title>Starting editorβ¦</title>
|
|
1525
1584
|
<style>body{font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#1e1e1e;color:#ccc}</style>
|
|
1526
1585
|
</head><body><p>Starting your editorβ¦</p></body></html>`);
|
|
@@ -1528,6 +1587,32 @@ function startApiServer(workingDir, port) {
|
|
|
1528
1587
|
return;
|
|
1529
1588
|
}
|
|
1530
1589
|
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
1590
|
+
// ββ Dev-domain subdomain proxy: PORT-slug.dev.voice-native.com β localhost:PORT/* ββ
|
|
1591
|
+
// Fly replay routes the request here from the fleet router; the app lives at root
|
|
1592
|
+
// so the full path is forwarded unchanged (no basePath prefix needed).
|
|
1593
|
+
const devPort = parseDevSubdomainPort(req);
|
|
1594
|
+
if (devPort !== null) {
|
|
1595
|
+
ideLastProxiedActivity = Date.now();
|
|
1596
|
+
const devProxyOptions = {
|
|
1597
|
+
hostname: '127.0.0.1',
|
|
1598
|
+
port: devPort,
|
|
1599
|
+
path: url.pathname + (url.search || ''),
|
|
1600
|
+
method: req.method,
|
|
1601
|
+
headers: { ...req.headers, host: `127.0.0.1:${devPort}` },
|
|
1602
|
+
};
|
|
1603
|
+
const devProxyReq = httpRequest(devProxyOptions, (devProxyRes) => {
|
|
1604
|
+
res.writeHead(devProxyRes.statusCode || 502, devProxyRes.headers);
|
|
1605
|
+
devProxyRes.pipe(res);
|
|
1606
|
+
});
|
|
1607
|
+
devProxyReq.on('error', () => {
|
|
1608
|
+
if (!res.headersSent) {
|
|
1609
|
+
res.writeHead(502);
|
|
1610
|
+
res.end('Dev port proxy error');
|
|
1611
|
+
}
|
|
1612
|
+
});
|
|
1613
|
+
req.pipe(devProxyReq);
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1531
1616
|
// ββ Port proxy: /proxy/PORT/* β localhost:PORT/proxy/PORT/* βββββββββββββ
|
|
1532
1617
|
// Forwards to a local dev server while PRESERVING the full path so that
|
|
1533
1618
|
// basePath-aware apps (e.g. Next.js with basePath: '/proxy/3000') receive
|
|
@@ -1536,7 +1621,7 @@ function startApiServer(workingDir, port) {
|
|
|
1536
1621
|
const portProxyMatch = url.pathname.match(/^\/proxy\/(\d+)(\/.*)?$/);
|
|
1537
1622
|
if (portProxyMatch) {
|
|
1538
1623
|
const targetPort = parseInt(portProxyMatch[1]);
|
|
1539
|
-
if (targetPort > 1024 && targetPort < 65536 && targetPort !== 8300 && targetPort !==
|
|
1624
|
+
if (targetPort > 1024 && targetPort < 65536 && targetPort !== 8300 && targetPort !== OSBORN_API_PORT) {
|
|
1540
1625
|
const portProxyOptions = {
|
|
1541
1626
|
hostname: '127.0.0.1',
|
|
1542
1627
|
port: targetPort,
|
|
@@ -1635,6 +1720,13 @@ function startApiServer(workingDir, port) {
|
|
|
1635
1720
|
// β this must work for the integrated terminal to connect.
|
|
1636
1721
|
// Reject all other upgrades (meeting audio moved to polling in 0.9.xx).
|
|
1637
1722
|
server.on('upgrade', (req, socket, head) => {
|
|
1723
|
+
// Dev-domain subdomain WebSocket proxy (same pattern as HTTP handler above)
|
|
1724
|
+
const wsDevPort = parseDevSubdomainPort(req);
|
|
1725
|
+
if (wsDevPort !== null) {
|
|
1726
|
+
ideLastProxiedActivity = Date.now();
|
|
1727
|
+
wsProxy.ws(req, socket, head, { target: `http://127.0.0.1:${wsDevPort}` });
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1638
1730
|
const upgradePath = new URL(req.url || '/', `http://localhost`).pathname;
|
|
1639
1731
|
const isAgentRoute = AGENT_ROUTE_PREFIXES.some(p => upgradePath === p || upgradePath.startsWith(p + '/'));
|
|
1640
1732
|
if (ideProxyEnabled && hasValidIdeCookie(req) && !isAgentRoute) {
|
|
@@ -5137,7 +5229,7 @@ async function main() {
|
|
|
5137
5229
|
let readySent = false;
|
|
5138
5230
|
const provider = sessionVoiceMode === 'realtime' ? realtimeConfig.provider : 'claude';
|
|
5139
5231
|
// Fetch full session list for startup session browser (all Claude projects)
|
|
5140
|
-
const allSessions = await listAllClaudeSessions(
|
|
5232
|
+
const allSessions = await listAllClaudeSessions();
|
|
5141
5233
|
const recentSessionId = allSessions.length > 0 ? allSessions[0].sessionId : null;
|
|
5142
5234
|
const hasRecentSession = allSessions.length > 0;
|
|
5143
5235
|
// Prepare sessions for frontend (up to 50)
|
|
@@ -5391,7 +5483,7 @@ async function main() {
|
|
|
5391
5483
|
// List available sessions across all Claude projects
|
|
5392
5484
|
console.log('π Listing available sessions (all projects)...');
|
|
5393
5485
|
try {
|
|
5394
|
-
const sessions = await listAllClaudeSessions(
|
|
5486
|
+
const sessions = await listAllClaudeSessions();
|
|
5395
5487
|
await sendToFrontend({
|
|
5396
5488
|
type: 'sessions_list',
|
|
5397
5489
|
// See `/sessions` HTTP handler β baseCwd is the agent's
|
|
@@ -6220,7 +6312,7 @@ async function main() {
|
|
|
6220
6312
|
let resolvedSessionId = targetSessionId;
|
|
6221
6313
|
if (!resolvedSessionId) {
|
|
6222
6314
|
// Pick the most recently modified session, excluding the focused one.
|
|
6223
|
-
const allSess = await listAllClaudeSessions(
|
|
6315
|
+
const allSess = await listAllClaudeSessions();
|
|
6224
6316
|
const focusedId = currentLLM?.sessionId || currentResumeSessionId || null;
|
|
6225
6317
|
const candidate = allSess.find(s => s.sessionId !== focusedId);
|
|
6226
6318
|
resolvedSessionId = candidate?.sessionId ?? undefined;
|
|
@@ -6294,8 +6386,9 @@ async function main() {
|
|
|
6294
6386
|
// Process exited between readdir and read β skip it
|
|
6295
6387
|
}
|
|
6296
6388
|
}
|
|
6297
|
-
// Sort by resident memory descending, cap at 40
|
|
6389
|
+
// Sort by resident memory descending, cap table at 40 but keep real total
|
|
6298
6390
|
processes.sort((a, b) => b.rssMb - a.rssMb);
|
|
6391
|
+
const totalCount = processes.length;
|
|
6299
6392
|
const topProcesses = processes.slice(0, 40);
|
|
6300
6393
|
// Reuse the existing os module memory totals (same source as /health endpoint)
|
|
6301
6394
|
const totalMb = Math.round(totalmem() / 1024 / 1024);
|
|
@@ -6304,6 +6397,7 @@ async function main() {
|
|
|
6304
6397
|
await sendToFrontend({
|
|
6305
6398
|
type: 'process_list',
|
|
6306
6399
|
processes: topProcesses,
|
|
6400
|
+
totalCount,
|
|
6307
6401
|
memory: { usedMb, totalMb, freeMb },
|
|
6308
6402
|
});
|
|
6309
6403
|
}
|