MCPcopy
hub / github.com/garrytan/gstack / buildFetchHandler

Function buildFetchHandler

browse/src/server.ts:1469–2857  ·  view source on GitHub ↗
(cfg: ServerConfig)

Source from the content-addressed store, hash-verified

1467 * responsibility — embedders may fd-pass; CLI uses Bun.serve normally.
1468 */
1469export function buildFetchHandler(cfg: ServerConfig): ServerHandle {
1470 if (!cfg.authToken || cfg.authToken.length < 16) {
1471 throw new Error('buildFetchHandler: cfg.authToken must be a non-empty string >= 16 chars');
1472 }
1473 if (!cfg.browserManager) {
1474 throw new Error('buildFetchHandler: cfg.browserManager is required');
1475 }
1476
1477 // Re-run init with cfg-provided values. ensureStateDir is idempotent
1478 // (mkdir -p); initAuditLog is idempotent (sets a module string);
1479 // initRegistry is idempotent for same-token, throws for different-token.
1480 // Owning init here (instead of at module load) means cfg.authToken is the
1481 // single source of truth for the registry root token.
1482 ensureStateDir(cfg.config);
1483 initAuditLog(cfg.config.auditLog);
1484 initRegistry(cfg.authToken);
1485
1486 const { authToken, browserManager: cfgBrowserManager, startTime, beforeRoute, browsePort } = cfg;
1487 // Strict opt-out: only explicit `false` flips the gate. Any other value
1488 // (undefined, truthy non-bool from a JS caller bypassing TS, etc.) defaults
1489 // to gstack-owns. Matches the "default-true preserves CLI bit-for-bit"
1490 // premise even under malformed cfg.
1491 const ownsTerminalAgent = cfg.ownsTerminalAgent === false ? false : true;
1492
1493 // ─── Terminal-Agent Watchdog (v1.44+) ─────────────────────────────
1494 //
1495 // The terminal-agent process can die independently of the server: SIGKILL
1496 // from the OS OOM killer, an uncaught exception under load, an external
1497 // `pkill` from a sibling debugging session. Pre-v1.44 the sidebar would
1498 // see the broken connection and stay broken until the user reloaded.
1499 // Now: 60s ticker checks the recorded agent PID, respawns via the shared
1500 // spawnTerminalAgent helper if dead.
1501 //
1502 // Identity-based — uses readAgentRecord + isProcessAlive, NOT a process
1503 // name probe. Critical: prevents respawning around a slow-but-alive agent
1504 // (which would create split-brain — two agents writing the port file,
1505 // tokens diverging between them, mystery PTY upgrade failures).
1506 //
1507 // Crash-loop guard: 3 respawn attempts inside 60s → stop trying and emit
1508 // a one-line error. Manual `forceRestart` from the sidebar clears the
1509 // history (the user is the explicit signal to retry).
1510 //
1511 // Only active when ownsTerminalAgent === true. Embedders that pre-launch
1512 // their own PTY server (gbrowser phoenix overlay) must not be auto-respawned
1513 // by us — their lifecycle is their concern.
1514 let agentWatchdogInterval: ReturnType<typeof setInterval> | null = null;
1515 const respawnHistory: number[] = [];
1516 const AGENT_WATCHDOG_TICK_MS = parseInt(
1517 process.env.GSTACK_AGENT_WATCHDOG_TICK_MS || '60000',
1518 10,
1519 );
1520 const RESPAWN_GUARD_WINDOW_MS = 60_000;
1521 const RESPAWN_GUARD_MAX = 3;
1522 let agentRespawnGuardTripped = false;
1523
1524 if (ownsTerminalAgent) {
1525 agentWatchdogInterval = setInterval(() => {
1526 if (isShuttingDown) return;

Callers 3

startFunction · 0.85

Calls 9

ensureStateDirFunction · 0.90
initAuditLogFunction · 0.90
initRegistryFunction · 0.90
readAgentRecordFunction · 0.90
isProcessAliveFunction · 0.90
spawnTerminalAgentFunction · 0.90
activeShutdownFunction · 0.85
makeFetchHandlerFunction · 0.85
pushMethod · 0.45

Tested by

no test coverage detected