osborn 0.9.154 β†’ 0.9.156

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.
@@ -101,6 +101,7 @@ ENV OSBORN_CWD=/workspace
101
101
  # Caveat: native user modules are tied to the image's Node ABI (currently 22) β€”
102
102
  # a future Node-major image bump would need an `npm rebuild` of volume globals.
103
103
  ENV NPM_CONFIG_PREFIX=/workspace/.npm-global
104
+ ENV NPM_CONFIG_CACHE=/tmp/npm-cache
104
105
  ENV PATH=/workspace/.npm-global/bin:$PATH
105
106
 
106
107
  WORKDIR /workspace
@@ -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/index.js CHANGED
@@ -267,6 +267,7 @@ 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);
270
271
  // Route prefixes that belong to the agent itself. The proxy fall-through checks
271
272
  // this list FIRST so it never intercepts an agent API path.
272
273
  const AGENT_ROUTE_PREFIXES = [
@@ -1498,21 +1499,44 @@ function startApiServer(workingDir, port) {
1498
1499
  // 3 s until ready.
1499
1500
  if (url.pathname === '/editor' && req.method === 'GET') {
1500
1501
  // AUTH GATE (future): validate key / signed session before minting the cookie
1502
+ // Optional ?folder= param: must be an absolute path under /workspace with no
1503
+ // directory-traversal sequences. If absent or invalid, fall back to bare '/'.
1504
+ const rawFolder = url.searchParams.get('folder') ?? '';
1505
+ const folderParam = (() => {
1506
+ if (!rawFolder)
1507
+ return '';
1508
+ // Must be absolute, start with /workspace, no traversal sequences
1509
+ if (!rawFolder.startsWith('/workspace') ||
1510
+ rawFolder.includes('..') ||
1511
+ rawFolder.includes('\0'))
1512
+ return '';
1513
+ // Normalise and re-check (catches encoded or doubled slashes resolving outside /workspace)
1514
+ const normalised = rawFolder.replace(/\/+/g, '/');
1515
+ if (!normalised.startsWith('/workspace'))
1516
+ return '';
1517
+ return normalised;
1518
+ })();
1501
1519
  if (ideProxyEnabled && ideSessionToken) {
1502
- // code-server is up β€” set cookie and redirect to root (code-server's asset base)
1520
+ // code-server is up β€” set cookie and redirect into the requested folder (if valid)
1521
+ // or to root if no valid folder was supplied.
1522
+ const location = folderParam ? `/?folder=${encodeURIComponent(folderParam)}` : '/';
1503
1523
  res.writeHead(302, {
1504
1524
  'Set-Cookie': `osborn_ide=${ideSessionToken}; Path=/; HttpOnly; SameSite=Lax`,
1505
- 'Location': '/',
1525
+ 'Location': location,
1506
1526
  });
1507
1527
  res.end();
1508
1528
  }
1509
1529
  else {
1510
- // Not yet running β€” kick off start in the background, show "Starting…" page
1530
+ // Not yet running β€” kick off start in the background, show "Starting…" page.
1531
+ // Preserve ?folder= in the refresh URL so it survives the polling loop.
1511
1532
  if (codeServerStartHook) {
1512
1533
  codeServerStartHook().catch((err) => {
1513
1534
  console.error('❌ startCodeServer() from /editor failed:', err);
1514
1535
  });
1515
1536
  }
1537
+ const refreshTarget = folderParam
1538
+ ? `/editor?folder=${encodeURIComponent(folderParam)}`
1539
+ : '/editor';
1516
1540
  res.writeHead(200, {
1517
1541
  'Content-Type': 'text/html; charset=utf-8',
1518
1542
  'Cache-Control': 'no-store, must-revalidate',
@@ -1520,7 +1544,7 @@ function startApiServer(workingDir, port) {
1520
1544
  });
1521
1545
  res.end(`<!DOCTYPE html><html><head>
1522
1546
  <meta charset="utf-8">
1523
- <meta http-equiv="refresh" content="3;url=/editor">
1547
+ <meta http-equiv="refresh" content="3;url=${refreshTarget}">
1524
1548
  <title>Starting editor…</title>
1525
1549
  <style>body{font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#1e1e1e;color:#ccc}</style>
1526
1550
  </head><body><p>Starting your editor…</p></body></html>`);
@@ -1528,6 +1552,37 @@ function startApiServer(workingDir, port) {
1528
1552
  return;
1529
1553
  }
1530
1554
  // ────────────────────────────────────────────────────────────────────────
1555
+ // ── Port proxy: /proxy/PORT/* β†’ localhost:PORT/proxy/PORT/* ─────────────
1556
+ // Forwards to a local dev server while PRESERVING the full path so that
1557
+ // basePath-aware apps (e.g. Next.js with basePath: '/proxy/3000') receive
1558
+ // the complete URL and can strip the prefix themselves. No cookie required β€”
1559
+ // the machine is per-user and Fly's HTTPS is the outer auth boundary.
1560
+ const portProxyMatch = url.pathname.match(/^\/proxy\/(\d+)(\/.*)?$/);
1561
+ if (portProxyMatch) {
1562
+ const targetPort = parseInt(portProxyMatch[1]);
1563
+ if (targetPort > 1024 && targetPort < 65536 && targetPort !== 8300 && targetPort !== OSBORN_API_PORT) {
1564
+ const portProxyOptions = {
1565
+ hostname: '127.0.0.1',
1566
+ port: targetPort,
1567
+ path: url.pathname + (url.search || ''),
1568
+ method: req.method,
1569
+ headers: { ...req.headers, host: `127.0.0.1:${targetPort}` },
1570
+ };
1571
+ const portProxyReq = httpRequest(portProxyOptions, (portProxyRes) => {
1572
+ res.writeHead(portProxyRes.statusCode || 502, portProxyRes.headers);
1573
+ portProxyRes.pipe(res);
1574
+ });
1575
+ portProxyReq.on('error', () => {
1576
+ if (!res.headersSent) {
1577
+ res.writeHead(502);
1578
+ res.end('Port proxy error');
1579
+ }
1580
+ });
1581
+ req.pipe(portProxyReq);
1582
+ return;
1583
+ }
1584
+ }
1585
+ // ────────────────────────────────────────────────────────────────────────
1531
1586
  // ── IDE reverse proxy (cookie-gated fall-through) ────────────────────────
1532
1587
  // Forwards to code-server ONLY when:
1533
1588
  // 1. ideProxyEnabled is true (code-server is confirmed ready), AND
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.154",
3
+ "version": "0.9.156",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {