@agimon-ai/doompi-web 0.0.1-alpha.10 → 0.0.1-alpha.13

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 (106) hide show
  1. package/dist/src/adapters/bundledServer.cjs +1 -1
  2. package/dist/src/adapters/bundledServer.cjs.map +1 -1
  3. package/dist/src/adapters/bundledServer.mjs +1 -1
  4. package/dist/src/adapters/bundledServer.mjs.map +1 -1
  5. package/dist/src/adapters/httpServer.cjs +2 -2
  6. package/dist/src/adapters/httpServer.cjs.map +1 -1
  7. package/dist/src/adapters/httpServer.d.cts.map +1 -1
  8. package/dist/src/adapters/httpServer.d.mts.map +1 -1
  9. package/dist/src/adapters/httpServer.mjs +2 -2
  10. package/dist/src/adapters/httpServer.mjs.map +1 -1
  11. package/dist/src/adapters/promptImages.cjs +3 -0
  12. package/dist/src/adapters/promptImages.cjs.map +1 -0
  13. package/dist/src/adapters/promptImages.mjs +3 -0
  14. package/dist/src/adapters/promptImages.mjs.map +1 -0
  15. package/dist/src/adapters/sessionHub.cjs +1 -1
  16. package/dist/src/adapters/sessionHub.cjs.map +1 -1
  17. package/dist/src/adapters/sessionHub.mjs +1 -1
  18. package/dist/src/adapters/sessionHub.mjs.map +1 -1
  19. package/dist/src/adapters/settingsRoutes.cjs +1 -1
  20. package/dist/src/adapters/settingsRoutes.cjs.map +1 -1
  21. package/dist/src/adapters/settingsRoutes.mjs +1 -1
  22. package/dist/src/adapters/settingsRoutes.mjs.map +1 -1
  23. package/dist/src/adapters/syncGuard.cjs +1 -1
  24. package/dist/src/adapters/syncGuard.cjs.map +1 -1
  25. package/dist/src/adapters/syncGuard.mjs +1 -1
  26. package/dist/src/adapters/syncGuard.mjs.map +1 -1
  27. package/dist/src/adapters/threadJournals.cjs +1 -1
  28. package/dist/src/adapters/threadJournals.cjs.map +1 -1
  29. package/dist/src/adapters/threadJournals.mjs +1 -1
  30. package/dist/src/adapters/threadJournals.mjs.map +1 -1
  31. package/dist/src/adapters/webPluginGenerate.cjs +1 -1
  32. package/dist/src/adapters/webPluginGenerate.cjs.map +1 -1
  33. package/dist/src/adapters/webPluginGenerate.mjs +1 -1
  34. package/dist/src/adapters/webPluginGenerate.mjs.map +1 -1
  35. package/dist/src/adapters/webTelemetry.cjs +1 -1
  36. package/dist/src/adapters/webTelemetry.cjs.map +1 -1
  37. package/dist/src/adapters/webTelemetry.mjs +1 -1
  38. package/dist/src/adapters/webTelemetry.mjs.map +1 -1
  39. package/dist/src/types/settings.cjs +1 -1
  40. package/dist/src/types/settings.cjs.map +1 -1
  41. package/dist/src/types/settings.mjs +1 -1
  42. package/dist/src/types/settings.mjs.map +1 -1
  43. package/dist/web/assets/{CodeEditorView-Drbj5Jmq.js → CodeEditorView-Cv2_Pqe1.js} +3 -3
  44. package/dist/web/assets/{CodeEditorView-Drbj5Jmq.js.map → CodeEditorView-Cv2_Pqe1.js.map} +1 -1
  45. package/dist/web/assets/{dist-7iNxebeg.js → dist-1N2cDSZc.js} +2 -2
  46. package/dist/web/assets/{dist-7iNxebeg.js.map → dist-1N2cDSZc.js.map} +1 -1
  47. package/dist/web/assets/{dist-CyY9Rnb9.js → dist-BvJltgg9.js} +2 -2
  48. package/dist/web/assets/{dist-CyY9Rnb9.js.map → dist-BvJltgg9.js.map} +1 -1
  49. package/dist/web/assets/{dist-BCRyBbdR.js → dist-CMcoiIPx.js} +2 -2
  50. package/dist/web/assets/{dist-BCRyBbdR.js.map → dist-CMcoiIPx.js.map} +1 -1
  51. package/dist/web/assets/{dist-CM-9xFgG.js → dist-CSIKLWGg.js} +2 -2
  52. package/dist/web/assets/{dist-CM-9xFgG.js.map → dist-CSIKLWGg.js.map} +1 -1
  53. package/dist/web/assets/{dist-C2KhFiEs.js → dist-CVA2128k.js} +1 -1
  54. package/dist/web/assets/{dist-ClQi4oJ4.js → dist-CfQNkp46.js} +2 -2
  55. package/dist/web/assets/{dist-ClQi4oJ4.js.map → dist-CfQNkp46.js.map} +1 -1
  56. package/dist/web/assets/{dist-D_AIHDMX.js → dist-Dc0vEh5H.js} +2 -2
  57. package/dist/web/assets/{dist-D_AIHDMX.js.map → dist-Dc0vEh5H.js.map} +1 -1
  58. package/dist/web/assets/{dist-DjFwY_To.js → dist-DlhyAlgc.js} +2 -2
  59. package/dist/web/assets/{dist-DjFwY_To.js.map → dist-DlhyAlgc.js.map} +1 -1
  60. package/dist/web/assets/{dist-joNLZ1a7.js → dist-Dusr83oZ.js} +2 -2
  61. package/dist/web/assets/{dist-joNLZ1a7.js.map → dist-Dusr83oZ.js.map} +1 -1
  62. package/dist/web/assets/{dist-BiFP6LQz.js → dist-x765ui50.js} +2 -2
  63. package/dist/web/assets/{dist-BiFP6LQz.js.map → dist-x765ui50.js.map} +1 -1
  64. package/dist/web/assets/index-CdO0WgVA.js +101 -0
  65. package/dist/web/assets/index-CdO0WgVA.js.map +1 -0
  66. package/dist/web/assets/index-_BcdH66j.css +2 -0
  67. package/dist/web/index.html +2 -2
  68. package/package.json +10 -10
  69. package/src/adapters/bundledServer.ts +13 -6
  70. package/src/adapters/httpServer.ts +47 -5
  71. package/src/adapters/promptImages.ts +80 -0
  72. package/src/adapters/sessionHub.ts +46 -3
  73. package/src/adapters/settingsRoutes.ts +51 -0
  74. package/src/adapters/syncGuard.ts +4 -0
  75. package/src/adapters/threadJournals.ts +69 -9
  76. package/src/adapters/webPluginGenerate.ts +3 -0
  77. package/src/adapters/webTelemetry.ts +109 -24
  78. package/src/types/settings.ts +21 -0
  79. package/src/web/features/activity/ActivityDock.tsx +16 -10
  80. package/src/web/features/activity/ContextItemDialog.tsx +204 -0
  81. package/src/web/features/activity/ContextPanel.tsx +27 -6
  82. package/src/web/features/leader/CommandPalette.tsx +11 -3
  83. package/src/web/features/sessions/SessionRail.tsx +4 -1
  84. package/src/web/features/settings/AppearanceSettings.tsx +6 -8
  85. package/src/web/features/settings/ContributedSettings.tsx +35 -22
  86. package/src/web/features/settings/ImageSettings.tsx +112 -0
  87. package/src/web/features/settings/NotificationSettings.tsx +6 -8
  88. package/src/web/features/settings/PluginSettings.tsx +9 -2
  89. package/src/web/features/settings/ProviderSettings.tsx +15 -13
  90. package/src/web/features/settings/RemoteControlSettings.tsx +6 -8
  91. package/src/web/features/settings/RepositorySettings.tsx +9 -10
  92. package/src/web/features/settings/RepositoryWorkspace.tsx +21 -18
  93. package/src/web/features/settings/SettingsMenu.tsx +6 -3
  94. package/src/web/features/settings/SettingsPanelHost.tsx +2 -4
  95. package/src/web/features/settings/SettingsSectionHeader.tsx +34 -0
  96. package/src/web/features/settings/TunnelSettings.tsx +8 -8
  97. package/src/web/lib/browserTelemetry.ts +112 -7
  98. package/src/web/lib/composition.ts +3 -3
  99. package/src/web/lib/contextDetailApi.ts +55 -0
  100. package/src/web/lib/settingsApi.ts +37 -0
  101. package/src/web/lib/settingsSections.ts +6 -0
  102. package/src/web/main.tsx +4 -0
  103. package/src/web/routes/SettingsPage.tsx +15 -9
  104. package/dist/web/assets/index-BuzTi9Gb.css +0 -2
  105. package/dist/web/assets/index-Ek5bNpFT.js +0 -101
  106. package/dist/web/assets/index-Ek5bNpFT.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"httpServer.cjs","names":["REMOTE_HTTP_ROUTE","REMOTE_CHANNEL_ROUTE","path","fileURLToPath","fs","createSessionHub","watchRegistry","createServerSpawner","readGitStatus","createHash","findRepositoryRoot","DOOM_API_ROUTE_PREFIX","API_SESSION_QUERY_PARAM","readTraceContext","proxyToSocket","forwardedTraceContext","createWebTelemetry","createRemoteAccessStore","globalDoomConfigDirectory","readSyncRegistration","readRegistryRecords","createSyncGuard","resolveWebComposition","loadHubChannels","createThreadJournals","Hono","createNodeWebSocket","createBundlePublication","planSessionMigration","describeStranded","createRemoteAccess","createTunnelLauncher","serve","insideSandbox","createLiveWebPush","parseDoomNotificationEntry","createRemoteGuard","getCookie","DEVICE_COOKIE","allowedOriginsFromEnv","requestOperation","bodyLimit","createBrowserTelemetryRateLimit","WEB_TELEMETRY_ROUTE","parseBrowserPerformanceBatch","REMOTE_PUSH_KEY_ROUTE","REMOTE_PUSH_ROUTE","createPiWebSocketListener","createPiHubService","PiServerError","PiServer","createProviderAuth","readSyncDrift","readSyncState","PACKAGE_API_DIR_ENV","loadPackageApis","SESSIONS_API_ROUTE","SessionManager","listSessionFiles","DIRECTORIES_API_ROUTE","suggestDirectories","SESSION_FILE_ROUTE","readSessionFile","MAX_SESSION_FILE_BYTES","sessionFileHeaders","SESSION_SOCKET_ROUTE","randomUUID","hubHello","SESSIONS_SNAPSHOT_TYPE","SESSION_UPSERT_TYPE","SESSION_REMOVED_TYPE","sessionFrameEnvelope","threadFrameEnvelope","threadBacklog","PROTOCOL_SOCKET_ROUTE","resolveAssetPath","contentTypeFor","BUNDLE_MANIFEST_ROUTE","assetFor","PAIRING_PAGE_ROUTE","HUB_RESYNCED_TYPE","shutdownWebTelemetry"],"sources":["../../../src/adapters/httpServer.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { serve } from '@hono/node-server';\nimport { createNodeWebSocket } from '@hono/node-ws';\nimport { SessionManager } from '@earendil-works/pi-coding-agent';\nimport { PiServer, PiServerError } from '@earendil-works/pi-server';\nimport { readSyncDrift, readSyncRegistration } from '@agimon-ai/doompi/services';\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config';\nimport { readSyncState, type SyncState } from '@agimon-ai/doompi/services/syncState';\nimport type { DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport { BUNDLE_MANIFEST_ROUTE, assetFor } from '@agimon-ai/doompi-web-security';\nimport { createPiHubService } from './piHubService.ts';\nimport { createSyncGuard, type SyncGuard } from './syncGuard.ts';\nimport { createPiWebSocketListener } from './piWebSocketListener.ts';\nimport { type Context, Hono } from 'hono';\nimport { getCookie } from 'hono/cookie';\nimport { bodyLimit } from 'hono/body-limit';\nimport type { WebHubChannel } from '@agimon-ai/doompi-web-contracts';\nimport {\n DOOM_API_ROUTE_PREFIX,\n type DoomApi,\n type DoomApiHandler,\n type DoomRepositorySyncView,\n} from '@agimon-ai/doompi-extension-contracts/package-api';\nimport { loadPackageApis, PACKAGE_API_DIR_ENV } from '@agimon-ai/doompi-extension-contracts/package-api-loader';\nimport { insideSandbox } from '@agimon-ai/doompi-extension-contracts/sandbox-harness';\nimport { findRepositoryRoot } from '@agimon-ai/doompi/utils/repository';\nimport { sessionFileHeaders } from '../services/fileMedia.ts';\nimport { contentTypeFor, resolveAssetPath } from '../services/staticAssets.ts';\nimport { MAX_SESSION_FILE_BYTES, SESSION_FILE_ROUTE } from '../types/media.ts';\nimport type { SettingsRepository } from '../types/settings.ts';\nimport type { WebServer, WebServerOptions } from '../types/bridge.ts';\nimport {\n API_SESSION_QUERY_PARAM,\n DIRECTORIES_API_ROUTE,\n HISTORY_REQUEST_TYPE,\n HUB_PROTOCOL_VERSION,\n HUB_RESYNCED_TYPE,\n HUB_ROLE,\n SESSIONS_API_ROUTE,\n SESSIONS_SNAPSHOT_TYPE,\n SESSION_COMMAND_TYPE,\n SESSION_REMOVED_TYPE,\n SESSION_UPSERT_TYPE,\n SUBSCRIBE_THREAD_TYPE,\n SUBSCRIBE_TYPE,\n UNSUBSCRIBE_THREAD_TYPE,\n UNSUBSCRIBE_TYPE,\n hubHello,\n sessionFrameEnvelope,\n threadBacklog,\n threadFrameEnvelope,\n} from '../types/hub.ts';\nimport { parseDoomNotificationEntry } from '../types/notification.ts';\nimport { ATTACH_TYPE, type SessionFrame } from '../types/session.ts';\nimport { readGitStatus } from './gitStatus.ts';\nimport { readRegistryRecords, watchRegistry } from './registryWatcher.ts';\nimport { createServerSpawner } from './serverSpawner.ts';\nimport { createSessionHub, type SessionHub } from './sessionHub.ts';\nimport { allowedOriginsFromEnv } from '../services/remoteGuardPolicy.ts';\nimport { describeStranded, planSessionMigration } from '../services/sessionMigration.ts';\nimport { registerAuthRoutes } from './authRoutes.ts';\nimport { registerSettingsRoutes } from './settingsRoutes.ts';\nimport { createProviderAuth } from './providerAuth.ts';\nimport { createRemoteGuard } from './remoteGuard.ts';\nimport { createRemoteAccess } from './remoteAccess.ts';\nimport { createBundlePublication } from './bundlePublication.ts';\nimport { createLiveWebPush, type LiveWebPush } from './webPush.ts';\nimport { createRemoteAccessStore } from './remoteAccessStore.ts';\nimport { registerRemoteRoutes } from './remoteRoutes.ts';\nimport { createTunnelLauncher, reapStaleTunnel } from './tunnelProcess.ts';\nimport {\n DEVICE_COOKIE,\n PAIRING_PAGE_ROUTE,\n PROTOCOL_SOCKET_ROUTE,\n REMOTE_CHANNEL_ROUTE,\n REMOTE_HTTP_ROUTE,\n REMOTE_PUSH_KEY_ROUTE,\n REMOTE_PUSH_ROUTE,\n SESSION_SOCKET_ROUTE,\n type RemoteAccessSettings,\n} from '../types/remoteAccess.ts';\nimport { suggestDirectories } from './directoryListing.ts';\nimport { listSessionFiles, readSessionFile } from './sessionFiles.ts';\nimport { proxyToSocket } from './packageApiProxy.ts';\nimport { createThreadJournals } from './threadJournals.ts';\nimport { loadHubChannels } from './webHubPluginLoader.ts';\nimport {\n createBrowserTelemetryRateLimit,\n createWebTelemetry,\n forwardedTraceContext,\n parseBrowserPerformanceBatch,\n readTraceContext,\n requestOperation,\n shutdownWebTelemetry,\n WEB_TELEMETRY_ROUTE,\n} from './webTelemetry.ts';\nimport { resolveWebComposition } from './webComposition.ts';\n\n// Pi's protocol rides its own socket so the DoomPi channel keeps the\n// vocabulary the protocol has no shape for: dialogs, minor modes, selection.\n/** Directory suggestions per picker query; more than this means \"type further\". */\nconst DIRECTORY_SUGGESTION_LIMIT = 12;\nconst INDEX_FILE = 'index.html';\n/** Env override for the assets directory, set by launchers that know a synced bundle. */\nconst WEB_DIST_ENV = 'DOOMPI_WEB_DIST';\n/** Comma-separated origins the operator allows past the guard, for dev setups this package cannot guess. */\nconst ALLOW_ORIGIN_ENV = 'DOOMPI_WEB_ALLOW_ORIGIN';\nconst RAW_BUNDLE_PREFIX = '/bundle-assets/';\nconst PWA_ASSET_PREFIX = '/pwa/';\nconst SEALED_HTTP_VERSION = 1;\n/** Upper bound for any body accepted from the tunnel before route-specific limits. */\nconst TUNNEL_BODY_BYTES = 8 * 1024 * 1024;\nconst TUNNEL_HEADER_BYTES = 16 * 1024;\nconst TUNNEL_HEADERS_TIMEOUT_MS = 5000;\nconst TUNNEL_REQUEST_TIMEOUT_MS = 10_000;\nconst TUNNEL_CONNECTION_LIMIT = 64;\nconst TUNNEL_REQUESTS_PER_SOCKET = 100;\nconst SEALED_HTTP_METHODS = new Set(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']);\nconst SEALED_HTTP_FORBIDDEN_HEADERS = new Set([\n 'connection',\n 'content-length',\n 'cookie',\n 'forwarded',\n 'host',\n 'origin',\n 'transfer-encoding',\n 'upgrade',\n 'x-forwarded-for',\n 'x-forwarded-host',\n 'x-forwarded-proto',\n]);\n\ninterface SealedRequestBindings {\n incoming?: { socket?: { localPort?: number } };\n sealedDeviceId?: string;\n}\n\ninterface SealedHttpRequest {\n v: number;\n method: string;\n target: string;\n headers: Array<[string, string]>;\n body?: string;\n}\n\nfunction parseSealedHttpRequest(value: unknown): SealedHttpRequest | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const request = value as Partial<SealedHttpRequest>;\n if (\n request.v !== SEALED_HTTP_VERSION ||\n typeof request.method !== 'string' ||\n !SEALED_HTTP_METHODS.has(request.method) ||\n typeof request.target !== 'string' ||\n !request.target.startsWith('/') ||\n request.target.startsWith('//') ||\n request.target.includes('#') ||\n !Array.isArray(request.headers) ||\n !request.headers.every(\n (header) => Array.isArray(header) && header.length === 2 && header.every((part) => typeof part === 'string'),\n ) ||\n (request.body !== undefined && typeof request.body !== 'string')\n ) {\n return undefined;\n }\n const target = new URL(request.target, 'http://sealed.doompi.invalid');\n if (target.pathname === REMOTE_HTTP_ROUTE || target.pathname === REMOTE_CHANNEL_ROUTE) return undefined;\n return request as SealedHttpRequest;\n}\n\nfunction sealedRequestHeaders(request: SealedHttpRequest, outer: Context): Headers | undefined {\n try {\n const headers = new Headers();\n for (const [name, value] of request.headers) {\n const normalized = name.toLowerCase();\n if (SEALED_HTTP_FORBIDDEN_HEADERS.has(normalized) || normalized.startsWith('sec-')) continue;\n headers.append(name, value);\n }\n for (const name of ['cookie', 'host', 'origin'] as const) {\n const value = outer.req.header(name);\n if (value !== undefined) headers.set(name, value);\n }\n return headers;\n } catch {\n return undefined;\n }\n}\n\nfunction decodeSealedHttpBody(value: string | undefined): Buffer | undefined {\n if (value === undefined) return Buffer.alloc(0);\n if (!/^(?:[A-Za-z\\d+/]{4})*(?:[A-Za-z\\d+/]{2}==|[A-Za-z\\d+/]{3}=)?$/.test(value)) return undefined;\n const body = Buffer.from(value, 'base64');\n return body.toString('base64') === value ? body : undefined;\n}\n\n/**\n * Locates the built SPA next to the package that owns this module.\n *\n * Walking to the manifest rather than counting `..` segments keeps this correct\n * whether the code runs from `src` in development or from the unbundled `dist`\n * tree, whose depth is a build detail.\n */\nfunction packagedDirectory(name: 'web' | 'pwa'): string {\n let dir = path.dirname(fileURLToPath(import.meta.url));\n for (;;) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return path.join(dir, 'dist', name);\n const parent = path.dirname(dir);\n if (parent === dir) throw new Error('doompi-web could not locate its own package root.');\n dir = parent;\n }\n}\n\nfunction packagedAssetsDir(): string {\n return packagedDirectory('web');\n}\n\nfunction packagedPwaDir(): string {\n return packagedDirectory('pwa');\n}\n\n/**\n * The version of the package this process is running from.\n *\n * Published on `/api/health` so a second `doompi-web` start can tell an\n * upgrade apart from a duplicate launch. An upgraded CLI that silently hands\n * back to an older running hub serves the older cockpit forever, because a\n * long-lived process signs its asset directory once and never revisits it.\n */\nexport function packagedVersion(): string {\n let dir = path.dirname(fileURLToPath(import.meta.url));\n for (;;) {\n const manifest = path.join(dir, 'package.json');\n if (fs.existsSync(manifest)) {\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(manifest, 'utf8'));\n const version = isRecord(parsed) ? parsed.version : undefined;\n return typeof version === 'string' ? version : 'unknown';\n } catch {\n return 'unknown';\n }\n }\n const parent = path.dirname(dir);\n if (parent === dir) return 'unknown';\n dir = parent;\n }\n}\n\n/** The assets to serve, with explicit overrides ahead of this hub's registration. */\nfunction resolveAssetsDir(\n explicit: string | undefined,\n composition: { webDirectory: string | null } | undefined,\n notice: (message: string) => void,\n): string {\n if (explicit !== undefined) return explicit;\n const fromEnv = process.env[WEB_DIST_ENV];\n if (fromEnv !== undefined && fromEnv !== '') return fromEnv;\n const synced = composition?.webDirectory;\n if (synced !== undefined && synced !== null && fs.existsSync(path.join(synced, INDEX_FILE))) {\n notice(`serving the synced cockpit bundle from ${synced}`);\n return synced;\n }\n return packagedAssetsDir();\n}\n\nfunction readAsset(filePath: string): Buffer | undefined {\n try {\n const stat = fs.statSync(filePath);\n if (!stat.isFile()) return undefined;\n return fs.readFileSync(filePath);\n } catch {\n return undefined;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A compact rail name while Pi starts and reports the thread's persisted name. */\nfunction resumedSessionName(info: { name?: string; firstMessage: string }, cwd: string): string {\n const firstLine = info.firstMessage.split('\\n', 1)[0]?.trim();\n return (info.name?.trim() || firstLine || path.basename(cwd) || 'untitled').slice(0, 80);\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** One page's hold on one thread; a session id never contains a newline, so the pair cannot collide. */\nfunction threadKey(sessionId: string, threadId: string): string {\n return `${sessionId}\\n${threadId}`;\n}\n\nfunction buildHub(\n options: WebServerOptions,\n notice: (message: string) => void,\n channels: readonly WebHubChannel[],\n telemetry: DoomTelemetry,\n): SessionHub {\n return createSessionHub({\n source: watchRegistry(options.registryDir, notice),\n spawner: createServerSpawner({\n registryDir: options.registryDir,\n command: options.spawnCommand,\n onNotice: notice,\n }),\n readGit: readGitStatus,\n channels,\n telemetry,\n onNotice: notice,\n });\n}\n\n/** Stable browser identity for an admitted canonical repository root. */\nfunction repositoryId(root: string): string {\n return `repo-${createHash('sha256').update(root).digest('base64url').slice(0, 24)}`;\n}\n\n/** Active repositories plus a bounded set seen earlier during this hub process. */\nexport function createSettingsRepositoryRegistry(hub: SessionHub): {\n list(): SettingsRepository[];\n resolve(repositoryId: string): string | undefined;\n} {\n const recent = new Map<string, { repository: SettingsRepository; seenAt: number }>();\n const refresh = (): SettingsRepository[] => {\n for (const [root, entry] of recent) {\n recent.set(root, { ...entry, repository: { ...entry.repository, active: false } });\n }\n for (const record of hub.records()) {\n try {\n const root = fs.realpathSync(findRepositoryRoot(record.cwd));\n recent.set(root, {\n repository: { id: repositoryId(root), path: root, name: path.basename(root) || root, active: true },\n seenAt: Date.now(),\n });\n } catch {\n // A session outside a marked repository is valid, but has no repository settings surface.\n }\n }\n const inactive = [...recent.entries()]\n .filter(([, entry]) => !entry.repository.active)\n .sort((left, right) => right[1].seenAt - left[1].seenAt);\n for (const [root] of inactive.slice(12)) recent.delete(root);\n return [...recent.values()]\n .sort(\n (left, right) =>\n Number(right.repository.active) - Number(left.repository.active) ||\n right.seenAt - left.seenAt ||\n left.repository.path.localeCompare(right.repository.path),\n )\n .map((entry) => entry.repository);\n };\n return {\n list: refresh,\n resolve(repositoryId) {\n return refresh().find((entry) => entry.id === repositoryId)?.path;\n },\n };\n}\n\n/**\n * Mounts the hub-scoped package APIs under /api/plugin/<basePath>/.\n *\n * The prefix is stripped before the API sees the request, so a package\n * declares its routes relative to its own mount and never repeats where a host\n * put it. An API that throws answers 500 for its own routes alone; one bad\n * package never takes the cockpit down with it.\n */\nfunction mountHubApis(\n app: Hono,\n apis: readonly DoomApi[],\n notice: (message: string) => void,\n resolveRepository: (repositoryId: string) => string | undefined,\n readRepositorySync: (repositoryId: string) => DoomRepositorySyncView | undefined,\n): DoomApiHandler[] {\n const handlers: DoomApiHandler[] = [];\n for (const api of apis) {\n const mount = `${DOOM_API_ROUTE_PREFIX}/${api.basePath}`;\n let handler: DoomApiHandler;\n try {\n handler = api.start({ scope: 'hub', onNotice: notice, resolveRepository, readRepositorySync });\n } catch (error) {\n notice(`hub API '${api.basePath}' did not start (${describeError(error)}); its routes stay unmounted`);\n continue;\n }\n handlers.push(handler);\n app.all(`${mount}/*`, async (context) => {\n if (context.req.query(API_SESSION_QUERY_PARAM) !== undefined) return context.notFound();\n const url = new URL(context.req.url);\n url.pathname = url.pathname.slice(mount.length) || '/';\n try {\n return await handler.fetch(new Request(url, context.req.raw));\n } catch (error) {\n notice(`hub API '${api.basePath}' failed on ${url.pathname} (${describeError(error)})`);\n return context.json({ error: `The '${api.basePath}' API failed.` }, 500);\n }\n });\n }\n return handlers;\n}\n\n/**\n * Forwards a package API request carrying ?session= to that session's own\n * server, over the API socket its registry record names.\n *\n * The session servers are where session-scoped data lives, but the browser must\n * not talk to them: their attach tokens stay in this process and the page only\n * ever reaches loopback. So the hub is the one door, and this is the hop behind\n * it. A session that never mounted an API answers 404 with the reason rather\n * than leaving the page waiting.\n */\nfunction mountSessionApiProxy(\n app: Hono,\n hub: SessionHub,\n notice: (message: string) => void,\n telemetry: DoomTelemetry,\n): void {\n app.all(`${DOOM_API_ROUTE_PREFIX}/*`, async (context) => {\n const sessionId = context.req.query(API_SESSION_QUERY_PARAM);\n if (sessionId === undefined) return context.notFound();\n const summary = hub.snapshot().find((candidate) => candidate.id === sessionId);\n if (summary === undefined) return context.json({ error: `No session ${sessionId}.` }, 404);\n if (summary.apiSocketPath === undefined) {\n return context.json({ error: `Session ${sessionId} serves no package API.` }, 404);\n }\n const url = new URL(context.req.url);\n url.searchParams.delete(API_SESSION_QUERY_PARAM);\n const parent = readTraceContext(context.req.raw.headers);\n try {\n return await telemetry.runInSpan(\n 'web.package_proxy',\n { 'operation.name': 'web.package_proxy', 'http.method': context.req.method },\n async (trace) =>\n await proxyToSocket({\n socketPath: summary.apiSocketPath as string,\n path: `${url.pathname}${url.search}`,\n method: context.req.method,\n headers: context.req.raw.headers,\n body: context.req.raw.body,\n trace: forwardedTraceContext(trace, parent),\n }),\n parent,\n );\n } catch (error) {\n notice(`session ${sessionId} API is unreachable (${describeError(error)})`);\n return context.json({ error: `Session ${sessionId} is not answering.` }, 502);\n }\n });\n}\n\n/**\n * Serves the cockpit and multiplexes every session behind it.\n *\n * Attach tokens stay in this process. The browser authenticates by reaching a\n * loopback port, so a page never holds a credential that would let it talk to\n * a session directly. One page WebSocket carries all sessions: hub frames\n * describe the set, and session traffic travels enveloped by session id.\n */\nexport async function serveWeb(options: WebServerOptions): Promise<WebServer> {\n const host = options.host ?? '127.0.0.1';\n const notice = options.onNotice ?? ((): void => {});\n const telemetry = createWebTelemetry();\n const store = createRemoteAccessStore({ stateDir: options.remoteStateDir, onNotice: notice });\n const repositoryRoot = (directory: string): string | undefined => {\n try {\n return fs.realpathSync(findRepositoryRoot(directory));\n } catch {\n return undefined;\n }\n };\n const compositionRoot = (directory: string): string | undefined => {\n const repository = repositoryRoot(directory);\n if (repository !== undefined) return repository;\n const globalRoot = globalDoomConfigDirectory();\n try {\n return readSyncRegistration(globalRoot)?.root;\n } catch (error) {\n notice(`global cockpit composition is unreadable (${describeError(error)})`);\n return undefined;\n }\n };\n const pinnedRoot = options.compositionDir === undefined ? undefined : repositoryRoot(options.compositionDir);\n if (options.compositionDir !== undefined && pinnedRoot === undefined) {\n throw new Error(`Cockpit composition directory is not inside a repository: ${options.compositionDir}`);\n }\n const initialRecords = readRegistryRecords(options.registryDir, notice);\n const liveRoots = initialRecords\n .map((record) => compositionRoot(record.cwd))\n .filter((root): root is string => root !== undefined);\n const compositionRoots = [...new Set(pinnedRoot === undefined ? liveRoots : [pinnedRoot])].sort((left, right) =>\n left.localeCompare(right),\n );\n const syncGuards = new Map<string, SyncGuard>();\n for (const root of compositionRoots) {\n syncGuards.set(root, createSyncGuard({ repoRoot: root, onNotice: notice }));\n }\n await Promise.all([...syncGuards.values()].map(async (guard) => await guard.ensureSynced()));\n const resolveComposition = async () =>\n await resolveWebComposition({\n repositoryRoots: compositionRoots,\n stateDirectory: store.directory,\n onNotice: notice,\n });\n const startupComposition = await resolveComposition();\n let served = {\n assetsDir: resolveAssetsDir(options.assetsDir, startupComposition, notice),\n apiDirectory: startupComposition?.apiDirectory,\n };\n const ensureSessionSynced = async (directory: string): Promise<void> => {\n const root = compositionRoot(directory);\n if (root === undefined) return;\n const existing = syncGuards.get(root);\n if (existing !== undefined) {\n await existing.ensureSynced();\n return;\n }\n const guard = createSyncGuard({ repoRoot: root, onNotice: notice });\n try {\n await guard.ensureSynced();\n } finally {\n guard.close();\n }\n };\n const hub = buildHub(options, notice, await loadHubChannels(served.assetsDir, notice), telemetry);\n // Threads are journals the data channels can name (a subagent run's own\n // session file); the hub tails one only while a page follows it.\n const threads = createThreadJournals({\n resolve: (sessionId, threadId) => hub.threadJournal(sessionId, threadId),\n onNotice: notice,\n });\n /**\n * Every attached page, tagged with the listener it arrived on.\n *\n * The split matters: an approval prompt must reach the host and only the\n * host, because a paired phone that could approve devices would be able to\n * make its own access permanent.\n */\n const pages = new Set<{ post: (frame: object) => void; local: boolean }>();\n const broadcast = (frame: object, localOnly: boolean): void => {\n for (const page of pages) {\n if (localOnly && !page.local) continue;\n page.post(frame);\n }\n };\n\n const app = new Hono();\n const nodeWs = createNodeWebSocket({ app });\n /** Known once the listener binds; until then the guard treats every request as remote. */\n let loopbackPort: number | undefined;\n const pwaDir = packagedPwaDir();\n const publication = createBundlePublication({\n assetsDir: () => served.assetsDir,\n stateDir: store.directory,\n onNotice: notice,\n });\n let livePush: LiveWebPush | undefined;\n reapStaleTunnel(store.directory, notice);\n /**\n * Stands the host's sessions down so the container can take them over.\n *\n * Done here rather than in the launcher because this is where the hub is, and\n * the stop has to happen before the move: a recreated session opens the same\n * working tree, and two agents in one tree would fight over it.\n */\n const handover =\n options.onHandover === undefined\n ? undefined\n : (settings: RemoteAccessSettings): void => {\n const plan = planSessionMigration(\n hub.snapshot().map((session) => ({\n id: session.id,\n cwd: session.cwd,\n ...(session.name === undefined ? {} : { name: session.name }),\n })),\n settings.sandbox.workspaces,\n );\n for (const line of describeStranded(plan.stranded)) notice(line);\n for (const session of plan.migrate) {\n const stopped = hub.stop(session.id);\n if (!stopped.ok) notice(`could not stop ${session.name ?? session.id} before the move: ${stopped.error}`);\n }\n options.onHandover?.({ settings, sessions: plan.migrate });\n };\n\n const remote = createRemoteAccess({\n store,\n launchTunnel:\n options.remoteAccess?.launchTunnel ??\n createTunnelLauncher({\n stateDir: store.directory,\n ...(options.cloudflaredPath === undefined ? {} : { cloudflaredPath: options.cloudflaredPath }),\n onNotice: notice,\n onExit: (message) => {\n notice(`remote access: ${message}`);\n void remote.disable();\n },\n }),\n // The tunnel gets its own loopback socket. That is the only reliable way to\n // tell its traffic from the host's, since cloudflared connects from\n // 127.0.0.1 and forges nothing a header could reveal.\n bindListener: async () =>\n await new Promise((resolve, reject) => {\n const tunnelServer = serve(\n {\n fetch: app.fetch,\n port: 0,\n hostname: '127.0.0.1',\n serverOptions: {\n maxHeaderSize: TUNNEL_HEADER_BYTES,\n headersTimeout: TUNNEL_HEADERS_TIMEOUT_MS,\n requestTimeout: TUNNEL_REQUEST_TIMEOUT_MS,\n },\n },\n (info) => {\n nodeWs.injectWebSocket(tunnelServer);\n resolve({\n port: info.port,\n close: async () =>\n await new Promise<void>((done) => {\n (tunnelServer as { closeAllConnections?: () => void }).closeAllConnections?.();\n tunnelServer.close(() => done());\n }),\n });\n },\n );\n const bounded = tunnelServer as typeof tunnelServer & { maxConnections: number; maxRequestsPerSocket: number };\n bounded.maxConnections = TUNNEL_CONNECTION_LIMIT;\n bounded.maxRequestsPerSocket = TUNNEL_REQUESTS_PER_SOCKET;\n tunnelServer.once('error', reject);\n }),\n bundleTrust: () => publication.trust(),\n onDeviceDropped: (deviceId) => livePush?.remove(deviceId),\n onNotice: notice,\n ...(handover === undefined ? {} : { requestHandover: handover }),\n contained: insideSandbox(process.env),\n broadcastLocal: (frame) => broadcast(frame, true),\n broadcastAll: (frame) => broadcast(frame, false),\n ...(options.remoteAccess?.now === undefined ? {} : { now: options.remoteAccess.now }),\n });\n livePush = createLiveWebPush({\n stateDir: store.directory,\n isConnected: (deviceId) => remote.isDeviceConnected(deviceId),\n onNotice: notice,\n });\n const disconnectLivePush = hub.onEvent((event) => {\n if (event.kind === 'frame' && parseDoomNotificationEntry(event.frame) !== undefined) void livePush?.notify();\n });\n // First route on the app, because Hono composes matching handlers in\n // registration order: a guard added after a terminating handler never runs\n // for that path. It also refuses socket upgrades, which is what closes the\n // cross-site WebSocket hijack that loopback binding never covered.\n const guard = createRemoteGuard({\n loopbackPort: () => loopbackPort,\n tunnelPolicy: () => remote.tunnelPolicy(),\n authorize: (context) => remote.authorize(getCookie(context, DEVICE_COOKIE, 'host')),\n trustedDevice: (context) => (context.env as SealedRequestBindings | undefined)?.sealedDeviceId,\n channelReady: (deviceId, scope) => remote.channelFor(deviceId, scope) !== undefined,\n stepUp: {\n required: (action) => remote.stepUpRequired(action),\n verify: async (context, action, assertion) => {\n if (typeof assertion !== 'object' || assertion === null) return false;\n const ceremonyId = (assertion as { ceremonyId?: unknown }).ceremonyId;\n const response = (assertion as { response?: unknown }).response;\n if (typeof ceremonyId !== 'string') return false;\n const trusted = (context.env as SealedRequestBindings | undefined)?.sealedDeviceId;\n const deviceId = trusted ?? remote.authorize(getCookie(context, DEVICE_COOKIE, 'host'));\n const caller = deviceId === undefined ? 'local' : `device:${deviceId}`;\n return await remote.passkeys().finishStepUp(ceremonyId, caller, action, response);\n },\n },\n extraOrigins: allowedOriginsFromEnv(process.env[ALLOW_ORIGIN_ENV]),\n });\n app.use('*', guard.middleware);\n app.use('*', async (context, next) => {\n const pathname = new URL(context.req.url).pathname;\n if (pathname === REMOTE_HTTP_ROUTE) return next();\n const operation = requestOperation(pathname);\n if (operation === undefined) return next();\n const started = performance.now();\n return await telemetry.runInSpan(\n operation,\n { 'operation.name': operation, 'http.method': context.req.method },\n async () => {\n await next();\n await telemetry.recordEvent('web.api.request', {\n 'operation.name': operation,\n 'http.method': context.req.method,\n 'http.status_code': context.res.status,\n duration_ms: Math.max(0, Math.round(performance.now() - started)),\n });\n },\n readTraceContext(context.req.raw.headers),\n );\n });\n // Reject a declared oversized body before any public route reads it. Chunked\n // bodies are bounded by the route-specific or downstream streaming limiter.\n app.use('*', async (context, next) => {\n if (guard.listenerOf(context) === 'local') return next();\n const declared = Number(context.req.header('content-length'));\n if (Number.isFinite(declared) && declared > TUNNEL_BODY_BYTES) {\n return context.json({ error: 'The tunnel request body is too large.' }, 413);\n }\n return next();\n });\n registerRemoteRoutes(app, { remote, listenerOf: (context) => guard.listenerOf(context) });\n const tunnelBodyLimit = bodyLimit({\n maxSize: TUNNEL_BODY_BYTES,\n onError: (context) => context.json({ error: 'The tunnel request body is too large.' }, 413),\n });\n app.use('*', async (context, next) => {\n if (guard.listenerOf(context) === 'local') return next();\n return await tunnelBodyLimit(context, next);\n });\n const browserTelemetryBodyLimit = bodyLimit({\n maxSize: 8 * 1024,\n onError: (context) => context.json({ error: 'The telemetry batch is too large.' }, 413),\n });\n const acceptBrowserTelemetryRequest = createBrowserTelemetryRateLimit();\n app.post(WEB_TELEMETRY_ROUTE, browserTelemetryBodyLimit, async (context) => {\n if (!acceptBrowserTelemetryRequest()) return context.json({ error: 'Too many telemetry batches.' }, 429);\n let body: unknown;\n try {\n body = await context.req.json();\n } catch {\n return context.json({ error: 'The telemetry batch must be JSON.' }, 400);\n }\n const events = parseBrowserPerformanceBatch(body);\n if (events === undefined) return context.json({ error: 'The telemetry batch is invalid.' }, 400);\n for (const event of events) {\n await telemetry.recordEvent(event.name, {\n ...(event.duration_ms === undefined ? {} : { duration_ms: event.duration_ms }),\n ...(event.count === undefined ? {} : { count: event.count }),\n });\n }\n return context.body(null, 204);\n });\n app.post(REMOTE_HTTP_ROUTE, async (context) => {\n const deviceId = remote.authorize(getCookie(context, DEVICE_COOKIE, 'host'));\n if (deviceId === undefined) return context.json({ error: 'This device is not paired.' }, 401);\n const channel = remote.channelFor(deviceId, 'http');\n if (channel === undefined) return context.json({ error: 'This device has no sealed HTTP channel.' }, 401);\n\n let envelope: unknown;\n try {\n envelope = await context.req.json();\n } catch {\n return context.json({ error: 'The sealed request envelope was malformed.' }, 400);\n }\n const opened = channel.open(envelope);\n if (!opened.ok) return context.json({ error: `The sealed request was refused: ${opened.failure}.` }, 400);\n\n let decoded: unknown;\n try {\n decoded = JSON.parse(new TextDecoder().decode(opened.plaintext));\n } catch {\n return context.json({ error: 'The sealed HTTP request was malformed.' }, 400);\n }\n const request = parseSealedHttpRequest(decoded);\n const headers = request === undefined ? undefined : sealedRequestHeaders(request, context);\n const body = request === undefined ? undefined : decodeSealedHttpBody(request.body);\n if (request === undefined || headers === undefined || body === undefined) {\n return context.json({ error: 'The sealed HTTP request was invalid.' }, 400);\n }\n\n const target = new URL(request.target, new URL(context.req.url).origin);\n const innerResponse = await app.request(\n target,\n {\n method: request.method,\n headers,\n ...(request.method === 'GET' || request.method === 'HEAD' ? {} : { body: Uint8Array.from(body).buffer }),\n },\n { ...(context.env as SealedRequestBindings), sealedDeviceId: deviceId },\n );\n const responseBody = Buffer.from(await innerResponse.arrayBuffer());\n const sealed = channel.seal(\n new TextEncoder().encode(\n JSON.stringify({\n v: SEALED_HTTP_VERSION,\n status: innerResponse.status,\n headers: Array.from(innerResponse.headers.entries()),\n body: responseBody.toString('base64'),\n }),\n ),\n );\n if (!sealed.ok) return context.json({ error: `The sealed response failed: ${sealed.failure}.` }, 503);\n return context.json(sealed.envelope);\n });\n const pushBodyLimit = bodyLimit({\n maxSize: 8 * 1024,\n onError: (context) => context.json({ error: 'The Push subscription is too large.' }, 413),\n });\n const pushDevice = (context: Context): string | undefined =>\n (context.env as SealedRequestBindings | undefined)?.sealedDeviceId;\n app.get(REMOTE_PUSH_KEY_ROUTE, (context) => {\n if (pushDevice(context) === undefined) return context.json({ error: 'A sealed paired device is required.' }, 401);\n return context.json({ publicKey: livePush?.publicKey() }, 200, { 'Cache-Control': 'no-store' });\n });\n app.post(REMOTE_PUSH_ROUTE, pushBodyLimit, async (context) => {\n const deviceId = pushDevice(context);\n if (deviceId === undefined) return context.json({ error: 'A sealed paired device is required.' }, 401);\n let subscription: unknown;\n try {\n subscription = await context.req.json();\n } catch {\n return context.json({ error: 'The Push subscription must be JSON.' }, 400);\n }\n if (livePush?.subscribe(deviceId, subscription) !== true) {\n return context.json({ error: 'The Push subscription is invalid.' }, 400);\n }\n return context.body(null, 204);\n });\n app.delete(REMOTE_PUSH_ROUTE, (context) => {\n const deviceId = pushDevice(context);\n if (deviceId === undefined) return context.json({ error: 'A sealed paired device is required.' }, 401);\n livePush?.remove(deviceId);\n return context.body(null, 204);\n });\n // Every running session already serves Pi's protocol; the hub composes them\n // into one server so a browser sees a single endpoint with many sessions.\n // Remote clients get a distinct protocol service that can attach to existing\n // sessions but cannot bypass HTTP step-up by creating one directly.\n const localProtocolListener = createPiWebSocketListener({ onError: (error) => notice(`protocol: ${error.message}`) });\n const remoteProtocolListener = createPiWebSocketListener({\n onError: (error) => notice(`protocol: ${error.message}`),\n });\n const protocolService = createPiHubService({\n records: () => hub.records(),\n spawn: (input) => hub.create(input),\n onNotice: notice,\n });\n const remoteProtocolService = {\n ...protocolService,\n createSession: async () => {\n throw new PiServerError('not_implemented', 'Remote protocol clients cannot create sessions');\n },\n };\n const localProtocolServer = new PiServer(protocolService, {\n listeners: [localProtocolListener],\n onError: (error) => notice(`protocol: ${error.message}`),\n });\n const remoteProtocolServer = new PiServer(remoteProtocolService, {\n listeners: [remoteProtocolListener],\n onError: (error) => notice(`protocol: ${error.message}`),\n });\n await Promise.all([localProtocolServer.start(), remoteProtocolServer.start()]);\n // Provider credentials belong to the machine, not to a session: the hub\n // keeps one Pi runtime over the shared auth.json and signs in for all.\n const providerAuth = createProviderAuth({ runtime: options.authRuntime, onNotice: notice });\n registerAuthRoutes(app, providerAuth);\n // Settings read and write the machine's Doom config. Session working\n // directories are normalized to their nearest repository marker before the\n // picker or a package API can address them. The bounded registry keeps recent\n // roots available after their last session closes without trusting browser paths.\n const repositoryRegistry = createSettingsRepositoryRegistry(hub);\n const repositories = (): SettingsRepository[] => repositoryRegistry.list();\n const resolveRepository = (repositoryId: string): string | undefined => repositoryRegistry.resolve(repositoryId);\n const readRepositorySync = (repositoryId: string): DoomRepositorySyncView | undefined => {\n const root = resolveRepository(repositoryId);\n if (root === undefined) return undefined;\n const drift = readSyncDrift({ repoRoot: root });\n let state: SyncState | undefined;\n try {\n state = readSyncRegistration(root) ? readSyncState(root) : undefined;\n } catch {\n state = undefined;\n }\n return {\n fresh: drift.fresh,\n reasons: [...drift.reasons],\n ...(state === undefined ? {} : { mcpProjection: state.fileState.mcpProjection }),\n };\n };\n registerSettingsRoutes(app, {\n repositories,\n models: () => providerAuth.listModels(),\n });\n // Package APIs, mounted before the SPA fallback so their routes are reachable\n // and everything else still falls through to the bundle. Hub-scoped ones run\n // here; session-scoped ones live in each session's own server and are proxied.\n const apiDirectory = served.apiDirectory;\n const hubApis =\n apiDirectory === undefined && !process.env[PACKAGE_API_DIR_ENV]\n ? []\n : await loadPackageApis('hub', { apiDirectory, onNotice: notice });\n const pluginApis = mountHubApis(app, hubApis, notice, resolveRepository, readRepositorySync);\n mountSessionApiProxy(app, hub, notice, telemetry);\n\n app.get('/api/health', (context) =>\n context.json({\n ok: true,\n role: HUB_ROLE,\n protocol: HUB_PROTOCOL_VERSION,\n sessions: hub.snapshot().length,\n pid: process.pid,\n version: packagedVersion(),\n }),\n );\n\n app.post(SESSIONS_API_ROUTE, async (context) => {\n let body: unknown;\n try {\n body = await context.req.json();\n } catch {\n return context.json({ error: 'The request body must be JSON.' }, 400);\n }\n if (!isRecord(body) || typeof body.cwd !== 'string' || body.cwd === '') {\n return context.json({ error: 'A cwd string is required.' }, 400);\n }\n const name = typeof body.name === 'string' && body.name !== '' ? body.name : undefined;\n await ensureSessionSynced(body.cwd);\n const outcome = await hub.create({ cwd: body.cwd, name, trace: readTraceContext(context.req.raw.headers) });\n if (outcome.ok) return context.json({ sessionId: outcome.sessionId }, 201);\n return context.json({ error: outcome.error }, outcome.code === 'invalid_request' ? 400 : 502);\n });\n\n // A running server reads the composition once: its extensions when the agent\n // starts, its package API routes when the process does. So a rebuild reaches\n // an existing session only by replacing the process, and the sync has to\n // finish before the replacement starts or it reads the same stale artifacts.\n app.post(`${SESSIONS_API_ROUTE}/:sessionId/restart`, async (context) => {\n const sessionId = context.req.param('sessionId');\n const summary = hub.snapshot().find((candidate) => candidate.id === sessionId);\n if (summary !== undefined) await ensureSessionSynced(summary.cwd);\n const outcome = await hub.restart(sessionId, readTraceContext(context.req.raw.headers));\n if (outcome.ok) return context.json({ sessionId: outcome.sessionId }, 202);\n return context.json({ error: outcome.error }, outcome.code === 'invalid_request' ? 404 : 502);\n });\n\n app.get(`${SESSIONS_API_ROUTE}/:sessionId/history`, async (context) => {\n const sessionId = context.req.param('sessionId');\n const summary = hub.snapshot().find((candidate) => candidate.id === sessionId);\n if (!summary) return context.json({ error: 'Unknown session.' }, 404);\n const sessions = await SessionManager.list(summary.cwd);\n return context.json({\n sessions: sessions.map((session) => ({\n id: session.id,\n ...(session.name === undefined ? {} : { name: session.name }),\n firstMessage: session.firstMessage,\n createdAt: session.created.toISOString(),\n updatedAt: session.modified.toISOString(),\n messageCount: session.messageCount,\n })),\n });\n });\n\n app.post(`${SESSIONS_API_ROUTE}/:sessionId/resume`, async (context) => {\n const sessionId = context.req.param('sessionId');\n const summary = hub.snapshot().find((candidate) => candidate.id === sessionId);\n if (!summary) return context.json({ error: 'Unknown session.' }, 404);\n let body: unknown;\n try {\n body = await context.req.json();\n } catch {\n return context.json({ error: 'The request body must be JSON.' }, 400);\n }\n if (!isRecord(body) || typeof body.targetSessionId !== 'string' || body.targetSessionId === '') {\n return context.json({ error: 'A targetSessionId string is required.' }, 400);\n }\n const sessions = await SessionManager.list(summary.cwd);\n const target = sessions.find((session) => session.id === body.targetSessionId);\n if (!target) return context.json({ error: 'That Pi thread does not belong to this workspace.' }, 404);\n await ensureSessionSynced(summary.cwd);\n const outcome = await hub.resume(\n sessionId,\n { sessionId: target.id, name: resumedSessionName(target, summary.cwd) },\n readTraceContext(context.req.raw.headers),\n );\n if (outcome.ok) return context.json({ sessionId: outcome.sessionId }, 202);\n return context.json({ error: outcome.error }, outcome.code === 'invalid_request' ? 409 : 502);\n });\n app.delete(`${SESSIONS_API_ROUTE}/:sessionId`, (context) => {\n const sessionId = context.req.param('sessionId');\n const outcome = hub.stop(sessionId);\n if (outcome.ok) return context.json({ sessionId }, 202);\n return context.json(\n { error: outcome.error },\n outcome.code === 'unknown' ? 404 : outcome.code === 'self' ? 409 : 502,\n );\n });\n\n // File completion for the composer's @ references: bounded, cwd-scoped,\n // and only for sessions the hub actually manages.\n app.get('/api/sessions/:sessionId/files', async (context) => {\n const sessionId = context.req.param('sessionId');\n const summary = hub.snapshot().find((candidate) => candidate.id === sessionId);\n if (!summary) return context.json({ error: 'Unknown session.' }, 404);\n const query = context.req.query('q') ?? '';\n const files = await listSessionFiles(summary.cwd, query, 20);\n return context.json({ files });\n });\n\n /**\n * The subtree directory suggestions may name, or undefined for all of them.\n *\n * A paired device can ask this route, and answering from the whole home\n * directory hands it a map of the machine: every project, every client name,\n * every checkout. So a request arriving on the tunnel is pinned to the\n * directory the cockpit was started from, which is the one the person running\n * it already chose.\n *\n * Only that request. The person at this keyboard already has a shell, so\n * pinning them would cost the picker its usefulness and buy nothing. A\n * contained cockpit needs none of it either, because its mounts are the\n * boundary and nothing outside them is visible to it at all.\n */\n const browseRoot = (context: Context): string | undefined => {\n if (guard.listenerOf(context) === 'local' || insideSandbox(process.env)) return undefined;\n return options.browseRoot ?? process.cwd();\n };\n\n // Directory suggestions for the new-session picker: the children of the\n // typed parent while a path is being drilled into, and a ranked search of\n // the home directory for anything else, so a name or a path remembered from\n // another machine still finds the folder.\n app.get(DIRECTORIES_API_ROUTE, async (context) => {\n const root = browseRoot(context);\n const directories = await suggestDirectories(context.req.query('q') ?? '', {\n limit: DIRECTORY_SUGGESTION_LIMIT,\n ...(root === undefined ? {} : { root }),\n });\n return context.json({ directories });\n });\n\n // The timeline's @file previews: one cwd-contained file, capped in size.\n app.get(SESSION_FILE_ROUTE, async (context) => {\n const sessionId = context.req.param('sessionId');\n const summary = hub.snapshot().find((candidate) => candidate.id === sessionId);\n if (!summary) return context.json({ error: 'Unknown session.' }, 404);\n const relativePath = context.req.query('path') ?? '';\n const file = await readSessionFile(summary.cwd, relativePath, MAX_SESSION_FILE_BYTES);\n if (file.status === 'forbidden') return context.json({ error: 'The path leaves the session directory.' }, 403);\n if (file.status === 'not-found') return context.json({ error: 'No such file.' }, 404);\n if (file.status === 'too-large') return context.json({ error: 'The file is too large to preview.' }, 413);\n return context.body(new Uint8Array(file.body), 200, sessionFileHeaders(relativePath));\n });\n\n app.get(\n SESSION_SOCKET_ROUTE,\n nodeWs.upgradeWebSocket((context) => {\n const local = guard.listenerOf(context) === 'local';\n // Resolved once at upgrade: the device is fixed for the socket's life, and\n // looking it up per frame would bump last-seen on every keystroke.\n const deviceId = local ? undefined : remote.authorize(getCookie(context, DEVICE_COOKIE, 'host'));\n const channel = deviceId === undefined ? undefined : remote.channelFor(deviceId, 'session');\n const subscriptions = new Set<string>();\n const connectionId = randomUUID();\n /** The threads this page follows; one socket may follow several of one session. */\n const threadSubscriptions = new Map<string, { sessionId: string; threadId: string }>();\n let disconnect: (() => void) | undefined;\n let disconnectThreads: (() => void) | undefined;\n /** Held on the socket, not inside onOpen, so close can withdraw it. */\n let registered: { post: (frame: object) => void; local: boolean } | undefined;\n /** Withdraws this socket from the remote registry, so switch-off can close it. */\n let untrack: (() => void) | undefined;\n /** Lets go of every followed thread, or only a departed session's. */\n const releaseThreads = (sessionId?: string): void => {\n for (const [key, held] of threadSubscriptions) {\n if (sessionId !== undefined && held.sessionId !== sessionId) continue;\n threadSubscriptions.delete(key);\n threads.unsubscribe(held.sessionId, held.threadId);\n }\n };\n return {\n onOpen(_event, ws) {\n if (!local && channel === undefined) {\n ws.close(1008, 'sealed session channel required');\n return;\n }\n const post = (frame: SessionFrame | object): void => {\n const text = JSON.stringify(frame);\n let outgoing = text;\n if (!local) {\n if (channel === undefined) return;\n const sealed = channel.seal(new TextEncoder().encode(text));\n if (!sealed.ok) {\n ws.close(1008, 'sealed session channel failed');\n return;\n }\n outgoing = JSON.stringify(sealed.envelope);\n }\n try {\n ws.send(outgoing);\n } catch {\n // The browser went away mid-write; onClose tears the socket down.\n }\n };\n registered = { post, local };\n pages.add(registered);\n // A socket that has upgraded has left the HTTP server's connection\n // tracking, so closing the tunnel listener does not reach it.\n // Without this a paired phone keeps driving the agent after remote\n // access is switched off.\n if (deviceId !== undefined) untrack = remote.trackSocket(deviceId, (code, reason) => ws.close(code, reason));\n post(hubHello(hub.channelTypes()));\n post({ type: SESSIONS_SNAPSHOT_TYPE, sessions: hub.snapshot() });\n disconnect = hub.onEvent((event) => {\n if (event.kind === 'upsert') post({ type: SESSION_UPSERT_TYPE, session: event.session });\n else if (event.kind === 'removed') {\n subscriptions.delete(event.sessionId);\n releaseThreads(event.sessionId);\n post({ type: SESSION_REMOVED_TYPE, sessionId: event.sessionId });\n } else if (event.kind === 'channel') {\n if (\n event.connectionId === connectionId ||\n (event.connectionId === undefined && subscriptions.has(event.sessionId))\n ) {\n post({ type: event.frameType, sessionId: event.sessionId, payload: event.payload });\n }\n } else {\n const notification = parseDoomNotificationEntry(event.frame);\n if (notification !== undefined || subscriptions.has(event.sessionId)) {\n post(sessionFrameEnvelope(event.sessionId, event.frame));\n }\n }\n });\n disconnectThreads = threads.onFrame((event) => {\n if (threadSubscriptions.has(threadKey(event.sessionId, event.threadId))) {\n post(threadFrameEnvelope(event.sessionId, event.threadId, event.frame));\n }\n });\n notice('browser attached');\n },\n onMessage(event) {\n if (typeof event.data !== 'string') return;\n let parsed: unknown;\n try {\n parsed = JSON.parse(event.data);\n } catch {\n return;\n }\n if (!local) {\n if (channel === undefined) return;\n const opened = channel.open(parsed);\n // A frame that will not open was altered or replayed. Dropping it\n // is the only safe answer; there is no plaintext to fall back to.\n if (!opened.ok) return;\n try {\n parsed = JSON.parse(new TextDecoder().decode(opened.plaintext));\n } catch {\n return;\n }\n }\n if (!isRecord(parsed) || typeof parsed.sessionId !== 'string') return;\n const sessionId = parsed.sessionId;\n if (\n typeof parsed.type === 'string' &&\n hub.channelTypes().includes(parsed.type) &&\n (subscriptions.has(sessionId) || hub.channelReceivesWithoutSubscription(parsed.type))\n ) {\n hub.receiveChannel(sessionId, parsed.type, parsed.payload, connectionId);\n return;\n }\n if (parsed.type === SUBSCRIBE_TYPE) {\n const backlog = hub.backlog(sessionId);\n if (!backlog) return; // Unknown session; the snapshot said otherwise.\n subscriptions.add(sessionId);\n registered?.post(backlog);\n for (const frame of hub.channelFrames(sessionId)) registered?.post(frame);\n return;\n }\n if (parsed.type === UNSUBSCRIBE_TYPE) {\n subscriptions.delete(sessionId);\n return;\n }\n if (parsed.type === HISTORY_REQUEST_TYPE) {\n // Older transcript, on demand. The hub kept what the attach path\n // was too small to publish, so scrolling back reads from memory\n // rather than asking the session to re-read its journal.\n const page = hub.history(sessionId, {\n ...(typeof parsed.before === 'string' ? { before: parsed.before } : {}),\n ...(typeof parsed.limit === 'number' ? { limit: parsed.limit } : {}),\n });\n if (!page) return;\n registered?.post(page);\n return;\n }\n if (parsed.type === SUBSCRIBE_THREAD_TYPE || parsed.type === UNSUBSCRIBE_THREAD_TYPE) {\n if (typeof parsed.threadId !== 'string') return;\n const threadId = parsed.threadId;\n const key = threadKey(sessionId, threadId);\n if (parsed.type === UNSUBSCRIBE_THREAD_TYPE) {\n if (threadSubscriptions.delete(key)) threads.unsubscribe(sessionId, threadId);\n return;\n }\n if (threadSubscriptions.has(key) || hub.backlog(sessionId) === undefined) return;\n threadSubscriptions.set(key, { sessionId, threadId });\n registered?.post(threadBacklog(sessionId, threadId, threads.subscribe(sessionId, threadId)));\n return;\n }\n if (parsed.type === SESSION_COMMAND_TYPE && isRecord(parsed.frame)) {\n // The hub owns the handshake; a page must not be able to replay it.\n if (parsed.frame.type === ATTACH_TYPE) return;\n hub.command(sessionId, parsed.frame);\n }\n },\n onClose() {\n hub.disconnectChannels(connectionId);\n if (registered) pages.delete(registered);\n registered = undefined;\n untrack?.();\n untrack = undefined;\n disconnect?.();\n disconnect = undefined;\n disconnectThreads?.();\n disconnectThreads = undefined;\n subscriptions.clear();\n releaseThreads();\n notice('browser detached');\n },\n };\n }),\n );\n\n app.get(\n PROTOCOL_SOCKET_ROUTE,\n nodeWs.upgradeWebSocket((context) => {\n const local = guard.listenerOf(context) === 'local';\n const deviceId = local ? undefined : remote.authorize(getCookie(context, DEVICE_COOKIE, 'host'));\n const channel = deviceId === undefined ? undefined : remote.channelFor(deviceId, 'protocol');\n let handler: ReturnType<typeof localProtocolListener.accept>;\n let untrack: (() => void) | undefined;\n return {\n onOpen(_event, ws) {\n if (!local && channel === undefined) {\n ws.close(1008, 'sealed protocol channel required');\n return;\n }\n if (deviceId !== undefined) untrack = remote.trackSocket(deviceId, (code, reason) => ws.close(code, reason));\n handler = (local ? localProtocolListener : remoteProtocolListener).accept({\n send: (data) => {\n if (local) {\n ws.send(data as ArrayBuffer);\n return;\n }\n if (channel === undefined) return;\n const bytes = data instanceof Uint8Array ? data : new Uint8Array(data as ArrayBuffer);\n const sealed = channel.seal(bytes);\n if (!sealed.ok) {\n ws.close(1008, 'sealed protocol channel failed');\n return;\n }\n ws.send(new TextEncoder().encode(JSON.stringify(sealed.envelope)));\n },\n close: () => ws.close(),\n get readyState() {\n return ws.readyState;\n },\n });\n },\n async onMessage(event) {\n const data = event.data;\n if (typeof data === 'string') return;\n const bytes =\n data instanceof Blob\n ? new Uint8Array(await data.arrayBuffer())\n : data instanceof ArrayBuffer\n ? new Uint8Array(data)\n : new Uint8Array(data);\n if (local) {\n handler?.onData(bytes);\n return;\n }\n if (channel === undefined) return;\n let envelope: unknown;\n try {\n envelope = JSON.parse(new TextDecoder().decode(bytes));\n } catch {\n return;\n }\n const opened = channel.open(envelope);\n if (opened.ok) handler?.onData(opened.plaintext);\n },\n onClose() {\n untrack?.();\n untrack = undefined;\n handler?.onClose();\n handler = undefined;\n },\n onError(event) {\n handler?.onError(event instanceof Error ? event : new Error('The protocol socket failed'));\n handler = undefined;\n },\n };\n }),\n );\n\n app.get('/manifest.webmanifest', (context) => {\n const file = path.join(pwaDir, 'manifest.webmanifest');\n const body = readAsset(file);\n if (body === undefined) return context.text('The PWA manifest is unavailable.', 404);\n return context.body(new Uint8Array(body), 200, {\n 'Cache-Control': 'no-store',\n 'Content-Type': 'application/manifest+json; charset=utf-8',\n });\n });\n app.get('/sw.js', (context) => {\n const file = path.join(pwaDir, 'sw.js');\n const body = readAsset(file);\n if (body === undefined) return context.text('The trusted service worker is unavailable.', 404);\n return context.body(new Uint8Array(body), 200, {\n 'Cache-Control': 'no-cache',\n 'Content-Type': 'text/javascript; charset=utf-8',\n 'Service-Worker-Allowed': '/',\n });\n });\n app.get(`${PWA_ASSET_PREFIX}*`, (context) => {\n const requested = new URL(context.req.url).pathname.slice(PWA_ASSET_PREFIX.length);\n const file = resolveAssetPath(pwaDir, `/${requested}`);\n const body = file === undefined ? undefined : readAsset(file);\n if (file === undefined || body === undefined) return context.text('PWA asset not found.', 404);\n return context.body(new Uint8Array(body), 200, {\n 'Cache-Control': 'no-store',\n 'Content-Type': contentTypeFor(file),\n 'X-Content-Type-Options': 'nosniff',\n });\n });\n app.get(BUNDLE_MANIFEST_ROUTE, (context) => {\n const current = publication.current();\n if (current === undefined) return context.json({ error: 'No signed cockpit bundle is available.' }, 503);\n return context.json(current.signed, 200, { 'Cache-Control': 'no-store' });\n });\n app.get(`${RAW_BUNDLE_PREFIX}*`, (context) => {\n const requested = new URL(context.req.url).pathname;\n const matched = /^\\/bundle-assets\\/([1-9][0-9]*)(\\/.*)$/u.exec(requested);\n const revision = matched === null ? undefined : Number(matched[1]);\n const current = publication.current();\n if (\n matched === null ||\n !Number.isSafeInteger(revision) ||\n current === undefined ||\n current.signed.manifest.revision !== revision\n ) {\n return context.text('That signed bundle revision is unavailable.', 404);\n }\n const asset = assetFor(current.signed.manifest, matched[2] ?? '');\n // Read through the directory this manifest was signed over, not whatever is\n // current now: a generation that changed mid-request would otherwise serve\n // bytes the page is about to reject as a digest mismatch.\n const file = asset === undefined ? undefined : resolveAssetPath(current.assetsDir, asset.path);\n const body = file === undefined ? undefined : readAsset(file);\n if (asset === undefined || file === undefined || body === undefined)\n return context.text('Bundle asset not found.', 404);\n return context.body(new Uint8Array(body), 200, {\n 'Cache-Control': 'no-store',\n 'Content-Length': String(body.byteLength),\n 'Content-Type': asset.contentType,\n 'X-Content-Type-Options': 'nosniff',\n });\n });\n app.get('/', (context) => context.redirect(PAIRING_PAGE_ROUTE));\n app.get('*', (context) => {\n const pathname = new URL(context.req.url).pathname;\n const acceptsHtml = context.req.header('accept')?.includes('text/html') === true;\n if (!pathname.startsWith('/api/') && acceptsHtml) return context.redirect('/');\n return context.text('Not found.', 404);\n });\n\n // A rebuilt bundle only changes on disk, so re-resolve the synchronized\n // registrations and tell every page to stage the replacement.\n const refreshServedComposition = async (): Promise<void> => {\n try {\n const rebuilt = await resolveComposition();\n if (rebuilt !== undefined && rebuilt.webDirectory !== served.assetsDir) {\n served = { assetsDir: rebuilt.webDirectory, apiDirectory: rebuilt.apiDirectory };\n notice(`serving the rebuilt cockpit bundle from ${served.assetsDir}`);\n }\n publication.refresh();\n broadcast({ type: HUB_RESYNCED_TYPE }, false);\n } catch (error) {\n notice(`the rebuilt cockpit composition is unavailable (${describeError(error)}); keeping the served bundle`);\n }\n };\n for (const guard of syncGuards.values()) {\n guard.watch(() => void refreshServedComposition());\n }\n\n return new Promise<WebServer>((resolve, reject) => {\n const server = serve({ fetch: app.fetch, port: options.port, hostname: host }, (info) => {\n // Before this the guard has no loopback port to compare against and\n // treats everything as remote, which is the safe direction to be wrong in.\n loopbackPort = info.port;\n nodeWs.injectWebSocket(server);\n const url = `http://${host}:${info.port}`;\n notice(`cockpit on ${url}`);\n let closePromise: Promise<void> | undefined;\n const close = async (): Promise<void> => {\n // Remote access first, so the tunnel is down and every paired socket\n // is closed before the rest of the hub starts letting go.\n await remote.close();\n await new Promise<void>((done) => {\n threads.close();\n for (const guard of syncGuards.values()) guard.close();\n void localProtocolServer.close();\n void remoteProtocolServer.close();\n disconnectLivePush();\n livePush?.close();\n hub.close();\n providerAuth.close();\n for (const handler of pluginApis) handler.close();\n // An upgraded socket leaves the HTTP server's connection tracking,\n // so only the WebSocket server can let go of it. Without this the\n // close callback waits on a browser that has no reason to leave.\n for (const client of nodeWs.wss.clients) client.terminate();\n nodeWs.wss.close();\n (server as { closeAllConnections?: () => void }).closeAllConnections?.();\n server.close(() => done());\n });\n await shutdownWebTelemetry(telemetry);\n };\n resolve({\n url,\n port: info.port,\n close: () => (closePromise ??= close()),\n });\n });\n server.once('error', (error) => {\n threads.close();\n disconnectLivePush();\n livePush?.close();\n hub.close();\n providerAuth.close();\n void remote.close();\n for (const handler of pluginApis) handler.close();\n void telemetry.shutdown();\n reject(error);\n });\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"httpServer.cjs","names":["REMOTE_HTTP_ROUTE","REMOTE_CHANNEL_ROUTE","path","fileURLToPath","fs","createSessionHub","watchRegistry","createServerSpawner","readGitStatus","createHash","findRepositoryRoot","DOOM_API_ROUTE_PREFIX","API_SESSION_QUERY_PARAM","readTraceContext","proxyToSocket","forwardedTraceContext","createWebTelemetry","createRemoteAccessStore","globalDoomConfigDirectory","readSyncRegistration","readRegistryRecords","createSyncGuard","resolveWebComposition","loadHubChannels","createThreadJournals","Hono","createNodeWebSocket","createBundlePublication","planSessionMigration","describeStranded","createRemoteAccess","createTunnelLauncher","serve","insideSandbox","createLiveWebPush","parseDoomNotificationEntry","createRemoteGuard","getCookie","DEVICE_COOKIE","allowedOriginsFromEnv","requestOperation","bodyLimit","createBrowserTelemetryRateLimit","WEB_TELEMETRY_ROUTE","parseBrowserTelemetryBatch","isBrowserErrorEvent","BROWSER_ERROR_EVENT","createBrowserError","REMOTE_PUSH_KEY_ROUTE","REMOTE_PUSH_ROUTE","createPiWebSocketListener","createPiHubService","PiServerError","PiServer","createProviderAuth","readSyncDrift","readSyncState","PACKAGE_API_DIR_ENV","loadPackageApis","SESSIONS_API_ROUTE","SessionManager","listSessionFiles","DIRECTORIES_API_ROUTE","suggestDirectories","SESSION_FILE_ROUTE","readSessionFile","MAX_SESSION_FILE_BYTES","sessionFileHeaders","SESSION_SOCKET_ROUTE","randomUUID","hubHello","SESSIONS_SNAPSHOT_TYPE","SESSION_UPSERT_TYPE","SESSION_REMOVED_TYPE","sessionFrameEnvelope","threadFrameEnvelope","threadBacklog","PROTOCOL_SOCKET_ROUTE","resolveAssetPath","contentTypeFor","BUNDLE_MANIFEST_ROUTE","assetFor","PAIRING_PAGE_ROUTE","HUB_RESYNCED_TYPE","shutdownWebTelemetry"],"sources":["../../../src/adapters/httpServer.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { serve } from '@hono/node-server';\nimport { createNodeWebSocket } from '@hono/node-ws';\nimport { SessionManager } from '@earendil-works/pi-coding-agent';\nimport { PiServer, PiServerError } from '@earendil-works/pi-server';\nimport { readSyncDrift, readSyncRegistration } from '@agimon-ai/doompi/services';\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config';\nimport { readSyncState, type SyncState } from '@agimon-ai/doompi/services/syncState';\nimport type { DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport { BUNDLE_MANIFEST_ROUTE, assetFor } from '@agimon-ai/doompi-web-security';\nimport { createPiHubService } from './piHubService.ts';\nimport { createSyncGuard, type SyncGuard } from './syncGuard.ts';\nimport { createPiWebSocketListener } from './piWebSocketListener.ts';\nimport { type Context, Hono } from 'hono';\nimport { getCookie } from 'hono/cookie';\nimport { bodyLimit } from 'hono/body-limit';\nimport type { WebHubChannel } from '@agimon-ai/doompi-web-contracts';\nimport {\n DOOM_API_ROUTE_PREFIX,\n type DoomApi,\n type DoomApiHandler,\n type DoomRepositorySyncView,\n} from '@agimon-ai/doompi-extension-contracts/package-api';\nimport { loadPackageApis, PACKAGE_API_DIR_ENV } from '@agimon-ai/doompi-extension-contracts/package-api-loader';\nimport { insideSandbox } from '@agimon-ai/doompi-extension-contracts/sandbox-harness';\nimport { findRepositoryRoot } from '@agimon-ai/doompi/utils/repository';\nimport { sessionFileHeaders } from '../services/fileMedia.ts';\nimport { contentTypeFor, resolveAssetPath } from '../services/staticAssets.ts';\nimport { MAX_SESSION_FILE_BYTES, SESSION_FILE_ROUTE } from '../types/media.ts';\nimport type { SettingsRepository } from '../types/settings.ts';\nimport type { WebServer, WebServerOptions } from '../types/bridge.ts';\nimport {\n API_SESSION_QUERY_PARAM,\n DIRECTORIES_API_ROUTE,\n HISTORY_REQUEST_TYPE,\n HUB_PROTOCOL_VERSION,\n HUB_RESYNCED_TYPE,\n HUB_ROLE,\n SESSIONS_API_ROUTE,\n SESSIONS_SNAPSHOT_TYPE,\n SESSION_COMMAND_TYPE,\n SESSION_REMOVED_TYPE,\n SESSION_UPSERT_TYPE,\n SUBSCRIBE_THREAD_TYPE,\n SUBSCRIBE_TYPE,\n UNSUBSCRIBE_THREAD_TYPE,\n UNSUBSCRIBE_TYPE,\n hubHello,\n sessionFrameEnvelope,\n threadBacklog,\n threadFrameEnvelope,\n} from '../types/hub.ts';\nimport { parseDoomNotificationEntry } from '../types/notification.ts';\nimport { ATTACH_TYPE, type SessionFrame } from '../types/session.ts';\nimport { readGitStatus } from './gitStatus.ts';\nimport { readRegistryRecords, watchRegistry } from './registryWatcher.ts';\nimport { createServerSpawner } from './serverSpawner.ts';\nimport { createSessionHub, type SessionHub } from './sessionHub.ts';\nimport { allowedOriginsFromEnv } from '../services/remoteGuardPolicy.ts';\nimport { describeStranded, planSessionMigration } from '../services/sessionMigration.ts';\nimport { registerAuthRoutes } from './authRoutes.ts';\nimport { registerSettingsRoutes } from './settingsRoutes.ts';\nimport { createProviderAuth } from './providerAuth.ts';\nimport { createRemoteGuard } from './remoteGuard.ts';\nimport { createRemoteAccess } from './remoteAccess.ts';\nimport { createBundlePublication } from './bundlePublication.ts';\nimport { createLiveWebPush, type LiveWebPush } from './webPush.ts';\nimport { createRemoteAccessStore } from './remoteAccessStore.ts';\nimport { registerRemoteRoutes } from './remoteRoutes.ts';\nimport { createTunnelLauncher, reapStaleTunnel } from './tunnelProcess.ts';\nimport {\n DEVICE_COOKIE,\n PAIRING_PAGE_ROUTE,\n PROTOCOL_SOCKET_ROUTE,\n REMOTE_CHANNEL_ROUTE,\n REMOTE_HTTP_ROUTE,\n REMOTE_PUSH_KEY_ROUTE,\n REMOTE_PUSH_ROUTE,\n SESSION_SOCKET_ROUTE,\n type RemoteAccessSettings,\n} from '../types/remoteAccess.ts';\nimport { suggestDirectories } from './directoryListing.ts';\nimport { listSessionFiles, readSessionFile } from './sessionFiles.ts';\nimport { proxyToSocket } from './packageApiProxy.ts';\nimport { createThreadJournals } from './threadJournals.ts';\nimport { loadHubChannels } from './webHubPluginLoader.ts';\nimport {\n BROWSER_ERROR_EVENT,\n createBrowserError,\n createBrowserTelemetryRateLimit,\n createWebTelemetry,\n forwardedTraceContext,\n isBrowserErrorEvent,\n parseBrowserTelemetryBatch,\n readTraceContext,\n requestOperation,\n shutdownWebTelemetry,\n WEB_TELEMETRY_ROUTE,\n} from './webTelemetry.ts';\nimport { resolveWebComposition } from './webComposition.ts';\n\n// Pi's protocol rides its own socket so the DoomPi channel keeps the\n// vocabulary the protocol has no shape for: dialogs, minor modes, selection.\n/** Directory suggestions per picker query; more than this means \"type further\". */\nconst DIRECTORY_SUGGESTION_LIMIT = 12;\nconst INDEX_FILE = 'index.html';\n/** Env override for the assets directory, set by launchers that know a synced bundle. */\nconst WEB_DIST_ENV = 'DOOMPI_WEB_DIST';\n/** Package-root override for bundled launchers whose chunks do not retain the npm layout. */\nconst WEB_PACKAGE_ROOT_ENV = 'DOOMPI_WEB_PACKAGE_ROOT';\n/** Comma-separated origins the operator allows past the guard, for dev setups this package cannot guess. */\nconst ALLOW_ORIGIN_ENV = 'DOOMPI_WEB_ALLOW_ORIGIN';\nconst RAW_BUNDLE_PREFIX = '/bundle-assets/';\nconst PWA_ASSET_PREFIX = '/pwa/';\nconst SEALED_HTTP_VERSION = 1;\n/** Upper bound for any body accepted from the tunnel before route-specific limits. */\nconst TUNNEL_BODY_BYTES = 8 * 1024 * 1024;\nconst TUNNEL_HEADER_BYTES = 16 * 1024;\n/**\n * A full telemetry batch of ten failures, each with a bounded stack, still fits\n * with room to spare. Anything larger is not a cockpit reporting itself.\n */\nconst BROWSER_TELEMETRY_BODY_BYTES = 32 * 1024;\nconst TUNNEL_HEADERS_TIMEOUT_MS = 5000;\nconst TUNNEL_REQUEST_TIMEOUT_MS = 10_000;\nconst TUNNEL_CONNECTION_LIMIT = 64;\nconst TUNNEL_REQUESTS_PER_SOCKET = 100;\nconst SEALED_HTTP_METHODS = new Set(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']);\nconst SEALED_HTTP_FORBIDDEN_HEADERS = new Set([\n 'connection',\n 'content-length',\n 'cookie',\n 'forwarded',\n 'host',\n 'origin',\n 'transfer-encoding',\n 'upgrade',\n 'x-forwarded-for',\n 'x-forwarded-host',\n 'x-forwarded-proto',\n]);\n\ninterface SealedRequestBindings {\n incoming?: { socket?: { localPort?: number } };\n sealedDeviceId?: string;\n}\n\ninterface SealedHttpRequest {\n v: number;\n method: string;\n target: string;\n headers: Array<[string, string]>;\n body?: string;\n}\n\nfunction parseSealedHttpRequest(value: unknown): SealedHttpRequest | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const request = value as Partial<SealedHttpRequest>;\n if (\n request.v !== SEALED_HTTP_VERSION ||\n typeof request.method !== 'string' ||\n !SEALED_HTTP_METHODS.has(request.method) ||\n typeof request.target !== 'string' ||\n !request.target.startsWith('/') ||\n request.target.startsWith('//') ||\n request.target.includes('#') ||\n !Array.isArray(request.headers) ||\n !request.headers.every(\n (header) => Array.isArray(header) && header.length === 2 && header.every((part) => typeof part === 'string'),\n ) ||\n (request.body !== undefined && typeof request.body !== 'string')\n ) {\n return undefined;\n }\n const target = new URL(request.target, 'http://sealed.doompi.invalid');\n if (target.pathname === REMOTE_HTTP_ROUTE || target.pathname === REMOTE_CHANNEL_ROUTE) return undefined;\n return request as SealedHttpRequest;\n}\n\nfunction sealedRequestHeaders(request: SealedHttpRequest, outer: Context): Headers | undefined {\n try {\n const headers = new Headers();\n for (const [name, value] of request.headers) {\n const normalized = name.toLowerCase();\n if (SEALED_HTTP_FORBIDDEN_HEADERS.has(normalized) || normalized.startsWith('sec-')) continue;\n headers.append(name, value);\n }\n for (const name of ['cookie', 'host', 'origin'] as const) {\n const value = outer.req.header(name);\n if (value !== undefined) headers.set(name, value);\n }\n return headers;\n } catch {\n return undefined;\n }\n}\n\nfunction decodeSealedHttpBody(value: string | undefined): Buffer | undefined {\n if (value === undefined) return Buffer.alloc(0);\n if (!/^(?:[A-Za-z\\d+/]{4})*(?:[A-Za-z\\d+/]{2}==|[A-Za-z\\d+/]{3}=)?$/.test(value)) return undefined;\n const body = Buffer.from(value, 'base64');\n return body.toString('base64') === value ? body : undefined;\n}\n\n/**\n * Locates the built SPA next to the package that owns this module.\n *\n * Walking to the manifest rather than counting `..` segments keeps this correct\n * whether the code runs from `src` in development or from the unbundled `dist`\n * tree, whose depth is a build detail.\n */\nfunction packagedDirectory(name: 'web' | 'pwa'): string {\n const configuredRoot = process.env[WEB_PACKAGE_ROOT_ENV];\n if (configuredRoot !== undefined && configuredRoot !== '') return path.join(configuredRoot, 'dist', name);\n let dir = path.dirname(fileURLToPath(import.meta.url));\n for (;;) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return path.join(dir, 'dist', name);\n const parent = path.dirname(dir);\n if (parent === dir) throw new Error('doompi-web could not locate its own package root.');\n dir = parent;\n }\n}\n\nfunction packagedAssetsDir(): string {\n return packagedDirectory('web');\n}\n\nfunction packagedPwaDir(): string {\n return packagedDirectory('pwa');\n}\n\n/**\n * The version of the package this process is running from.\n *\n * Published on `/api/health` so a second `doompi-web` start can tell an\n * upgrade apart from a duplicate launch. An upgraded CLI that silently hands\n * back to an older running hub serves the older cockpit forever, because a\n * long-lived process signs its asset directory once and never revisits it.\n */\nexport function packagedVersion(): string {\n const configuredRoot = process.env[WEB_PACKAGE_ROOT_ENV];\n let dir =\n configuredRoot !== undefined && configuredRoot !== ''\n ? configuredRoot\n : path.dirname(fileURLToPath(import.meta.url));\n for (;;) {\n const manifest = path.join(dir, 'package.json');\n if (fs.existsSync(manifest)) {\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(manifest, 'utf8'));\n const version = isRecord(parsed) ? parsed.version : undefined;\n return typeof version === 'string' ? version : 'unknown';\n } catch {\n return 'unknown';\n }\n }\n const parent = path.dirname(dir);\n if (parent === dir) return 'unknown';\n dir = parent;\n }\n}\n\n/** The assets to serve, with explicit overrides ahead of this hub's registration. */\nfunction resolveAssetsDir(\n explicit: string | undefined,\n composition: { webDirectory: string | null } | undefined,\n notice: (message: string) => void,\n): string {\n if (explicit !== undefined) return explicit;\n const fromEnv = process.env[WEB_DIST_ENV];\n if (fromEnv !== undefined && fromEnv !== '') return fromEnv;\n const synced = composition?.webDirectory;\n if (synced !== undefined && synced !== null && fs.existsSync(path.join(synced, INDEX_FILE))) {\n notice(`serving the synced cockpit bundle from ${synced}`);\n return synced;\n }\n return packagedAssetsDir();\n}\n\nfunction readAsset(filePath: string): Buffer | undefined {\n try {\n const stat = fs.statSync(filePath);\n if (!stat.isFile()) return undefined;\n return fs.readFileSync(filePath);\n } catch {\n return undefined;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A compact rail name while Pi starts and reports the thread's persisted name. */\nfunction resumedSessionName(info: { name?: string; firstMessage: string }, cwd: string): string {\n const firstLine = info.firstMessage.split('\\n', 1)[0]?.trim();\n return (info.name?.trim() || firstLine || path.basename(cwd) || 'untitled').slice(0, 80);\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** One page's hold on one thread; a session id never contains a newline, so the pair cannot collide. */\nfunction threadKey(sessionId: string, threadId: string): string {\n return `${sessionId}\\n${threadId}`;\n}\n\nfunction buildHub(\n options: WebServerOptions,\n notice: (message: string) => void,\n channels: readonly WebHubChannel[],\n telemetry: DoomTelemetry,\n): SessionHub {\n return createSessionHub({\n source: watchRegistry(options.registryDir, notice),\n spawner: createServerSpawner({\n registryDir: options.registryDir,\n command: options.spawnCommand,\n onNotice: notice,\n }),\n readGit: readGitStatus,\n channels,\n telemetry,\n onNotice: notice,\n });\n}\n\n/** Stable browser identity for an admitted canonical repository root. */\nfunction repositoryId(root: string): string {\n return `repo-${createHash('sha256').update(root).digest('base64url').slice(0, 24)}`;\n}\n\n/** Active repositories plus a bounded set seen earlier during this hub process. */\nexport function createSettingsRepositoryRegistry(hub: SessionHub): {\n list(): SettingsRepository[];\n resolve(repositoryId: string): string | undefined;\n} {\n const recent = new Map<string, { repository: SettingsRepository; seenAt: number }>();\n const refresh = (): SettingsRepository[] => {\n for (const [root, entry] of recent) {\n recent.set(root, { ...entry, repository: { ...entry.repository, active: false } });\n }\n for (const record of hub.records()) {\n try {\n const root = fs.realpathSync(findRepositoryRoot(record.cwd));\n recent.set(root, {\n repository: { id: repositoryId(root), path: root, name: path.basename(root) || root, active: true },\n seenAt: Date.now(),\n });\n } catch {\n // A session outside a marked repository is valid, but has no repository settings surface.\n }\n }\n const inactive = [...recent.entries()]\n .filter(([, entry]) => !entry.repository.active)\n .sort((left, right) => right[1].seenAt - left[1].seenAt);\n for (const [root] of inactive.slice(12)) recent.delete(root);\n return [...recent.values()]\n .sort(\n (left, right) =>\n Number(right.repository.active) - Number(left.repository.active) ||\n right.seenAt - left.seenAt ||\n left.repository.path.localeCompare(right.repository.path),\n )\n .map((entry) => entry.repository);\n };\n return {\n list: refresh,\n resolve(repositoryId) {\n return refresh().find((entry) => entry.id === repositoryId)?.path;\n },\n };\n}\n\n/**\n * Mounts the hub-scoped package APIs under /api/plugin/<basePath>/.\n *\n * The prefix is stripped before the API sees the request, so a package\n * declares its routes relative to its own mount and never repeats where a host\n * put it. An API that throws answers 500 for its own routes alone; one bad\n * package never takes the cockpit down with it.\n */\nfunction mountHubApis(\n app: Hono,\n apis: readonly DoomApi[],\n notice: (message: string) => void,\n resolveRepository: (repositoryId: string) => string | undefined,\n readRepositorySync: (repositoryId: string) => DoomRepositorySyncView | undefined,\n): DoomApiHandler[] {\n const handlers: DoomApiHandler[] = [];\n for (const api of apis) {\n const mount = `${DOOM_API_ROUTE_PREFIX}/${api.basePath}`;\n let handler: DoomApiHandler;\n try {\n handler = api.start({ scope: 'hub', onNotice: notice, resolveRepository, readRepositorySync });\n } catch (error) {\n notice(`hub API '${api.basePath}' did not start (${describeError(error)}); its routes stay unmounted`);\n continue;\n }\n handlers.push(handler);\n app.all(`${mount}/*`, async (context) => {\n if (context.req.query(API_SESSION_QUERY_PARAM) !== undefined) return context.notFound();\n const url = new URL(context.req.url);\n url.pathname = url.pathname.slice(mount.length) || '/';\n try {\n return await handler.fetch(new Request(url, context.req.raw));\n } catch (error) {\n notice(`hub API '${api.basePath}' failed on ${url.pathname} (${describeError(error)})`);\n return context.json({ error: `The '${api.basePath}' API failed.` }, 500);\n }\n });\n }\n return handlers;\n}\n\n/**\n * Forwards a package API request carrying ?session= to that session's own\n * server, over the API socket its registry record names.\n *\n * The session servers are where session-scoped data lives, but the browser must\n * not talk to them: their attach tokens stay in this process and the page only\n * ever reaches loopback. So the hub is the one door, and this is the hop behind\n * it. A session that never mounted an API answers 404 with the reason rather\n * than leaving the page waiting.\n */\nfunction mountSessionApiProxy(\n app: Hono,\n hub: SessionHub,\n notice: (message: string) => void,\n telemetry: DoomTelemetry,\n): void {\n app.all(`${DOOM_API_ROUTE_PREFIX}/*`, async (context) => {\n const sessionId = context.req.query(API_SESSION_QUERY_PARAM);\n if (sessionId === undefined) return context.notFound();\n const summary = hub.snapshot().find((candidate) => candidate.id === sessionId);\n if (summary === undefined) return context.json({ error: `No session ${sessionId}.` }, 404);\n if (summary.apiSocketPath === undefined) {\n return context.json({ error: `Session ${sessionId} serves no package API.` }, 404);\n }\n const url = new URL(context.req.url);\n url.searchParams.delete(API_SESSION_QUERY_PARAM);\n const parent = readTraceContext(context.req.raw.headers);\n try {\n return await telemetry.runInSpan(\n 'web.package_proxy',\n { 'operation.name': 'web.package_proxy', 'http.method': context.req.method },\n async (trace) =>\n await proxyToSocket({\n socketPath: summary.apiSocketPath as string,\n path: `${url.pathname}${url.search}`,\n method: context.req.method,\n headers: context.req.raw.headers,\n body: context.req.raw.body,\n trace: forwardedTraceContext(trace, parent),\n }),\n parent,\n );\n } catch (error) {\n notice(`session ${sessionId} API is unreachable (${describeError(error)})`);\n return context.json({ error: `Session ${sessionId} is not answering.` }, 502);\n }\n });\n}\n\n/**\n * Serves the cockpit and multiplexes every session behind it.\n *\n * Attach tokens stay in this process. The browser authenticates by reaching a\n * loopback port, so a page never holds a credential that would let it talk to\n * a session directly. One page WebSocket carries all sessions: hub frames\n * describe the set, and session traffic travels enveloped by session id.\n */\nexport async function serveWeb(options: WebServerOptions): Promise<WebServer> {\n const host = options.host ?? '127.0.0.1';\n const notice = options.onNotice ?? ((): void => {});\n const telemetry = createWebTelemetry();\n const store = createRemoteAccessStore({ stateDir: options.remoteStateDir, onNotice: notice });\n const repositoryRoot = (directory: string): string | undefined => {\n try {\n return fs.realpathSync(findRepositoryRoot(directory));\n } catch {\n return undefined;\n }\n };\n const compositionRoot = (directory: string): string | undefined => {\n const repository = repositoryRoot(directory);\n if (repository !== undefined) return repository;\n const globalRoot = globalDoomConfigDirectory();\n try {\n return readSyncRegistration(globalRoot)?.root;\n } catch (error) {\n notice(`global cockpit composition is unreadable (${describeError(error)})`);\n return undefined;\n }\n };\n const pinnedRoot = options.compositionDir === undefined ? undefined : repositoryRoot(options.compositionDir);\n if (options.compositionDir !== undefined && pinnedRoot === undefined) {\n throw new Error(`Cockpit composition directory is not inside a repository: ${options.compositionDir}`);\n }\n const initialRecords = readRegistryRecords(options.registryDir, notice);\n const liveRoots = initialRecords\n .map((record) => compositionRoot(record.cwd))\n .filter((root): root is string => root !== undefined);\n const compositionRoots = [...new Set(pinnedRoot === undefined ? liveRoots : [pinnedRoot])].sort((left, right) =>\n left.localeCompare(right),\n );\n const syncGuards = new Map<string, SyncGuard>();\n for (const root of compositionRoots) {\n syncGuards.set(root, createSyncGuard({ repoRoot: root, onNotice: notice }));\n }\n await Promise.all([...syncGuards.values()].map(async (guard) => await guard.ensureSynced()));\n const resolveComposition = async () =>\n await resolveWebComposition({\n repositoryRoots: compositionRoots,\n stateDirectory: store.directory,\n onNotice: notice,\n });\n // A repository whose web plugins cannot be bundled must not stop the hub\n // from binding. Serving the packaged cockpit loses the plugins, which the\n // notice says out loud, but a reachable hub can still be used to fix them;\n // a hub that refused to start could not.\n let startupComposition: Awaited<ReturnType<typeof resolveComposition>>;\n try {\n startupComposition = await resolveComposition();\n } catch (error) {\n notice(`the cockpit composition is unavailable (${describeError(error)}); serving the packaged bundle`);\n startupComposition = undefined;\n }\n let served = {\n assetsDir: resolveAssetsDir(options.assetsDir, startupComposition, notice),\n apiDirectory: startupComposition?.apiDirectory,\n };\n const ensureSessionSynced = async (directory: string): Promise<void> => {\n const root = compositionRoot(directory);\n if (root === undefined) return;\n const existing = syncGuards.get(root);\n if (existing !== undefined) {\n await existing.ensureSynced();\n return;\n }\n const guard = createSyncGuard({ repoRoot: root, onNotice: notice });\n try {\n await guard.ensureSynced();\n } finally {\n guard.close();\n }\n };\n const hub = buildHub(options, notice, await loadHubChannels(served.assetsDir, notice), telemetry);\n // Threads are journals the data channels can name (a subagent run's own\n // session file); the hub tails one only while a page follows it.\n const threads = createThreadJournals({\n resolve: (sessionId, threadId) => hub.threadJournal(sessionId, threadId),\n onNotice: notice,\n });\n /**\n * Every attached page, tagged with the listener it arrived on.\n *\n * The split matters: an approval prompt must reach the host and only the\n * host, because a paired phone that could approve devices would be able to\n * make its own access permanent.\n */\n const pages = new Set<{ post: (frame: object) => void; local: boolean }>();\n const broadcast = (frame: object, localOnly: boolean): void => {\n for (const page of pages) {\n if (localOnly && !page.local) continue;\n page.post(frame);\n }\n };\n\n const app = new Hono();\n const nodeWs = createNodeWebSocket({ app });\n /** Known once the listener binds; until then the guard treats every request as remote. */\n let loopbackPort: number | undefined;\n const pwaDir = packagedPwaDir();\n const publication = createBundlePublication({\n assetsDir: () => served.assetsDir,\n stateDir: store.directory,\n onNotice: notice,\n });\n let livePush: LiveWebPush | undefined;\n reapStaleTunnel(store.directory, notice);\n /**\n * Stands the host's sessions down so the container can take them over.\n *\n * Done here rather than in the launcher because this is where the hub is, and\n * the stop has to happen before the move: a recreated session opens the same\n * working tree, and two agents in one tree would fight over it.\n */\n const handover =\n options.onHandover === undefined\n ? undefined\n : (settings: RemoteAccessSettings): void => {\n const plan = planSessionMigration(\n hub.snapshot().map((session) => ({\n id: session.id,\n cwd: session.cwd,\n ...(session.name === undefined ? {} : { name: session.name }),\n })),\n settings.sandbox.workspaces,\n );\n for (const line of describeStranded(plan.stranded)) notice(line);\n for (const session of plan.migrate) {\n const stopped = hub.stop(session.id);\n if (!stopped.ok) notice(`could not stop ${session.name ?? session.id} before the move: ${stopped.error}`);\n }\n options.onHandover?.({ settings, sessions: plan.migrate });\n };\n\n const remote = createRemoteAccess({\n store,\n launchTunnel:\n options.remoteAccess?.launchTunnel ??\n createTunnelLauncher({\n stateDir: store.directory,\n ...(options.cloudflaredPath === undefined ? {} : { cloudflaredPath: options.cloudflaredPath }),\n onNotice: notice,\n onExit: (message) => {\n notice(`remote access: ${message}`);\n void remote.disable();\n },\n }),\n // The tunnel gets its own loopback socket. That is the only reliable way to\n // tell its traffic from the host's, since cloudflared connects from\n // 127.0.0.1 and forges nothing a header could reveal.\n bindListener: async () =>\n await new Promise((resolve, reject) => {\n const tunnelServer = serve(\n {\n fetch: app.fetch,\n port: 0,\n hostname: '127.0.0.1',\n serverOptions: {\n maxHeaderSize: TUNNEL_HEADER_BYTES,\n headersTimeout: TUNNEL_HEADERS_TIMEOUT_MS,\n requestTimeout: TUNNEL_REQUEST_TIMEOUT_MS,\n },\n },\n (info) => {\n nodeWs.injectWebSocket(tunnelServer);\n resolve({\n port: info.port,\n close: async () =>\n await new Promise<void>((done) => {\n (tunnelServer as { closeAllConnections?: () => void }).closeAllConnections?.();\n tunnelServer.close(() => done());\n }),\n });\n },\n );\n const bounded = tunnelServer as typeof tunnelServer & { maxConnections: number; maxRequestsPerSocket: number };\n bounded.maxConnections = TUNNEL_CONNECTION_LIMIT;\n bounded.maxRequestsPerSocket = TUNNEL_REQUESTS_PER_SOCKET;\n tunnelServer.once('error', reject);\n }),\n bundleTrust: () => publication.trust(),\n onDeviceDropped: (deviceId) => livePush?.remove(deviceId),\n onNotice: notice,\n ...(handover === undefined ? {} : { requestHandover: handover }),\n contained: insideSandbox(process.env),\n broadcastLocal: (frame) => broadcast(frame, true),\n broadcastAll: (frame) => broadcast(frame, false),\n ...(options.remoteAccess?.now === undefined ? {} : { now: options.remoteAccess.now }),\n });\n livePush = createLiveWebPush({\n stateDir: store.directory,\n isConnected: (deviceId) => remote.isDeviceConnected(deviceId),\n onNotice: notice,\n });\n const disconnectLivePush = hub.onEvent((event) => {\n if (event.kind === 'frame' && parseDoomNotificationEntry(event.frame) !== undefined) void livePush?.notify();\n });\n // First route on the app, because Hono composes matching handlers in\n // registration order: a guard added after a terminating handler never runs\n // for that path. It also refuses socket upgrades, which is what closes the\n // cross-site WebSocket hijack that loopback binding never covered.\n const guard = createRemoteGuard({\n loopbackPort: () => loopbackPort,\n tunnelPolicy: () => remote.tunnelPolicy(),\n authorize: (context) => remote.authorize(getCookie(context, DEVICE_COOKIE, 'host')),\n trustedDevice: (context) => (context.env as SealedRequestBindings | undefined)?.sealedDeviceId,\n channelReady: (deviceId, scope) => remote.channelFor(deviceId, scope) !== undefined,\n stepUp: {\n required: (action) => remote.stepUpRequired(action),\n verify: async (context, action, assertion) => {\n if (typeof assertion !== 'object' || assertion === null) return false;\n const ceremonyId = (assertion as { ceremonyId?: unknown }).ceremonyId;\n const response = (assertion as { response?: unknown }).response;\n if (typeof ceremonyId !== 'string') return false;\n const trusted = (context.env as SealedRequestBindings | undefined)?.sealedDeviceId;\n const deviceId = trusted ?? remote.authorize(getCookie(context, DEVICE_COOKIE, 'host'));\n const caller = deviceId === undefined ? 'local' : `device:${deviceId}`;\n return await remote.passkeys().finishStepUp(ceremonyId, caller, action, response);\n },\n },\n extraOrigins: allowedOriginsFromEnv(process.env[ALLOW_ORIGIN_ENV]),\n });\n app.use('*', guard.middleware);\n app.use('*', async (context, next) => {\n const pathname = new URL(context.req.url).pathname;\n if (pathname === REMOTE_HTTP_ROUTE) return next();\n const operation = requestOperation(pathname);\n if (operation === undefined) return next();\n const started = performance.now();\n return await telemetry.runInSpan(\n operation,\n { 'operation.name': operation, 'http.method': context.req.method },\n async () => {\n await next();\n await telemetry.recordEvent('web.api.request', {\n 'operation.name': operation,\n 'http.method': context.req.method,\n 'http.status_code': context.res.status,\n duration_ms: Math.max(0, Math.round(performance.now() - started)),\n });\n },\n readTraceContext(context.req.raw.headers),\n );\n });\n // Reject a declared oversized body before any public route reads it. Chunked\n // bodies are bounded by the route-specific or downstream streaming limiter.\n app.use('*', async (context, next) => {\n if (guard.listenerOf(context) === 'local') return next();\n const declared = Number(context.req.header('content-length'));\n if (Number.isFinite(declared) && declared > TUNNEL_BODY_BYTES) {\n return context.json({ error: 'The tunnel request body is too large.' }, 413);\n }\n return next();\n });\n registerRemoteRoutes(app, { remote, listenerOf: (context) => guard.listenerOf(context) });\n const tunnelBodyLimit = bodyLimit({\n maxSize: TUNNEL_BODY_BYTES,\n onError: (context) => context.json({ error: 'The tunnel request body is too large.' }, 413),\n });\n app.use('*', async (context, next) => {\n if (guard.listenerOf(context) === 'local') return next();\n return await tunnelBodyLimit(context, next);\n });\n const browserTelemetryBodyLimit = bodyLimit({\n maxSize: BROWSER_TELEMETRY_BODY_BYTES,\n onError: (context) => context.json({ error: 'The telemetry batch is too large.' }, 413),\n });\n const acceptBrowserTelemetryRequest = createBrowserTelemetryRateLimit();\n app.post(WEB_TELEMETRY_ROUTE, browserTelemetryBodyLimit, async (context) => {\n if (!acceptBrowserTelemetryRequest()) return context.json({ error: 'Too many telemetry batches.' }, 429);\n let body: unknown;\n try {\n body = await context.req.json();\n } catch {\n return context.json({ error: 'The telemetry batch must be JSON.' }, 400);\n }\n const events = parseBrowserTelemetryBatch(body);\n if (events === undefined) return context.json({ error: 'The telemetry batch is invalid.' }, 400);\n for (const event of events) {\n if (isBrowserErrorEvent(event)) {\n // The message and stack ride the exception, not the attributes:\n // sanitisation drops those keys, and rightly so for everything that is\n // not an error the reader asked to see.\n await telemetry.recordError(\n BROWSER_ERROR_EVENT,\n createBrowserError(event),\n {\n 'browser.source': event.source,\n ...(event.session_id === undefined ? {} : { 'session.id': event.session_id }),\n 'failure.kind': 'browser',\n },\n { includeException: true },\n );\n continue;\n }\n await telemetry.recordEvent(event.name, {\n ...(event.duration_ms === undefined ? {} : { duration_ms: event.duration_ms }),\n ...(event.count === undefined ? {} : { count: event.count }),\n });\n }\n return context.body(null, 204);\n });\n app.post(REMOTE_HTTP_ROUTE, async (context) => {\n const deviceId = remote.authorize(getCookie(context, DEVICE_COOKIE, 'host'));\n if (deviceId === undefined) return context.json({ error: 'This device is not paired.' }, 401);\n const channel = remote.channelFor(deviceId, 'http');\n if (channel === undefined) return context.json({ error: 'This device has no sealed HTTP channel.' }, 401);\n\n let envelope: unknown;\n try {\n envelope = await context.req.json();\n } catch {\n return context.json({ error: 'The sealed request envelope was malformed.' }, 400);\n }\n const opened = channel.open(envelope);\n if (!opened.ok) return context.json({ error: `The sealed request was refused: ${opened.failure}.` }, 400);\n\n let decoded: unknown;\n try {\n decoded = JSON.parse(new TextDecoder().decode(opened.plaintext));\n } catch {\n return context.json({ error: 'The sealed HTTP request was malformed.' }, 400);\n }\n const request = parseSealedHttpRequest(decoded);\n const headers = request === undefined ? undefined : sealedRequestHeaders(request, context);\n const body = request === undefined ? undefined : decodeSealedHttpBody(request.body);\n if (request === undefined || headers === undefined || body === undefined) {\n return context.json({ error: 'The sealed HTTP request was invalid.' }, 400);\n }\n\n const target = new URL(request.target, new URL(context.req.url).origin);\n const innerResponse = await app.request(\n target,\n {\n method: request.method,\n headers,\n ...(request.method === 'GET' || request.method === 'HEAD' ? {} : { body: Uint8Array.from(body).buffer }),\n },\n { ...(context.env as SealedRequestBindings), sealedDeviceId: deviceId },\n );\n const responseBody = Buffer.from(await innerResponse.arrayBuffer());\n const sealed = channel.seal(\n new TextEncoder().encode(\n JSON.stringify({\n v: SEALED_HTTP_VERSION,\n status: innerResponse.status,\n headers: Array.from(innerResponse.headers.entries()),\n body: responseBody.toString('base64'),\n }),\n ),\n );\n if (!sealed.ok) return context.json({ error: `The sealed response failed: ${sealed.failure}.` }, 503);\n return context.json(sealed.envelope);\n });\n const pushBodyLimit = bodyLimit({\n maxSize: 8 * 1024,\n onError: (context) => context.json({ error: 'The Push subscription is too large.' }, 413),\n });\n const pushDevice = (context: Context): string | undefined =>\n (context.env as SealedRequestBindings | undefined)?.sealedDeviceId;\n app.get(REMOTE_PUSH_KEY_ROUTE, (context) => {\n if (pushDevice(context) === undefined) return context.json({ error: 'A sealed paired device is required.' }, 401);\n return context.json({ publicKey: livePush?.publicKey() }, 200, { 'Cache-Control': 'no-store' });\n });\n app.post(REMOTE_PUSH_ROUTE, pushBodyLimit, async (context) => {\n const deviceId = pushDevice(context);\n if (deviceId === undefined) return context.json({ error: 'A sealed paired device is required.' }, 401);\n let subscription: unknown;\n try {\n subscription = await context.req.json();\n } catch {\n return context.json({ error: 'The Push subscription must be JSON.' }, 400);\n }\n if (livePush?.subscribe(deviceId, subscription) !== true) {\n return context.json({ error: 'The Push subscription is invalid.' }, 400);\n }\n return context.body(null, 204);\n });\n app.delete(REMOTE_PUSH_ROUTE, (context) => {\n const deviceId = pushDevice(context);\n if (deviceId === undefined) return context.json({ error: 'A sealed paired device is required.' }, 401);\n livePush?.remove(deviceId);\n return context.body(null, 204);\n });\n // Every running session already serves Pi's protocol; the hub composes them\n // into one server so a browser sees a single endpoint with many sessions.\n // Remote clients get a distinct protocol service that can attach to existing\n // sessions but cannot bypass HTTP step-up by creating one directly.\n const localProtocolListener = createPiWebSocketListener({ onError: (error) => notice(`protocol: ${error.message}`) });\n const remoteProtocolListener = createPiWebSocketListener({\n onError: (error) => notice(`protocol: ${error.message}`),\n });\n const protocolService = createPiHubService({\n records: () => hub.records(),\n spawn: (input) => hub.create(input),\n onNotice: notice,\n });\n const remoteProtocolService = {\n ...protocolService,\n createSession: async () => {\n throw new PiServerError('not_implemented', 'Remote protocol clients cannot create sessions');\n },\n };\n const localProtocolServer = new PiServer(protocolService, {\n listeners: [localProtocolListener],\n onError: (error) => notice(`protocol: ${error.message}`),\n });\n const remoteProtocolServer = new PiServer(remoteProtocolService, {\n listeners: [remoteProtocolListener],\n onError: (error) => notice(`protocol: ${error.message}`),\n });\n await Promise.all([localProtocolServer.start(), remoteProtocolServer.start()]);\n // Provider credentials belong to the machine, not to a session: the hub\n // keeps one Pi runtime over the shared auth.json and signs in for all.\n const providerAuth = createProviderAuth({ runtime: options.authRuntime, onNotice: notice });\n registerAuthRoutes(app, providerAuth);\n // Settings read and write the machine's Doom config. Session working\n // directories are normalized to their nearest repository marker before the\n // picker or a package API can address them. The bounded registry keeps recent\n // roots available after their last session closes without trusting browser paths.\n const repositoryRegistry = createSettingsRepositoryRegistry(hub);\n const repositories = (): SettingsRepository[] => repositoryRegistry.list();\n const resolveRepository = (repositoryId: string): string | undefined => repositoryRegistry.resolve(repositoryId);\n const readRepositorySync = (repositoryId: string): DoomRepositorySyncView | undefined => {\n const root = resolveRepository(repositoryId);\n if (root === undefined) return undefined;\n const drift = readSyncDrift({ repoRoot: root });\n let state: SyncState | undefined;\n try {\n state = readSyncRegistration(root) ? readSyncState(root) : undefined;\n } catch {\n state = undefined;\n }\n return {\n fresh: drift.fresh,\n reasons: [...drift.reasons],\n ...(state === undefined ? {} : { mcpProjection: state.fileState.mcpProjection }),\n };\n };\n registerSettingsRoutes(app, {\n repositories,\n models: () => providerAuth.listModels(),\n });\n // Package APIs, mounted before the SPA fallback so their routes are reachable\n // and everything else still falls through to the bundle. Hub-scoped ones run\n // here; session-scoped ones live in each session's own server and are proxied.\n const apiDirectory = served.apiDirectory;\n const hubApis =\n apiDirectory === undefined && !process.env[PACKAGE_API_DIR_ENV]\n ? []\n : await loadPackageApis('hub', { apiDirectory, onNotice: notice });\n const pluginApis = mountHubApis(app, hubApis, notice, resolveRepository, readRepositorySync);\n mountSessionApiProxy(app, hub, notice, telemetry);\n\n app.get('/api/health', (context) =>\n context.json({\n ok: true,\n role: HUB_ROLE,\n protocol: HUB_PROTOCOL_VERSION,\n sessions: hub.snapshot().length,\n pid: process.pid,\n version: packagedVersion(),\n }),\n );\n\n app.post(SESSIONS_API_ROUTE, async (context) => {\n let body: unknown;\n try {\n body = await context.req.json();\n } catch {\n return context.json({ error: 'The request body must be JSON.' }, 400);\n }\n if (!isRecord(body) || typeof body.cwd !== 'string' || body.cwd === '') {\n return context.json({ error: 'A cwd string is required.' }, 400);\n }\n const name = typeof body.name === 'string' && body.name !== '' ? body.name : undefined;\n await ensureSessionSynced(body.cwd);\n const outcome = await hub.create({ cwd: body.cwd, name, trace: readTraceContext(context.req.raw.headers) });\n if (outcome.ok) return context.json({ sessionId: outcome.sessionId }, 201);\n return context.json({ error: outcome.error }, outcome.code === 'invalid_request' ? 400 : 502);\n });\n\n // A running server reads the composition once: its extensions when the agent\n // starts, its package API routes when the process does. So a rebuild reaches\n // an existing session only by replacing the process, and the sync has to\n // finish before the replacement starts or it reads the same stale artifacts.\n app.post(`${SESSIONS_API_ROUTE}/:sessionId/restart`, async (context) => {\n const sessionId = context.req.param('sessionId');\n const summary = hub.snapshot().find((candidate) => candidate.id === sessionId);\n if (summary !== undefined) await ensureSessionSynced(summary.cwd);\n const outcome = await hub.restart(sessionId, readTraceContext(context.req.raw.headers));\n if (outcome.ok) return context.json({ sessionId: outcome.sessionId }, 202);\n return context.json({ error: outcome.error }, outcome.code === 'invalid_request' ? 404 : 502);\n });\n\n app.get(`${SESSIONS_API_ROUTE}/:sessionId/history`, async (context) => {\n const sessionId = context.req.param('sessionId');\n const summary = hub.snapshot().find((candidate) => candidate.id === sessionId);\n if (!summary) return context.json({ error: 'Unknown session.' }, 404);\n const sessions = await SessionManager.list(summary.cwd);\n return context.json({\n sessions: sessions.map((session) => ({\n id: session.id,\n ...(session.name === undefined ? {} : { name: session.name }),\n firstMessage: session.firstMessage,\n createdAt: session.created.toISOString(),\n updatedAt: session.modified.toISOString(),\n messageCount: session.messageCount,\n })),\n });\n });\n\n app.post(`${SESSIONS_API_ROUTE}/:sessionId/resume`, async (context) => {\n const sessionId = context.req.param('sessionId');\n const summary = hub.snapshot().find((candidate) => candidate.id === sessionId);\n if (!summary) return context.json({ error: 'Unknown session.' }, 404);\n let body: unknown;\n try {\n body = await context.req.json();\n } catch {\n return context.json({ error: 'The request body must be JSON.' }, 400);\n }\n if (!isRecord(body) || typeof body.targetSessionId !== 'string' || body.targetSessionId === '') {\n return context.json({ error: 'A targetSessionId string is required.' }, 400);\n }\n const sessions = await SessionManager.list(summary.cwd);\n const target = sessions.find((session) => session.id === body.targetSessionId);\n if (!target) return context.json({ error: 'That Pi thread does not belong to this workspace.' }, 404);\n await ensureSessionSynced(summary.cwd);\n const outcome = await hub.resume(\n sessionId,\n { sessionId: target.id, name: resumedSessionName(target, summary.cwd) },\n readTraceContext(context.req.raw.headers),\n );\n if (outcome.ok) return context.json({ sessionId: outcome.sessionId }, 202);\n return context.json({ error: outcome.error }, outcome.code === 'invalid_request' ? 409 : 502);\n });\n app.delete(`${SESSIONS_API_ROUTE}/:sessionId`, (context) => {\n const sessionId = context.req.param('sessionId');\n const outcome = hub.stop(sessionId);\n if (outcome.ok) return context.json({ sessionId }, 202);\n return context.json(\n { error: outcome.error },\n outcome.code === 'unknown' ? 404 : outcome.code === 'self' ? 409 : 502,\n );\n });\n\n // File completion for the composer's @ references: bounded, cwd-scoped,\n // and only for sessions the hub actually manages.\n app.get('/api/sessions/:sessionId/files', async (context) => {\n const sessionId = context.req.param('sessionId');\n const summary = hub.snapshot().find((candidate) => candidate.id === sessionId);\n if (!summary) return context.json({ error: 'Unknown session.' }, 404);\n const query = context.req.query('q') ?? '';\n const files = await listSessionFiles(summary.cwd, query, 20);\n return context.json({ files });\n });\n\n /**\n * The subtree directory suggestions may name, or undefined for all of them.\n *\n * A paired device can ask this route, and answering from the whole home\n * directory hands it a map of the machine: every project, every client name,\n * every checkout. So a request arriving on the tunnel is pinned to the\n * directory the cockpit was started from, which is the one the person running\n * it already chose.\n *\n * Only that request. The person at this keyboard already has a shell, so\n * pinning them would cost the picker its usefulness and buy nothing. A\n * contained cockpit needs none of it either, because its mounts are the\n * boundary and nothing outside them is visible to it at all.\n */\n const browseRoot = (context: Context): string | undefined => {\n if (guard.listenerOf(context) === 'local' || insideSandbox(process.env)) return undefined;\n return options.browseRoot ?? process.cwd();\n };\n\n // Directory suggestions for the new-session picker: the children of the\n // typed parent while a path is being drilled into, and a ranked search of\n // the home directory for anything else, so a name or a path remembered from\n // another machine still finds the folder.\n app.get(DIRECTORIES_API_ROUTE, async (context) => {\n const root = browseRoot(context);\n const directories = await suggestDirectories(context.req.query('q') ?? '', {\n limit: DIRECTORY_SUGGESTION_LIMIT,\n ...(root === undefined ? {} : { root }),\n });\n return context.json({ directories });\n });\n\n // The timeline's @file previews: one cwd-contained file, capped in size.\n app.get(SESSION_FILE_ROUTE, async (context) => {\n const sessionId = context.req.param('sessionId');\n const summary = hub.snapshot().find((candidate) => candidate.id === sessionId);\n if (!summary) return context.json({ error: 'Unknown session.' }, 404);\n const relativePath = context.req.query('path') ?? '';\n const file = await readSessionFile(summary.cwd, relativePath, MAX_SESSION_FILE_BYTES);\n if (file.status === 'forbidden') return context.json({ error: 'The path leaves the session directory.' }, 403);\n if (file.status === 'not-found') return context.json({ error: 'No such file.' }, 404);\n if (file.status === 'too-large') return context.json({ error: 'The file is too large to preview.' }, 413);\n return context.body(new Uint8Array(file.body), 200, sessionFileHeaders(relativePath));\n });\n\n app.get(\n SESSION_SOCKET_ROUTE,\n nodeWs.upgradeWebSocket((context) => {\n const local = guard.listenerOf(context) === 'local';\n // Resolved once at upgrade: the device is fixed for the socket's life, and\n // looking it up per frame would bump last-seen on every keystroke.\n const deviceId = local ? undefined : remote.authorize(getCookie(context, DEVICE_COOKIE, 'host'));\n const channel = deviceId === undefined ? undefined : remote.channelFor(deviceId, 'session');\n const subscriptions = new Set<string>();\n const connectionId = randomUUID();\n /** The threads this page follows; one socket may follow several of one session. */\n const threadSubscriptions = new Map<string, { sessionId: string; threadId: string }>();\n let disconnect: (() => void) | undefined;\n let disconnectThreads: (() => void) | undefined;\n /** Held on the socket, not inside onOpen, so close can withdraw it. */\n let registered: { post: (frame: object) => void; local: boolean } | undefined;\n /** Withdraws this socket from the remote registry, so switch-off can close it. */\n let untrack: (() => void) | undefined;\n /** Lets go of every followed thread, or only a departed session's. */\n const releaseThreads = (sessionId?: string): void => {\n for (const [key, held] of threadSubscriptions) {\n if (sessionId !== undefined && held.sessionId !== sessionId) continue;\n threadSubscriptions.delete(key);\n threads.unsubscribe(held.sessionId, held.threadId);\n }\n };\n return {\n onOpen(_event, ws) {\n if (!local && channel === undefined) {\n ws.close(1008, 'sealed session channel required');\n return;\n }\n const post = (frame: SessionFrame | object): void => {\n const text = JSON.stringify(frame);\n let outgoing = text;\n if (!local) {\n if (channel === undefined) return;\n const sealed = channel.seal(new TextEncoder().encode(text));\n if (!sealed.ok) {\n ws.close(1008, 'sealed session channel failed');\n return;\n }\n outgoing = JSON.stringify(sealed.envelope);\n }\n try {\n ws.send(outgoing);\n } catch {\n // The browser went away mid-write; onClose tears the socket down.\n }\n };\n registered = { post, local };\n pages.add(registered);\n // A socket that has upgraded has left the HTTP server's connection\n // tracking, so closing the tunnel listener does not reach it.\n // Without this a paired phone keeps driving the agent after remote\n // access is switched off.\n if (deviceId !== undefined) untrack = remote.trackSocket(deviceId, (code, reason) => ws.close(code, reason));\n post(hubHello(hub.channelTypes()));\n post({ type: SESSIONS_SNAPSHOT_TYPE, sessions: hub.snapshot() });\n disconnect = hub.onEvent((event) => {\n if (event.kind === 'upsert') post({ type: SESSION_UPSERT_TYPE, session: event.session });\n else if (event.kind === 'removed') {\n subscriptions.delete(event.sessionId);\n releaseThreads(event.sessionId);\n post({ type: SESSION_REMOVED_TYPE, sessionId: event.sessionId });\n } else if (event.kind === 'channel') {\n if (\n event.connectionId === connectionId ||\n (event.connectionId === undefined && subscriptions.has(event.sessionId))\n ) {\n post({ type: event.frameType, sessionId: event.sessionId, payload: event.payload });\n }\n } else {\n const notification = parseDoomNotificationEntry(event.frame);\n if (notification !== undefined || subscriptions.has(event.sessionId)) {\n post(sessionFrameEnvelope(event.sessionId, event.frame));\n }\n }\n });\n disconnectThreads = threads.onFrame((event) => {\n if (threadSubscriptions.has(threadKey(event.sessionId, event.threadId))) {\n post(threadFrameEnvelope(event.sessionId, event.threadId, event.frame));\n }\n });\n notice('browser attached');\n },\n onMessage(event) {\n if (typeof event.data !== 'string') return;\n let parsed: unknown;\n try {\n parsed = JSON.parse(event.data);\n } catch {\n return;\n }\n if (!local) {\n if (channel === undefined) return;\n const opened = channel.open(parsed);\n // A frame that will not open was altered or replayed. Dropping it\n // is the only safe answer; there is no plaintext to fall back to.\n if (!opened.ok) return;\n try {\n parsed = JSON.parse(new TextDecoder().decode(opened.plaintext));\n } catch {\n return;\n }\n }\n if (!isRecord(parsed) || typeof parsed.sessionId !== 'string') return;\n const sessionId = parsed.sessionId;\n if (\n typeof parsed.type === 'string' &&\n hub.channelTypes().includes(parsed.type) &&\n (subscriptions.has(sessionId) || hub.channelReceivesWithoutSubscription(parsed.type))\n ) {\n hub.receiveChannel(sessionId, parsed.type, parsed.payload, connectionId);\n return;\n }\n if (parsed.type === SUBSCRIBE_TYPE) {\n const backlog = hub.backlog(sessionId);\n if (!backlog) return; // Unknown session; the snapshot said otherwise.\n subscriptions.add(sessionId);\n registered?.post(backlog);\n for (const frame of hub.channelFrames(sessionId)) registered?.post(frame);\n return;\n }\n if (parsed.type === UNSUBSCRIBE_TYPE) {\n subscriptions.delete(sessionId);\n return;\n }\n if (parsed.type === HISTORY_REQUEST_TYPE) {\n // Older transcript, on demand. The hub kept what the attach path\n // was too small to publish, so scrolling back reads from memory\n // rather than asking the session to re-read its journal.\n const page = hub.history(sessionId, {\n ...(typeof parsed.before === 'string' ? { before: parsed.before } : {}),\n ...(typeof parsed.limit === 'number' ? { limit: parsed.limit } : {}),\n });\n if (!page) return;\n registered?.post(page);\n return;\n }\n if (parsed.type === SUBSCRIBE_THREAD_TYPE || parsed.type === UNSUBSCRIBE_THREAD_TYPE) {\n if (typeof parsed.threadId !== 'string') return;\n const threadId = parsed.threadId;\n const key = threadKey(sessionId, threadId);\n if (parsed.type === UNSUBSCRIBE_THREAD_TYPE) {\n if (threadSubscriptions.delete(key)) threads.unsubscribe(sessionId, threadId);\n return;\n }\n if (threadSubscriptions.has(key) || hub.backlog(sessionId) === undefined) return;\n threadSubscriptions.set(key, { sessionId, threadId });\n registered?.post(threadBacklog(sessionId, threadId, threads.subscribe(sessionId, threadId)));\n return;\n }\n if (parsed.type === SESSION_COMMAND_TYPE && isRecord(parsed.frame)) {\n // The hub owns the handshake; a page must not be able to replay it.\n if (parsed.frame.type === ATTACH_TYPE) return;\n hub.command(sessionId, parsed.frame);\n }\n },\n onClose() {\n hub.disconnectChannels(connectionId);\n if (registered) pages.delete(registered);\n registered = undefined;\n untrack?.();\n untrack = undefined;\n disconnect?.();\n disconnect = undefined;\n disconnectThreads?.();\n disconnectThreads = undefined;\n subscriptions.clear();\n releaseThreads();\n notice('browser detached');\n },\n };\n }),\n );\n\n app.get(\n PROTOCOL_SOCKET_ROUTE,\n nodeWs.upgradeWebSocket((context) => {\n const local = guard.listenerOf(context) === 'local';\n const deviceId = local ? undefined : remote.authorize(getCookie(context, DEVICE_COOKIE, 'host'));\n const channel = deviceId === undefined ? undefined : remote.channelFor(deviceId, 'protocol');\n let handler: ReturnType<typeof localProtocolListener.accept>;\n let untrack: (() => void) | undefined;\n return {\n onOpen(_event, ws) {\n if (!local && channel === undefined) {\n ws.close(1008, 'sealed protocol channel required');\n return;\n }\n if (deviceId !== undefined) untrack = remote.trackSocket(deviceId, (code, reason) => ws.close(code, reason));\n handler = (local ? localProtocolListener : remoteProtocolListener).accept({\n send: (data) => {\n if (local) {\n ws.send(data as ArrayBuffer);\n return;\n }\n if (channel === undefined) return;\n const bytes = data instanceof Uint8Array ? data : new Uint8Array(data as ArrayBuffer);\n const sealed = channel.seal(bytes);\n if (!sealed.ok) {\n ws.close(1008, 'sealed protocol channel failed');\n return;\n }\n ws.send(new TextEncoder().encode(JSON.stringify(sealed.envelope)));\n },\n close: () => ws.close(),\n get readyState() {\n return ws.readyState;\n },\n });\n },\n async onMessage(event) {\n const data = event.data;\n if (typeof data === 'string') return;\n const bytes =\n data instanceof Blob\n ? new Uint8Array(await data.arrayBuffer())\n : data instanceof ArrayBuffer\n ? new Uint8Array(data)\n : new Uint8Array(data);\n if (local) {\n handler?.onData(bytes);\n return;\n }\n if (channel === undefined) return;\n let envelope: unknown;\n try {\n envelope = JSON.parse(new TextDecoder().decode(bytes));\n } catch {\n return;\n }\n const opened = channel.open(envelope);\n if (opened.ok) handler?.onData(opened.plaintext);\n },\n onClose() {\n untrack?.();\n untrack = undefined;\n handler?.onClose();\n handler = undefined;\n },\n onError(event) {\n handler?.onError(event instanceof Error ? event : new Error('The protocol socket failed'));\n handler = undefined;\n },\n };\n }),\n );\n\n app.get('/manifest.webmanifest', (context) => {\n const file = path.join(pwaDir, 'manifest.webmanifest');\n const body = readAsset(file);\n if (body === undefined) return context.text('The PWA manifest is unavailable.', 404);\n return context.body(new Uint8Array(body), 200, {\n 'Cache-Control': 'no-store',\n 'Content-Type': 'application/manifest+json; charset=utf-8',\n });\n });\n app.get('/sw.js', (context) => {\n const file = path.join(pwaDir, 'sw.js');\n const body = readAsset(file);\n if (body === undefined) return context.text('The trusted service worker is unavailable.', 404);\n return context.body(new Uint8Array(body), 200, {\n 'Cache-Control': 'no-cache',\n 'Content-Type': 'text/javascript; charset=utf-8',\n 'Service-Worker-Allowed': '/',\n });\n });\n app.get(`${PWA_ASSET_PREFIX}*`, (context) => {\n const requested = new URL(context.req.url).pathname.slice(PWA_ASSET_PREFIX.length);\n const file = resolveAssetPath(pwaDir, `/${requested}`);\n const body = file === undefined ? undefined : readAsset(file);\n if (file === undefined || body === undefined) return context.text('PWA asset not found.', 404);\n return context.body(new Uint8Array(body), 200, {\n 'Cache-Control': 'no-store',\n 'Content-Type': contentTypeFor(file),\n 'X-Content-Type-Options': 'nosniff',\n });\n });\n app.get(BUNDLE_MANIFEST_ROUTE, (context) => {\n const current = publication.current();\n if (current === undefined) return context.json({ error: 'No signed cockpit bundle is available.' }, 503);\n return context.json(current.signed, 200, { 'Cache-Control': 'no-store' });\n });\n app.get(`${RAW_BUNDLE_PREFIX}*`, (context) => {\n const requested = new URL(context.req.url).pathname;\n const matched = /^\\/bundle-assets\\/([1-9][0-9]*)(\\/.*)$/u.exec(requested);\n const revision = matched === null ? undefined : Number(matched[1]);\n const current = publication.current();\n if (\n matched === null ||\n !Number.isSafeInteger(revision) ||\n current === undefined ||\n current.signed.manifest.revision !== revision\n ) {\n return context.text('That signed bundle revision is unavailable.', 404);\n }\n const asset = assetFor(current.signed.manifest, matched[2] ?? '');\n // Read through the directory this manifest was signed over, not whatever is\n // current now: a generation that changed mid-request would otherwise serve\n // bytes the page is about to reject as a digest mismatch.\n const file = asset === undefined ? undefined : resolveAssetPath(current.assetsDir, asset.path);\n const body = file === undefined ? undefined : readAsset(file);\n if (asset === undefined || file === undefined || body === undefined)\n return context.text('Bundle asset not found.', 404);\n return context.body(new Uint8Array(body), 200, {\n 'Cache-Control': 'no-store',\n 'Content-Length': String(body.byteLength),\n 'Content-Type': asset.contentType,\n 'X-Content-Type-Options': 'nosniff',\n });\n });\n app.get('/', (context) => context.redirect(PAIRING_PAGE_ROUTE));\n app.get('*', (context) => {\n const pathname = new URL(context.req.url).pathname;\n const acceptsHtml = context.req.header('accept')?.includes('text/html') === true;\n if (!pathname.startsWith('/api/') && acceptsHtml) return context.redirect('/');\n return context.text('Not found.', 404);\n });\n\n // A rebuilt bundle only changes on disk, so re-resolve the synchronized\n // registrations and tell every page to stage the replacement.\n const refreshServedComposition = async (): Promise<void> => {\n try {\n const rebuilt = await resolveComposition();\n if (rebuilt !== undefined && rebuilt.webDirectory !== served.assetsDir) {\n served = { assetsDir: rebuilt.webDirectory, apiDirectory: rebuilt.apiDirectory };\n notice(`serving the rebuilt cockpit bundle from ${served.assetsDir}`);\n }\n publication.refresh();\n broadcast({ type: HUB_RESYNCED_TYPE }, false);\n } catch (error) {\n notice(`the rebuilt cockpit composition is unavailable (${describeError(error)}); keeping the served bundle`);\n }\n };\n for (const guard of syncGuards.values()) {\n guard.watch(() => void refreshServedComposition());\n }\n\n return new Promise<WebServer>((resolve, reject) => {\n const server = serve({ fetch: app.fetch, port: options.port, hostname: host }, (info) => {\n // Before this the guard has no loopback port to compare against and\n // treats everything as remote, which is the safe direction to be wrong in.\n loopbackPort = info.port;\n nodeWs.injectWebSocket(server);\n const url = `http://${host}:${info.port}`;\n notice(`cockpit on ${url}`);\n let closePromise: Promise<void> | undefined;\n const close = async (): Promise<void> => {\n // Remote access first, so the tunnel is down and every paired socket\n // is closed before the rest of the hub starts letting go.\n await remote.close();\n await new Promise<void>((done) => {\n threads.close();\n for (const guard of syncGuards.values()) guard.close();\n void localProtocolServer.close();\n void remoteProtocolServer.close();\n disconnectLivePush();\n livePush?.close();\n hub.close();\n providerAuth.close();\n for (const handler of pluginApis) handler.close();\n // An upgraded socket leaves the HTTP server's connection tracking,\n // so only the WebSocket server can let go of it. Without this the\n // close callback waits on a browser that has no reason to leave.\n for (const client of nodeWs.wss.clients) client.terminate();\n nodeWs.wss.close();\n (server as { closeAllConnections?: () => void }).closeAllConnections?.();\n server.close(() => done());\n });\n await shutdownWebTelemetry(telemetry);\n };\n resolve({\n url,\n port: info.port,\n close: () => (closePromise ??= close()),\n });\n });\n server.once('error', (error) => {\n threads.close();\n disconnectLivePush();\n livePush?.close();\n hub.close();\n providerAuth.close();\n void remote.close();\n for (const handler of pluginApis) handler.close();\n void telemetry.shutdown();\n reject(error);\n });\n });\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"httpServer.d.cts","names":[],"sources":["../../../src/adapters/httpServer.ts"],"mappings":";;;;;;;;;;;;iBA4csB,SAAS,SAAS,mBAAmB,QAAQ"}
1
+ {"version":3,"file":"httpServer.d.cts","names":[],"sources":["../../../src/adapters/httpServer.ts"],"mappings":";;;;;;;;;;;;iBA4dsB,SAAS,SAAS,mBAAmB,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"httpServer.d.mts","names":[],"sources":["../../../src/adapters/httpServer.ts"],"mappings":";;;;;;;;;;;;iBA4csB,SAAS,SAAS,mBAAmB,QAAQ"}
1
+ {"version":3,"file":"httpServer.d.mts","names":[],"sources":["../../../src/adapters/httpServer.ts"],"mappings":";;;;;;;;;;;;iBA4dsB,SAAS,SAAS,mBAAmB,QAAQ"}