osborn 0.9.152 → 0.9.153

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.
Files changed (2) hide show
  1. package/dist/index.js +47 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -298,6 +298,12 @@ wsProxy.on('error', (err, _req, socket) => {
298
298
  // valid cookie are forwarded, so a forgotten agent route can never be silently
299
299
  // swallowed by the IDE proxy. Cleared in stopIde().
300
300
  let ideSessionToken = null;
301
+ // Cancellation-generation guard for startCodeServer(). Incremented by stopIde()
302
+ // every time the IDE is torn down. startCodeServer() captures myGen at entry;
303
+ // if ideStartGeneration !== myGen when the poll resolves, the start has been
304
+ // superseded (stopIde ran mid-flight) and the result is discarded instead of
305
+ // re-enabling the proxy with no participant present.
306
+ let ideStartGeneration = 0;
301
307
  // Helper: parse the osborn_ide cookie from a request and return true iff it
302
308
  // matches the current ideSessionToken (and a token exists).
303
309
  function hasValidIdeCookie(req) {
@@ -1539,7 +1545,7 @@ function startApiServer(workingDir, port) {
1539
1545
  port: 8300,
1540
1546
  path: targetUrl.pathname + (targetUrl.search || ''),
1541
1547
  method: req.method,
1542
- headers: { ...req.headers, host: '127.0.0.1:8300' },
1548
+ headers: { ...req.headers, host: '127.0.0.1:8300', 'x-forwarded-host': req.headers.host },
1543
1549
  };
1544
1550
  const proxyReq = httpRequest(options, (proxyRes) => {
1545
1551
  res.writeHead(proxyRes.statusCode || 502, proxyRes.headers);
@@ -1602,6 +1608,13 @@ function startApiServer(workingDir, port) {
1602
1608
  const isAgentRoute = AGENT_ROUTE_PREFIXES.some(p => upgradePath === p || upgradePath.startsWith(p + '/'));
1603
1609
  if (ideProxyEnabled && hasValidIdeCookie(req) && !isAgentRoute) {
1604
1610
  ideLastProxiedActivity = Date.now();
1611
+ // code-server's authenticateOrigin() compares Origin against the effective host
1612
+ // (X-Forwarded-Host first). changeOrigin:true rewrites Host→127.0.0.1:8300 while the
1613
+ // browser's Origin stays the public hostname, causing a 403 → WS 1006. Preserve the
1614
+ // real public host so the origin check matches.
1615
+ if (!req.headers['x-forwarded-host'] && req.headers.host) {
1616
+ req.headers['x-forwarded-host'] = req.headers.host;
1617
+ }
1605
1618
  wsProxy.ws(req, socket, head, { target: IDE_TARGET });
1606
1619
  return;
1607
1620
  }
@@ -2339,6 +2352,9 @@ async function main() {
2339
2352
  };
2340
2353
  // Kill code-server, disable proxy, clear interval, reset IDE state.
2341
2354
  const stopIde = (reason) => {
2355
+ // Bump the generation counter so any in-flight startCodeServer() knows it
2356
+ // has been cancelled and must not re-enable the proxy or mint a new token.
2357
+ ideStartGeneration++;
2342
2358
  ideProxyEnabled = false;
2343
2359
  ideSessionToken = null;
2344
2360
  ideStartInProgress = null;
@@ -2412,6 +2428,11 @@ async function main() {
2412
2428
  // Fresh start: kill any stale proc (shouldn't normally exist here, but be safe).
2413
2429
  if (ideCodeServerProc)
2414
2430
  stopIde();
2431
+ // Capture the current generation AFTER any preceding stopIde() call so that
2432
+ // if stopIde() already ran (above), myGen reflects the incremented counter.
2433
+ // A concurrent stopIde() after this point will increment ideStartGeneration
2434
+ // beyond myGen, signalling cancellation to the poll loop and success branch.
2435
+ const myGen = ideStartGeneration;
2415
2436
  let resolveFn;
2416
2437
  let rejectFn;
2417
2438
  ideStartInProgress = new Promise((res, rej) => { resolveFn = res; rejectFn = rej; });
@@ -2462,11 +2483,13 @@ async function main() {
2462
2483
  await new Promise((res, rej) => {
2463
2484
  let attempts = 0;
2464
2485
  const poll = setInterval(() => {
2465
- // Early-exit: the proc we spawned is no longer the active one (stopIde
2466
- // was called, or a concurrent path replaced it).
2467
- if (ideCodeServerProc !== codeServerProc) {
2486
+ // Early-exit: stopIde() was called while we were waiting (generation bumped),
2487
+ // OR the proc we spawned is no longer the active one for another reason.
2488
+ // Either way, cancel the poll immediately so we don't keep hitting 8300
2489
+ // for a boot that has already been invalidated.
2490
+ if (ideStartGeneration !== myGen || ideCodeServerProc !== codeServerProc) {
2468
2491
  clearInterval(poll);
2469
- rej(new Error('code-server process was replaced or stopped mid-poll'));
2492
+ rej(new Error('code-server start cancelled (stopIde ran mid-poll)'));
2470
2493
  return;
2471
2494
  }
2472
2495
  attempts++;
@@ -2487,6 +2510,25 @@ async function main() {
2487
2510
  }, 500);
2488
2511
  });
2489
2512
  console.log('✅ code-server ready on 8300');
2513
+ // Generation guard: check again right before enabling the proxy. The poll
2514
+ // already checks this on each tick, but there is a narrow window between the
2515
+ // last tick resolving and reaching here. If stopIde() ran in that window,
2516
+ // ideStartGeneration will have been incremented beyond myGen — we must NOT
2517
+ // enable the proxy or mint a token, and we must kill the orphan process so
2518
+ // no zombie code-server is left running with no participant present.
2519
+ if (ideStartGeneration !== myGen) {
2520
+ console.log('🖥️ IDE start cancelled (stopIde ran while code-server was booting) — killing orphan proc');
2521
+ try {
2522
+ codeServerProc.kill();
2523
+ }
2524
+ catch { }
2525
+ // ideCodeServerProc may already be null (stopIde cleared it); only null it
2526
+ // if it still points to our proc to avoid clobbering a newer start.
2527
+ if (ideCodeServerProc === codeServerProc)
2528
+ ideCodeServerProc = null;
2529
+ resolveFn(); // resolve (not reject) so the promise chain exits cleanly
2530
+ return;
2531
+ }
2490
2532
  // Mint the session cookie token and enable the proxy. From this point the
2491
2533
  // /editor route will 302+Set-Cookie and the fall-through proxy will only
2492
2534
  // forward requests that carry the valid osborn_ide cookie.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.152",
3
+ "version": "0.9.153",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {