@agimon-ai/doompi-core 0.0.1-alpha.76 → 0.0.1-alpha.78

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 (205) hide show
  1. package/dist/headless-hub.cjs +107 -7
  2. package/dist/headless-hub.cjs.map +1 -1
  3. package/dist/headless-hub.d.cts +10 -0
  4. package/dist/headless-hub.d.cts.map +1 -1
  5. package/dist/headless-hub.d.mts +10 -0
  6. package/dist/headless-hub.d.mts.map +1 -1
  7. package/dist/headless-hub.mjs +107 -7
  8. package/dist/headless-hub.mjs.map +1 -1
  9. package/dist/headless-server.cjs +63 -6
  10. package/dist/headless-server.cjs.map +1 -1
  11. package/dist/headless-server.d.cts +2 -0
  12. package/dist/headless-server.d.cts.map +1 -1
  13. package/dist/headless-server.d.mts +2 -0
  14. package/dist/headless-server.d.mts.map +1 -1
  15. package/dist/headless-server.mjs +64 -7
  16. package/dist/headless-server.mjs.map +1 -1
  17. package/dist/headless-session-host.d.cts +5 -0
  18. package/dist/headless-session-host.d.cts.map +1 -1
  19. package/dist/headless-session-host.d.mts +5 -0
  20. package/dist/headless-session-host.d.mts.map +1 -1
  21. package/dist/headless.d.cts +2 -2
  22. package/dist/headless.d.mts +2 -2
  23. package/dist/history-import.cjs +1 -1
  24. package/dist/history-import.mjs +1 -1
  25. package/dist/history-ownership.cjs +1 -1
  26. package/dist/history-ownership.mjs +1 -1
  27. package/dist/history.cjs +2 -0
  28. package/dist/history.d.cts +2 -1
  29. package/dist/history.d.mts +2 -1
  30. package/dist/history.mjs +2 -1
  31. package/dist/hub-channel.d.cts +2 -2
  32. package/dist/hub-channel.d.mts +2 -2
  33. package/dist/jsonl-session-repo.cjs +1 -1
  34. package/dist/jsonl-session-repo.mjs +1 -1
  35. package/dist/mcp-facet.cjs +11 -0
  36. package/dist/mcp-facet.d.cts +4 -0
  37. package/dist/mcp-facet.d.mts +4 -0
  38. package/dist/mcp-facet.mjs +4 -0
  39. package/dist/mcp-schema-cost.cjs +2 -2
  40. package/dist/mcp-schema-cost.mjs +1 -1
  41. package/dist/package-api-server.cjs +13 -1
  42. package/dist/package-api-server.cjs.map +1 -1
  43. package/dist/package-api-server.mjs +13 -1
  44. package/dist/package-api-server.mjs.map +1 -1
  45. package/dist/remote-runtime.cjs +11 -3
  46. package/dist/remote-runtime.cjs.map +1 -1
  47. package/dist/remote-runtime.d.cts.map +1 -1
  48. package/dist/remote-runtime.d.mts.map +1 -1
  49. package/dist/remote-runtime.mjs +12 -4
  50. package/dist/remote-runtime.mjs.map +1 -1
  51. package/dist/runtime-cordis-host.cjs.map +1 -1
  52. package/dist/runtime-cordis-host.d.cts +1 -1
  53. package/dist/runtime-cordis-host.d.cts.map +1 -1
  54. package/dist/runtime-cordis-host.d.mts +1 -1
  55. package/dist/runtime-cordis-host.d.mts.map +1 -1
  56. package/dist/runtime-cordis-host.mjs.map +1 -1
  57. package/dist/runtime-identity.cjs +1 -1
  58. package/dist/runtime-identity.mjs +1 -1
  59. package/dist/runtime-json.cjs +1 -1
  60. package/dist/runtime-json.mjs +1 -1
  61. package/dist/runtime-pi-extension.cjs +6 -2
  62. package/dist/runtime-pi-extension.cjs.map +1 -1
  63. package/dist/runtime-pi-extension.d.cts.map +1 -1
  64. package/dist/runtime-pi-extension.d.mts.map +1 -1
  65. package/dist/runtime-pi-extension.mjs +6 -2
  66. package/dist/runtime-pi-extension.mjs.map +1 -1
  67. package/dist/schemas/builtinApiContracts.cjs +1 -1
  68. package/dist/schemas/builtinApiContracts.cjs.map +1 -1
  69. package/dist/schemas/builtinApiContracts.mjs +1 -1
  70. package/dist/schemas/builtinApiContracts.mjs.map +1 -1
  71. package/dist/schemas/headless.cjs.map +1 -1
  72. package/dist/schemas/headless.d.cts +10 -0
  73. package/dist/schemas/headless.d.cts.map +1 -1
  74. package/dist/schemas/headless.d.mts +10 -0
  75. package/dist/schemas/headless.d.mts.map +1 -1
  76. package/dist/schemas/headless.mjs.map +1 -1
  77. package/dist/schemas/httpApiContracts.cjs +27 -3
  78. package/dist/schemas/httpApiContracts.cjs.map +1 -1
  79. package/dist/schemas/httpApiContracts.mjs +25 -4
  80. package/dist/schemas/httpApiContracts.mjs.map +1 -1
  81. package/dist/schemas/hubChannel.d.cts +12 -0
  82. package/dist/schemas/hubChannel.d.cts.map +1 -1
  83. package/dist/schemas/hubChannel.d.mts +12 -0
  84. package/dist/schemas/hubChannel.d.mts.map +1 -1
  85. package/dist/schemas/mcpBundle.cjs +67 -0
  86. package/dist/schemas/mcpBundle.cjs.map +1 -0
  87. package/dist/schemas/mcpBundle.d.cts +21 -0
  88. package/dist/schemas/mcpBundle.d.cts.map +1 -0
  89. package/dist/schemas/mcpBundle.d.mts +21 -0
  90. package/dist/schemas/mcpBundle.d.mts.map +1 -0
  91. package/dist/schemas/mcpBundle.mjs +65 -0
  92. package/dist/schemas/mcpBundle.mjs.map +1 -0
  93. package/dist/schemas/mcpFacet.cjs +19 -0
  94. package/dist/schemas/mcpFacet.cjs.map +1 -0
  95. package/dist/schemas/mcpFacet.d.cts +66 -0
  96. package/dist/schemas/mcpFacet.d.cts.map +1 -0
  97. package/dist/schemas/mcpFacet.d.mts +66 -0
  98. package/dist/schemas/mcpFacet.d.mts.map +1 -0
  99. package/dist/schemas/mcpFacet.mjs +17 -0
  100. package/dist/schemas/mcpFacet.mjs.map +1 -0
  101. package/dist/schemas/packageApi.cjs.map +1 -1
  102. package/dist/schemas/packageApi.d.cts +3 -1
  103. package/dist/schemas/packageApi.d.cts.map +1 -1
  104. package/dist/schemas/packageApi.d.mts +3 -1
  105. package/dist/schemas/packageApi.d.mts.map +1 -1
  106. package/dist/schemas/packageApi.mjs.map +1 -1
  107. package/dist/schemas/sessionApiContracts.cjs +3 -0
  108. package/dist/schemas/sessionApiContracts.cjs.map +1 -1
  109. package/dist/schemas/sessionApiContracts.d.cts.map +1 -1
  110. package/dist/schemas/sessionApiContracts.d.mts.map +1 -1
  111. package/dist/schemas/sessionApiContracts.mjs +3 -0
  112. package/dist/schemas/sessionApiContracts.mjs.map +1 -1
  113. package/dist/server/headlessProtocol.cjs +26 -0
  114. package/dist/server/headlessProtocol.cjs.map +1 -1
  115. package/dist/server/headlessProtocol.mjs +26 -0
  116. package/dist/server/headlessProtocol.mjs.map +1 -1
  117. package/dist/server/mcpBundleLoader.cjs +56 -0
  118. package/dist/server/mcpBundleLoader.cjs.map +1 -0
  119. package/dist/server/mcpBundleLoader.d.cts +27 -0
  120. package/dist/server/mcpBundleLoader.d.cts.map +1 -0
  121. package/dist/server/mcpBundleLoader.d.mts +27 -0
  122. package/dist/server/mcpBundleLoader.d.mts.map +1 -0
  123. package/dist/server/mcpBundleLoader.mjs +52 -0
  124. package/dist/server/mcpBundleLoader.mjs.map +1 -0
  125. package/dist/server/sessionMcpHandler.cjs +75 -16
  126. package/dist/server/sessionMcpHandler.cjs.map +1 -1
  127. package/dist/server/sessionMcpHandler.d.cts.map +1 -1
  128. package/dist/server/sessionMcpHandler.d.mts.map +1 -1
  129. package/dist/server/sessionMcpHandler.mjs +76 -17
  130. package/dist/server/sessionMcpHandler.mjs.map +1 -1
  131. package/dist/server/sessionMcpRoutes.cjs +14 -7
  132. package/dist/server/sessionMcpRoutes.cjs.map +1 -1
  133. package/dist/server/sessionMcpRoutes.mjs +14 -7
  134. package/dist/server/sessionMcpRoutes.mjs.map +1 -1
  135. package/dist/services/historyCreationFileSystem/index.cjs +3 -2
  136. package/dist/services/historyCreationFileSystem/index.cjs.map +1 -1
  137. package/dist/services/historyCreationFileSystem/index.mjs +3 -2
  138. package/dist/services/historyCreationFileSystem/index.mjs.map +1 -1
  139. package/dist/services/piExtensionHost/index.cjs +19 -7
  140. package/dist/services/piExtensionHost/index.cjs.map +1 -1
  141. package/dist/services/piExtensionHost/index.mjs +19 -7
  142. package/dist/services/piExtensionHost/index.mjs.map +1 -1
  143. package/dist/services/pluginLifecycle/index.cjs +1 -1
  144. package/dist/services/pluginLifecycle/index.cjs.map +1 -1
  145. package/dist/services/pluginLifecycle/index.mjs +1 -1
  146. package/dist/services/pluginLifecycle/index.mjs.map +1 -1
  147. package/dist/services/webauthnPolicy/index.cjs +1 -1
  148. package/dist/services/webauthnPolicy/index.cjs.map +1 -1
  149. package/dist/services/webauthnPolicy/index.mjs +1 -1
  150. package/dist/services/webauthnPolicy/index.mjs.map +1 -1
  151. package/dist/services/workspaceRegistry/index.cjs +90 -0
  152. package/dist/services/workspaceRegistry/index.cjs.map +1 -0
  153. package/dist/services/workspaceRegistry/index.d.cts +18 -0
  154. package/dist/services/workspaceRegistry/index.d.cts.map +1 -0
  155. package/dist/services/workspaceRegistry/index.d.mts +18 -0
  156. package/dist/services/workspaceRegistry/index.d.mts.map +1 -0
  157. package/dist/services/workspaceRegistry/index.mjs +87 -0
  158. package/dist/services/workspaceRegistry/index.mjs.map +1 -0
  159. package/dist/sqlite-session-storage.cjs +1 -1
  160. package/dist/sqlite-session-storage.mjs +1 -1
  161. package/dist/sync-location.cjs +2 -2
  162. package/dist/sync-location.mjs +1 -1
  163. package/dist/sync-registration.cjs +19 -5
  164. package/dist/sync-registration.cjs.map +1 -1
  165. package/dist/sync-registration.d.cts +3 -0
  166. package/dist/sync-registration.d.cts.map +1 -1
  167. package/dist/sync-registration.d.mts +3 -0
  168. package/dist/sync-registration.d.mts.map +1 -1
  169. package/dist/sync-registration.mjs +18 -4
  170. package/dist/sync-registration.mjs.map +1 -1
  171. package/dist/systems/child/adapters/headlessChildSessionService.cjs +1 -1
  172. package/dist/systems/child/adapters/headlessChildSessionService.mjs +1 -1
  173. package/dist/systems/main/adapters/headlessHost.cjs +3 -1
  174. package/dist/systems/main/adapters/headlessHost.cjs.map +1 -1
  175. package/dist/systems/main/adapters/headlessHost.d.cts.map +1 -1
  176. package/dist/systems/main/adapters/headlessHost.d.mts.map +1 -1
  177. package/dist/systems/main/adapters/headlessHost.mjs +3 -1
  178. package/dist/systems/main/adapters/headlessHost.mjs.map +1 -1
  179. package/dist/systems/main/adapters/headlessSessionHost.cjs +248 -89
  180. package/dist/systems/main/adapters/headlessSessionHost.cjs.map +1 -1
  181. package/dist/systems/main/adapters/headlessSessionHost.d.cts.map +1 -1
  182. package/dist/systems/main/adapters/headlessSessionHost.d.mts.map +1 -1
  183. package/dist/systems/main/adapters/headlessSessionHost.mjs +247 -89
  184. package/dist/systems/main/adapters/headlessSessionHost.mjs.map +1 -1
  185. package/dist/terminal-pi-child-session-service.cjs +1 -1
  186. package/dist/terminal-pi-child-session-service.mjs +1 -1
  187. package/dist/testing/pi/testHost.cjs +6 -0
  188. package/dist/testing/pi/testHost.cjs.map +1 -1
  189. package/dist/testing/pi/testHost.mjs +6 -0
  190. package/dist/testing/pi/testHost.mjs.map +1 -1
  191. package/dist/types/server/sessionToolSurface.d.cts +5 -2
  192. package/dist/types/server/sessionToolSurface.d.cts.map +1 -1
  193. package/dist/types/server/sessionToolSurface.d.mts +5 -2
  194. package/dist/types/server/sessionToolSurface.d.mts.map +1 -1
  195. package/dist/v3-export.cjs +1 -1
  196. package/dist/v3-export.mjs +1 -1
  197. package/dist/web/types/webPlugin.d.cts +8 -0
  198. package/dist/web/types/webPlugin.d.cts.map +1 -1
  199. package/dist/web/types/webPlugin.d.mts +8 -0
  200. package/dist/web/types/webPlugin.d.mts.map +1 -1
  201. package/dist/web-compositions.cjs +2 -2
  202. package/dist/web-compositions.mjs +1 -1
  203. package/dist/web.d.cts +2 -2
  204. package/dist/web.d.mts +2 -2
  205. package/package.json +19 -14
@@ -1 +1 @@
1
- {"version":3,"file":"remote-runtime.mjs","names":[],"sources":["../src/server/remoteRuntime.ts"],"sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';\nimport path from 'node:path';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\n\nimport { Hono } from 'hono';\nimport { getCookie } from 'hono/cookie';\nimport WebSocket, { WebSocketServer, type RawData } from 'ws';\n\nimport { DEVICE_COOKIE, REMOTE_CHANNEL_ROUTE, REMOTE_HTTP_ROUTE, STEP_UP_HEADER } from '../constants/remote';\nimport { parseDoomSocketPath } from '../schemas/packageApi';\nimport { createRemoteAccess, type RemoteAccess } from '../services/remoteAccess';\nimport { createRemoteAccessStore } from '../services/remoteAccessStore';\nimport {\n isPublicPairingRoute,\n originVerdict,\n type GuardListener,\n localOriginPolicy,\n} from '../services/remoteGuardPolicy';\nimport { createTunnelLauncher, reapStaleTunnel } from '../services/tunnelProcess';\nimport { stepUpActionFor } from '../services/webauthnPolicy';\nimport { type TunnelLauncher } from '../types/remote';\nimport { registerRemoteRoutes } from './remoteRoutes';\nimport { isPublicSessionMcpRoute, isSessionMcpHostRoute } from './sessionMcpRoutes';\n\nconst MAX_REQUEST_BYTES = 8 * 1024 * 1024;\nconst TUNNEL_HEADERS_TIMEOUT_MS = 5_000;\nconst TUNNEL_REQUEST_TIMEOUT_MS = 10_000;\nconst TUNNEL_CONNECTION_LIMIT = 64;\nconst TUNNEL_REQUESTS_PER_SOCKET = 100;\nconst SEALED_HTTP_VERSION = 1;\nconst SEALED_HTTP_METHODS = new Set(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']);\nconst FORBIDDEN_HEADERS = new Set([\n 'authorization',\n 'connection',\n 'content-length',\n 'cookie',\n 'forwarded',\n 'host',\n 'origin',\n 'transfer-encoding',\n 'upgrade',\n 'x-doompi-token',\n 'x-forwarded-for',\n 'x-forwarded-host',\n 'x-forwarded-proto',\n]);\n\ninterface SealedHttpRequest {\n v: number;\n method: string;\n target: string;\n headers: Array<[string, string]>;\n body?: string;\n}\n\nfunction sealedRequest(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 const parsed = new URL(request.target, 'http://doompi.local');\n if (parsed.pathname === REMOTE_HTTP_ROUTE || parsed.pathname === REMOTE_CHANNEL_ROUTE) return undefined;\n return request as SealedHttpRequest;\n}\n\nfunction decodedBody(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}=)?$/u.test(value)) return undefined;\n const body = Buffer.from(value, 'base64');\n return body.toString('base64') === value ? body : undefined;\n}\n\ninterface ListenerBindings {\n listener: GuardListener;\n incoming?: IncomingMessage;\n sealedDeviceId?: string;\n}\n\nexport interface RemoteRuntimeOptions {\n homeDirectory: string;\n registrationToken: string;\n bundleTrust(): { publicKey: string; revision: number } | undefined;\n onNotice(message: string): void;\n /** The listener to which a sealed, authenticated request is forwarded. */\n forward(request: Request): Promise<Response>;\n connectProtocol(pathname: string): WebSocket;\n launchTunnel?: TunnelLauncher;\n}\n\nexport interface RemoteRuntime {\n readonly remote: RemoteAccess;\n fetchLocal(request: Request): Promise<Response>;\n close(): Promise<void>;\n}\n\nasync function readBody(request: IncomingMessage): Promise<Buffer> {\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const chunk of request) {\n const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n size += bytes.byteLength;\n if (size > MAX_REQUEST_BYTES) throw new Error('The tunnel request body is too large.');\n chunks.push(bytes);\n }\n return Buffer.concat(chunks);\n}\n\nasync function writeResponse(response: ServerResponse, result: Response): Promise<void> {\n response.writeHead(result.status, Object.fromEntries(result.headers.entries()));\n if (result.body === null) {\n response.end();\n return;\n }\n await pipeline(Readable.fromWeb(result.body as import('node:stream/web').ReadableStream), response);\n}\n\n/** One global control plane and a second socket dedicated to public tunnel traffic. */\nexport function createRemoteRuntime(options: RemoteRuntimeOptions): RemoteRuntime {\n const store = createRemoteAccessStore({\n stateDir: path.join(options.homeDirectory, '.doompi', 'web'),\n onNotice: (message) => options.onNotice(message),\n });\n reapStaleTunnel(store.directory, (message) => options.onNotice(message));\n const app = new Hono<{ Bindings: ListenerBindings }>();\n let remote: RemoteAccess;\n let frontendOrigin: string | undefined;\n app.use('*', async (context, next) => {\n if (context.env.listener === 'local') return next();\n const path = context.req.path;\n const publicSessionMcp = isPublicSessionMcpRoute(context.req.method, path);\n const verdict = originVerdict({\n listener: 'tunnel',\n // Exact OAuth and MCP routes authenticate at the application layer. Treat\n // an absent Origin like a server-to-server read while retaining Host and\n // supplied-Origin validation.\n method: publicSessionMcp ? 'GET' : context.req.method,\n isUpgrade: false,\n origin: context.req.header('origin'),\n host: context.req.header('host'),\n local: localOriginPolicy(0),\n tunnel: remote.tunnelPolicy(),\n });\n if (verdict !== 'allow') return context.json({ error: `Tunnel request refused: ${verdict}.` }, 403);\n if (isPublicPairingRoute(context.req.method, path) || publicSessionMcp) return next();\n const device = remote.authorize(getCookie(context, DEVICE_COOKIE, 'host'));\n if (device === undefined) return context.json({ error: 'This device is not paired.' }, 401);\n if (context.env.sealedDeviceId === device) return next();\n if (context.req.method === 'POST' && (path === REMOTE_CHANNEL_ROUTE || path === REMOTE_HTTP_ROUTE)) return next();\n if ((context.req.method === 'GET' || context.req.method === 'HEAD') && !path.startsWith('/api/')) return next();\n return context.json({ error: 'Remote HTTP requests must use the sealed gateway.' }, 401);\n });\n\n remote = createRemoteAccess({\n store,\n launchTunnel:\n options.launchTunnel ??\n createTunnelLauncher({\n stateDir: store.directory,\n onNotice: (message) => options.onNotice(message),\n onExit: (message) => {\n options.onNotice(`remote access: ${message}`);\n void remote.disable();\n },\n }),\n bindListener: async () => {\n const webSockets = new WebSocketServer({ noServer: true, maxPayload: MAX_REQUEST_BYTES });\n const server = createServer((incoming, outgoing) => {\n void (async () => {\n const abort = new AbortController();\n const disconnected = (): void => abort.abort();\n incoming.once('aborted', disconnected);\n outgoing.once('close', disconnected);\n try {\n const url = new URL(incoming.url ?? '/', `http://${incoming.headers.host ?? 'localhost'}`);\n const headers = new Headers();\n for (const [name, value] of Object.entries(incoming.headers))\n if (value !== undefined) headers.set(name, Array.isArray(value) ? value.join(', ') : value);\n const body = incoming.method === 'GET' || incoming.method === 'HEAD' ? undefined : await readBody(incoming);\n const request = new Request(url, {\n method: incoming.method,\n headers,\n signal: abort.signal,\n ...(body === undefined ? {} : { body }),\n });\n await writeResponse(outgoing, await app.fetch(request, { listener: 'tunnel', incoming }));\n } finally {\n incoming.off('aborted', disconnected);\n outgoing.off('close', disconnected);\n }\n })().catch((error: unknown) => {\n options.onNotice(`tunnel request failed: ${String(error)}`);\n if (!outgoing.headersSent) {\n outgoing.writeHead(500, { 'content-type': 'application/json' });\n outgoing.end(JSON.stringify({ error: 'The tunnel request failed.' }));\n } else outgoing.destroy();\n });\n });\n server.on('upgrade', (incoming, socket, head) => {\n const url = new URL(incoming.url ?? '/', `http://${incoming.headers.host ?? 'localhost'}`);\n const verdict = originVerdict({\n listener: 'tunnel',\n method: incoming.method ?? 'GET',\n isUpgrade: true,\n origin: incoming.headers.origin,\n host: incoming.headers.host,\n local: localOriginPolicy(0),\n tunnel: remote.tunnelPolicy(),\n });\n const cookies = new Map(\n (incoming.headers.cookie ?? '').split(';').map((part) => {\n const at = part.indexOf('=');\n return [part.slice(0, at).trim(), part.slice(at + 1).trim()];\n }),\n );\n const device = remote.authorize(cookies.get(`__Host-${DEVICE_COOKIE}`));\n const channel = device === undefined ? undefined : remote.channelFor(device, 'protocol');\n if (!parseDoomSocketPath(url.pathname) || verdict !== 'allow' || !device || !channel) {\n socket.write('HTTP/1.1 403 Forbidden\\r\\n\\r\\n');\n socket.destroy();\n return;\n }\n webSockets.handleUpgrade(incoming, socket, head, (client) => {\n let upstream: WebSocket;\n try {\n upstream = options.connectProtocol(url.pathname);\n } catch (error) {\n options.onNotice(`remote protocol could not connect: ${String(error)}`);\n client.close(1011, 'protocol unavailable');\n return;\n }\n const pending: Buffer[] = [];\n let pendingBytes = 0;\n let closed = false;\n const untrack = remote.trackSocket(device, (code, reason) => client.close(code, reason));\n const close = (): void => {\n if (closed) return;\n closed = true;\n untrack();\n client.close();\n upstream.close();\n };\n client.on('message', (raw: RawData) => {\n if (closed) return;\n const bytes = Array.isArray(raw) ? Buffer.concat(raw) : Buffer.from(raw as Buffer);\n let envelope: unknown;\n try {\n envelope = JSON.parse(bytes.toString('utf8')) as unknown;\n } catch {\n close();\n return;\n }\n const opened = channel.open(envelope);\n if (!opened.ok) {\n close();\n return;\n }\n const plain = Buffer.from(opened.plaintext);\n if (upstream.readyState === WebSocket.OPEN) upstream.send(plain);\n else {\n pendingBytes += plain.byteLength;\n if (pendingBytes > MAX_REQUEST_BYTES) close();\n else pending.push(plain);\n }\n });\n upstream.on('open', () => {\n for (const bytes of pending) upstream.send(bytes);\n pending.length = 0;\n pendingBytes = 0;\n });\n upstream.on('message', (raw: RawData) => {\n if (closed) return;\n const plain = Array.isArray(raw) ? Buffer.concat(raw) : Buffer.from(raw as Buffer);\n const sealed = channel.seal(plain);\n if (!sealed.ok) {\n close();\n return;\n }\n client.send(Buffer.from(JSON.stringify(sealed.envelope)));\n });\n client.on('close', close);\n client.on('error', close);\n upstream.on('close', close);\n upstream.on('error', (error) => {\n options.onNotice(`remote protocol failed: ${error.message}`);\n close();\n });\n });\n });\n server.headersTimeout = TUNNEL_HEADERS_TIMEOUT_MS;\n server.requestTimeout = TUNNEL_REQUEST_TIMEOUT_MS;\n server.maxConnections = TUNNEL_CONNECTION_LIMIT;\n server.maxRequestsPerSocket = TUNNEL_REQUESTS_PER_SOCKET;\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(0, '127.0.0.1', resolve);\n });\n const address = server.address();\n if (!address || typeof address === 'string') throw new Error('The tunnel listener has no TCP address.');\n return {\n port: address.port,\n close: async () => {\n for (const client of webSockets.clients) client.terminate();\n webSockets.close();\n server.closeAllConnections();\n await new Promise<void>((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));\n },\n };\n },\n bundleTrust: () => options.bundleTrust(),\n onNotice: (message) => options.onNotice(message),\n });\n registerRemoteRoutes(app as unknown as Hono, {\n remote,\n listenerOf: (context) => (context.env as ListenerBindings).listener,\n });\n app.post('/api/remote/frontend', async (context) => {\n if (context.env.listener !== 'local')\n return context.json({ error: 'This action is only available on the host.' }, 403);\n if (context.req.header('x-doompi-web-registration') !== options.registrationToken)\n return context.json({ error: 'The web presentation server was not authenticated.' }, 403);\n let body: unknown;\n try {\n body = await context.req.json();\n } catch {\n return context.json({ error: 'A loopback web origin is required.' }, 400);\n }\n const origin = typeof body === 'object' && body !== null && 'origin' in body ? body.origin : undefined;\n if (typeof origin !== 'string') return context.json({ error: 'A loopback web origin is required.' }, 400);\n let parsed: URL;\n try {\n parsed = new URL(origin);\n } catch {\n return context.json({ error: 'The web origin is invalid.' }, 400);\n }\n if (\n parsed.protocol !== 'http:' ||\n !['127.0.0.1', 'localhost', '[::1]'].includes(parsed.hostname) ||\n parsed.pathname !== '/' ||\n parsed.search !== '' ||\n parsed.hash !== '' ||\n !parsed.port\n )\n return context.json({ error: 'The web origin must be a loopback HTTP listener.' }, 400);\n frontendOrigin = parsed.origin;\n return context.json({ ok: true });\n });\n app.post(REMOTE_HTTP_ROUTE, async (context) => {\n const device = remote.authorize(getCookie(context, DEVICE_COOKIE, 'host'));\n const channel = device === undefined ? undefined : remote.channelFor(device, 'http');\n if (!device || !channel) return context.json({ error: 'A sealed paired device is required.' }, 401);\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 let decoded: unknown;\n try {\n decoded = JSON.parse(new TextDecoder().decode(opened.plaintext)) as unknown;\n } catch {\n return context.json({ error: 'The sealed HTTP request was malformed.' }, 400);\n }\n const inner = sealedRequest(decoded);\n const body = decodedBody(inner?.body);\n if (!inner || !body) return context.json({ error: 'The sealed HTTP request was invalid.' }, 400);\n const target = new URL(inner.target, 'http://doompi.local');\n const headers = new Headers();\n for (const [name, value] of inner.headers) {\n const normalized = name.toLowerCase();\n if (FORBIDDEN_HEADERS.has(normalized) || normalized.startsWith('sec-')) continue;\n headers.append(name, value);\n }\n const action = stepUpActionFor(inner.method, target.pathname);\n let stepUpDenied = false;\n if (action && remote.stepUpRequired(action)) {\n const assertion = headers.get(STEP_UP_HEADER);\n let credential: unknown;\n try {\n credential = assertion ? JSON.parse(Buffer.from(assertion, 'base64url').toString('utf8')) : undefined;\n } catch {\n credential = undefined;\n }\n const ceremony = credential as { ceremonyId?: unknown; response?: unknown } | undefined;\n if (\n typeof ceremony?.ceremonyId !== 'string' ||\n !(await remote.passkeys().finishStepUp(ceremony.ceremonyId, `device:${device}`, action, ceremony.response))\n )\n stepUpDenied = true;\n }\n headers.delete(STEP_UP_HEADER);\n let response: Response;\n if (stepUpDenied) {\n response = context.json({ error: 'This action needs a fresh passkey gesture.', action }, 401);\n } else if (isSessionMcpHostRoute(target.pathname)) {\n response = context.json({ error: 'Session MCP management is only available on the host.' }, 403);\n } else {\n const internal = new Request(target, {\n method: inner.method,\n headers,\n ...(inner.method === 'GET' || inner.method === 'HEAD' ? {} : { body }),\n });\n if (target.pathname === '/api/remote' || target.pathname.startsWith('/api/remote/')) {\n headers.set('cookie', context.req.header('cookie') ?? '');\n headers.set('host', context.req.header('host') ?? '');\n headers.set('origin', context.req.header('origin') ?? '');\n response = await app.fetch(new Request(internal, { headers }), {\n listener: 'tunnel',\n sealedDeviceId: device,\n });\n } else response = await options.forward(internal);\n }\n const responseBody = Buffer.from(await response.arrayBuffer());\n const sealed = channel.seal(\n new TextEncoder().encode(\n JSON.stringify({\n v: SEALED_HTTP_VERSION,\n status: response.status,\n headers: [...response.headers.entries()],\n body: responseBody.toString('base64'),\n }),\n ),\n );\n return sealed.ok\n ? context.json(sealed.envelope)\n : context.json({ error: `The sealed response failed: ${sealed.failure}.` }, 503);\n });\n app.on(['POST', 'DELETE'], '*', async (context) => {\n if (!isPublicSessionMcpRoute(context.req.method, context.req.path)) return context.notFound();\n return options.forward(context.req.raw);\n });\n app.on(['GET', 'HEAD'], '*', async (context) => {\n if (isPublicSessionMcpRoute(context.req.method, context.req.path)) return options.forward(context.req.raw);\n if (context.req.path.startsWith('/api/')) return context.json({ error: 'Not found.' }, 404);\n if (context.req.path === '/' && remote.authorize(getCookie(context, DEVICE_COOKIE, 'host')) === undefined)\n return context.redirect('/pair');\n if (context.req.path === '/bundle-manifest.json' || context.req.path.startsWith('/bundle-assets/')) {\n const from = new URL(context.req.url);\n return await options.forward(\n new Request(new URL(`${from.pathname}${from.search}`, 'http://doompi.local'), {\n method: context.req.method,\n }),\n );\n }\n if (!frontendOrigin) return context.json({ error: 'The web presentation server is unavailable.' }, 503);\n const from = new URL(context.req.url);\n const target = new URL(`${from.pathname}${from.search}`, frontendOrigin);\n const result = await fetch(target, { method: context.req.method, redirect: 'manual' });\n const headers = new Headers(result.headers);\n headers.delete('set-cookie');\n return new Response(result.body, { status: result.status, headers });\n });\n\n return {\n remote,\n fetchLocal: async (request) => app.fetch(request, { listener: 'local' }),\n close: async () => remote.close(),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"remote-runtime.mjs","names":[],"sources":["../src/server/remoteRuntime.ts"],"sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';\nimport path from 'node:path';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\n\nimport { Hono } from 'hono';\nimport { getCookie } from 'hono/cookie';\nimport WebSocket, { WebSocketServer, type RawData } from 'ws';\n\nimport { DEVICE_COOKIE, REMOTE_CHANNEL_ROUTE, REMOTE_HTTP_ROUTE, STEP_UP_HEADER } from '../constants/remote';\nimport {\n DOOM_API_CALLER_DEVICE_ID_HEADER,\n DOOM_API_CALLER_LOCALITY_HEADER,\n DOOM_API_CALLER_STEP_UP_HEADER,\n parseDoomSocketPath,\n} from '../schemas/packageApi';\nimport { createRemoteAccess, type RemoteAccess } from '../services/remoteAccess';\nimport { createRemoteAccessStore } from '../services/remoteAccessStore';\nimport {\n isPublicPairingRoute,\n originVerdict,\n type GuardListener,\n localOriginPolicy,\n} from '../services/remoteGuardPolicy';\nimport { createTunnelLauncher, reapStaleTunnel } from '../services/tunnelProcess';\nimport { stepUpActionFor } from '../services/webauthnPolicy';\nimport { type TunnelLauncher } from '../types/remote';\nimport { registerRemoteRoutes } from './remoteRoutes';\nimport { isPublicSessionMcpRoute, isSessionMcpHostRoute } from './sessionMcpRoutes';\n\nconst MAX_REQUEST_BYTES = 8 * 1024 * 1024;\nconst TUNNEL_HEADERS_TIMEOUT_MS = 5_000;\nconst TUNNEL_REQUEST_TIMEOUT_MS = 10_000;\nconst TUNNEL_CONNECTION_LIMIT = 64;\nconst TUNNEL_REQUESTS_PER_SOCKET = 100;\nconst SEALED_HTTP_VERSION = 1;\nconst SEALED_HTTP_METHODS = new Set(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']);\n/** HMAC-authenticated peer routes are the only non-device tunnel APIs. */\nconst SESSION_PEER_INBOX_ROUTE = '/api/plugins/session-peer/inbox';\nconst VOICE_PEER_ROUTES = new Set(['/api/plugins/voice/peer', '/api/plugins/voice/peer-ownership']);\nconst FORBIDDEN_HEADERS = new Set([\n 'authorization',\n 'connection',\n 'content-length',\n 'cookie',\n 'forwarded',\n 'host',\n 'origin',\n 'transfer-encoding',\n 'upgrade',\n 'x-doompi-token',\n 'x-forwarded-for',\n 'x-forwarded-host',\n 'x-forwarded-proto',\n]);\n\ninterface SealedHttpRequest {\n v: number;\n method: string;\n target: string;\n headers: Array<[string, string]>;\n body?: string;\n}\n\nfunction sealedRequest(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 const parsed = new URL(request.target, 'http://doompi.local');\n if (parsed.pathname === REMOTE_HTTP_ROUTE || parsed.pathname === REMOTE_CHANNEL_ROUTE) return undefined;\n return request as SealedHttpRequest;\n}\n\nfunction decodedBody(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}=)?$/u.test(value)) return undefined;\n const body = Buffer.from(value, 'base64');\n return body.toString('base64') === value ? body : undefined;\n}\n\ninterface ListenerBindings {\n listener: GuardListener;\n incoming?: IncomingMessage;\n sealedDeviceId?: string;\n}\n\nexport interface RemoteRuntimeOptions {\n homeDirectory: string;\n registrationToken: string;\n bundleTrust(): { publicKey: string; revision: number } | undefined;\n onNotice(message: string): void;\n /** The listener to which a sealed, authenticated request is forwarded. */\n forward(request: Request): Promise<Response>;\n connectProtocol(pathname: string): WebSocket;\n launchTunnel?: TunnelLauncher;\n}\n\nexport interface RemoteRuntime {\n readonly remote: RemoteAccess;\n fetchLocal(request: Request): Promise<Response>;\n close(): Promise<void>;\n}\n\nasync function readBody(request: IncomingMessage): Promise<Buffer> {\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const chunk of request) {\n const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n size += bytes.byteLength;\n if (size > MAX_REQUEST_BYTES) throw new Error('The tunnel request body is too large.');\n chunks.push(bytes);\n }\n return Buffer.concat(chunks);\n}\n\nasync function writeResponse(response: ServerResponse, result: Response): Promise<void> {\n response.writeHead(result.status, Object.fromEntries(result.headers.entries()));\n if (result.body === null) {\n response.end();\n return;\n }\n await pipeline(Readable.fromWeb(result.body as import('node:stream/web').ReadableStream), response);\n}\n\n/** One global control plane and a second socket dedicated to public tunnel traffic. */\nexport function createRemoteRuntime(options: RemoteRuntimeOptions): RemoteRuntime {\n const store = createRemoteAccessStore({\n stateDir: path.join(options.homeDirectory, '.doompi', 'web'),\n onNotice: (message) => options.onNotice(message),\n });\n reapStaleTunnel(store.directory, (message) => options.onNotice(message));\n const app = new Hono<{ Bindings: ListenerBindings }>();\n let remote: RemoteAccess;\n let frontendOrigin: string | undefined;\n app.use('*', async (context, next) => {\n if (context.env.listener === 'local') return next();\n const path = context.req.path;\n const publicSessionMcp = isPublicSessionMcpRoute(context.req.method, path);\n const publicSessionPeer = context.req.method === 'POST' && path === SESSION_PEER_INBOX_ROUTE;\n const publicVoicePeer = context.req.method === 'POST' && VOICE_PEER_ROUTES.has(path);\n const verdict = originVerdict({\n listener: 'tunnel',\n // Exact OAuth, MCP, and HMAC-authenticated peer routes authenticate at the\n // application layer. Treat an absent Origin like a server-to-server read.\n method: publicSessionMcp || publicSessionPeer || publicVoicePeer ? 'GET' : context.req.method,\n isUpgrade: false,\n origin: context.req.header('origin'),\n host: context.req.header('host'),\n local: localOriginPolicy(0),\n tunnel: remote.tunnelPolicy(),\n });\n if (verdict !== 'allow') return context.json({ error: `Tunnel request refused: ${verdict}.` }, 403);\n if (isPublicPairingRoute(context.req.method, path) || publicSessionMcp || publicSessionPeer || publicVoicePeer)\n return next();\n const device = remote.authorize(getCookie(context, DEVICE_COOKIE, 'host'));\n if (device === undefined) return context.json({ error: 'This device is not paired.' }, 401);\n if (context.env.sealedDeviceId === device) return next();\n if (context.req.method === 'POST' && (path === REMOTE_CHANNEL_ROUTE || path === REMOTE_HTTP_ROUTE)) return next();\n if ((context.req.method === 'GET' || context.req.method === 'HEAD') && !path.startsWith('/api/')) return next();\n return context.json({ error: 'Remote HTTP requests must use the sealed gateway.' }, 401);\n });\n\n remote = createRemoteAccess({\n store,\n launchTunnel:\n options.launchTunnel ??\n createTunnelLauncher({\n stateDir: store.directory,\n onNotice: (message) => options.onNotice(message),\n onExit: (message) => {\n options.onNotice(`remote access: ${message}`);\n void remote.disable();\n },\n }),\n bindListener: async () => {\n const webSockets = new WebSocketServer({ noServer: true, maxPayload: MAX_REQUEST_BYTES });\n const server = createServer((incoming, outgoing) => {\n void (async () => {\n const abort = new AbortController();\n const disconnected = (): void => abort.abort();\n incoming.once('aborted', disconnected);\n outgoing.once('close', disconnected);\n try {\n const url = new URL(incoming.url ?? '/', `http://${incoming.headers.host ?? 'localhost'}`);\n const headers = new Headers();\n for (const [name, value] of Object.entries(incoming.headers))\n if (value !== undefined) headers.set(name, Array.isArray(value) ? value.join(', ') : value);\n const body = incoming.method === 'GET' || incoming.method === 'HEAD' ? undefined : await readBody(incoming);\n const request = new Request(url, {\n method: incoming.method,\n headers,\n signal: abort.signal,\n ...(body === undefined ? {} : { body }),\n });\n await writeResponse(outgoing, await app.fetch(request, { listener: 'tunnel', incoming }));\n } finally {\n incoming.off('aborted', disconnected);\n outgoing.off('close', disconnected);\n }\n })().catch((error: unknown) => {\n options.onNotice(`tunnel request failed: ${String(error)}`);\n if (!outgoing.headersSent) {\n outgoing.writeHead(500, { 'content-type': 'application/json' });\n outgoing.end(JSON.stringify({ error: 'The tunnel request failed.' }));\n } else outgoing.destroy();\n });\n });\n server.on('upgrade', (incoming, socket, head) => {\n const url = new URL(incoming.url ?? '/', `http://${incoming.headers.host ?? 'localhost'}`);\n const verdict = originVerdict({\n listener: 'tunnel',\n method: incoming.method ?? 'GET',\n isUpgrade: true,\n origin: incoming.headers.origin,\n host: incoming.headers.host,\n local: localOriginPolicy(0),\n tunnel: remote.tunnelPolicy(),\n });\n const cookies = new Map(\n (incoming.headers.cookie ?? '').split(';').map((part) => {\n const at = part.indexOf('=');\n return [part.slice(0, at).trim(), part.slice(at + 1).trim()];\n }),\n );\n const device = remote.authorize(cookies.get(`__Host-${DEVICE_COOKIE}`));\n const channel = device === undefined ? undefined : remote.channelFor(device, 'protocol');\n if (!parseDoomSocketPath(url.pathname) || verdict !== 'allow' || !device || !channel) {\n socket.write('HTTP/1.1 403 Forbidden\\r\\n\\r\\n');\n socket.destroy();\n return;\n }\n webSockets.handleUpgrade(incoming, socket, head, (client) => {\n let upstream: WebSocket;\n try {\n upstream = options.connectProtocol(url.pathname);\n } catch (error) {\n options.onNotice(`remote protocol could not connect: ${String(error)}`);\n client.close(1011, 'protocol unavailable');\n return;\n }\n const pending: Buffer[] = [];\n let pendingBytes = 0;\n let closed = false;\n const untrack = remote.trackSocket(device, (code, reason) => client.close(code, reason));\n const close = (): void => {\n if (closed) return;\n closed = true;\n untrack();\n client.close();\n upstream.close();\n };\n client.on('message', (raw: RawData) => {\n if (closed) return;\n const bytes = Array.isArray(raw) ? Buffer.concat(raw) : Buffer.from(raw as Buffer);\n let envelope: unknown;\n try {\n envelope = JSON.parse(bytes.toString('utf8')) as unknown;\n } catch {\n close();\n return;\n }\n const opened = channel.open(envelope);\n if (!opened.ok) {\n close();\n return;\n }\n const plain = Buffer.from(opened.plaintext);\n if (upstream.readyState === WebSocket.OPEN) upstream.send(plain);\n else {\n pendingBytes += plain.byteLength;\n if (pendingBytes > MAX_REQUEST_BYTES) close();\n else pending.push(plain);\n }\n });\n upstream.on('open', () => {\n for (const bytes of pending) upstream.send(bytes);\n pending.length = 0;\n pendingBytes = 0;\n });\n upstream.on('message', (raw: RawData) => {\n if (closed) return;\n const plain = Array.isArray(raw) ? Buffer.concat(raw) : Buffer.from(raw as Buffer);\n const sealed = channel.seal(plain);\n if (!sealed.ok) {\n close();\n return;\n }\n client.send(Buffer.from(JSON.stringify(sealed.envelope)));\n });\n client.on('close', close);\n client.on('error', close);\n upstream.on('close', close);\n upstream.on('error', (error) => {\n options.onNotice(`remote protocol failed: ${error.message}`);\n close();\n });\n });\n });\n server.headersTimeout = TUNNEL_HEADERS_TIMEOUT_MS;\n server.requestTimeout = TUNNEL_REQUEST_TIMEOUT_MS;\n server.maxConnections = TUNNEL_CONNECTION_LIMIT;\n server.maxRequestsPerSocket = TUNNEL_REQUESTS_PER_SOCKET;\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(0, '127.0.0.1', resolve);\n });\n const address = server.address();\n if (!address || typeof address === 'string') throw new Error('The tunnel listener has no TCP address.');\n return {\n port: address.port,\n close: async () => {\n for (const client of webSockets.clients) client.terminate();\n webSockets.close();\n server.closeAllConnections();\n await new Promise<void>((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));\n },\n };\n },\n bundleTrust: () => options.bundleTrust(),\n onNotice: (message) => options.onNotice(message),\n });\n registerRemoteRoutes(app as unknown as Hono, {\n remote,\n listenerOf: (context) => (context.env as ListenerBindings).listener,\n });\n app.post('/api/remote/frontend', async (context) => {\n if (context.env.listener !== 'local')\n return context.json({ error: 'This action is only available on the host.' }, 403);\n if (context.req.header('x-doompi-web-registration') !== options.registrationToken)\n return context.json({ error: 'The web presentation server was not authenticated.' }, 403);\n let body: unknown;\n try {\n body = await context.req.json();\n } catch {\n return context.json({ error: 'A loopback web origin is required.' }, 400);\n }\n const origin = typeof body === 'object' && body !== null && 'origin' in body ? body.origin : undefined;\n if (typeof origin !== 'string') return context.json({ error: 'A loopback web origin is required.' }, 400);\n let parsed: URL;\n try {\n parsed = new URL(origin);\n } catch {\n return context.json({ error: 'The web origin is invalid.' }, 400);\n }\n if (\n parsed.protocol !== 'http:' ||\n !['127.0.0.1', 'localhost', '[::1]'].includes(parsed.hostname) ||\n parsed.pathname !== '/' ||\n parsed.search !== '' ||\n parsed.hash !== '' ||\n !parsed.port\n )\n return context.json({ error: 'The web origin must be a loopback HTTP listener.' }, 400);\n frontendOrigin = parsed.origin;\n return context.json({ ok: true });\n });\n app.post(REMOTE_HTTP_ROUTE, async (context) => {\n const device = remote.authorize(getCookie(context, DEVICE_COOKIE, 'host'));\n const channel = device === undefined ? undefined : remote.channelFor(device, 'http');\n if (!device || !channel) return context.json({ error: 'A sealed paired device is required.' }, 401);\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 let decoded: unknown;\n try {\n decoded = JSON.parse(new TextDecoder().decode(opened.plaintext)) as unknown;\n } catch {\n return context.json({ error: 'The sealed HTTP request was malformed.' }, 400);\n }\n const inner = sealedRequest(decoded);\n const body = decodedBody(inner?.body);\n if (!inner || !body) return context.json({ error: 'The sealed HTTP request was invalid.' }, 400);\n const target = new URL(inner.target, 'http://doompi.local');\n const headers = new Headers();\n for (const [name, value] of inner.headers) {\n const normalized = name.toLowerCase();\n if (FORBIDDEN_HEADERS.has(normalized) || normalized.startsWith('sec-')) continue;\n headers.append(name, value);\n }\n const action = stepUpActionFor(inner.method, target.pathname);\n let stepUpDenied = false;\n if (action && remote.stepUpRequired(action)) {\n const assertion = headers.get(STEP_UP_HEADER);\n let credential: unknown;\n try {\n credential = assertion ? JSON.parse(Buffer.from(assertion, 'base64url').toString('utf8')) : undefined;\n } catch {\n credential = undefined;\n }\n const ceremony = credential as { ceremonyId?: unknown; response?: unknown } | undefined;\n if (\n typeof ceremony?.ceremonyId !== 'string' ||\n !(await remote.passkeys().finishStepUp(ceremony.ceremonyId, `device:${device}`, action, ceremony.response))\n )\n stepUpDenied = true;\n }\n headers.delete(STEP_UP_HEADER);\n // Caller stamps are assigned after the sealed request is opened. They never\n // originate from browser-controlled sealed headers.\n headers.set(DOOM_API_CALLER_LOCALITY_HEADER, 'remote');\n headers.set(DOOM_API_CALLER_DEVICE_ID_HEADER, device);\n headers.set(DOOM_API_CALLER_STEP_UP_HEADER, action && remote.stepUpRequired(action) ? 'verified' : 'not-required');\n let response: Response;\n if (stepUpDenied) {\n response = context.json({ error: 'This action needs a fresh passkey gesture.', action }, 401);\n } else if (isSessionMcpHostRoute(target.pathname)) {\n response = context.json({ error: 'Session MCP management is only available on the host.' }, 403);\n } else {\n const internal = new Request(target, {\n method: inner.method,\n headers,\n ...(inner.method === 'GET' || inner.method === 'HEAD' ? {} : { body }),\n });\n if (target.pathname === '/api/remote' || target.pathname.startsWith('/api/remote/')) {\n headers.set('cookie', context.req.header('cookie') ?? '');\n headers.set('host', context.req.header('host') ?? '');\n headers.set('origin', context.req.header('origin') ?? '');\n response = await app.fetch(new Request(internal, { headers }), {\n listener: 'tunnel',\n sealedDeviceId: device,\n });\n } else response = await options.forward(internal);\n }\n const responseBody = Buffer.from(await response.arrayBuffer());\n const sealed = channel.seal(\n new TextEncoder().encode(\n JSON.stringify({\n v: SEALED_HTTP_VERSION,\n status: response.status,\n headers: [...response.headers.entries()],\n body: responseBody.toString('base64'),\n }),\n ),\n );\n return sealed.ok\n ? context.json(sealed.envelope)\n : context.json({ error: `The sealed response failed: ${sealed.failure}.` }, 503);\n });\n app.on(['POST', 'DELETE'], '*', async (context) => {\n if (\n !isPublicSessionMcpRoute(context.req.method, context.req.path) &&\n !(context.req.method === 'POST' && context.req.path === SESSION_PEER_INBOX_ROUTE) &&\n !(context.req.method === 'POST' && VOICE_PEER_ROUTES.has(context.req.path))\n )\n return context.notFound();\n return options.forward(context.req.raw);\n });\n app.on(['GET', 'HEAD'], '*', async (context) => {\n if (isPublicSessionMcpRoute(context.req.method, context.req.path)) return options.forward(context.req.raw);\n if (context.req.path.startsWith('/api/')) return context.json({ error: 'Not found.' }, 404);\n if (context.req.path === '/' && remote.authorize(getCookie(context, DEVICE_COOKIE, 'host')) === undefined)\n return context.redirect('/pair');\n if (context.req.path === '/bundle-manifest.json' || context.req.path.startsWith('/bundle-assets/')) {\n const from = new URL(context.req.url);\n return await options.forward(\n new Request(new URL(`${from.pathname}${from.search}`, 'http://doompi.local'), {\n method: context.req.method,\n }),\n );\n }\n if (!frontendOrigin) return context.json({ error: 'The web presentation server is unavailable.' }, 503);\n const from = new URL(context.req.url);\n const target = new URL(`${from.pathname}${from.search}`, frontendOrigin);\n const result = await fetch(target, { method: context.req.method, redirect: 'manual' });\n const headers = new Headers(result.headers);\n headers.delete('set-cookie');\n return new Response(result.body, { status: result.status, headers });\n });\n\n return {\n remote,\n fetchLocal: async (request) => app.fetch(request, { listener: 'local' }),\n close: async () => remote.close(),\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-cordis-host.cjs","names":["Context","DOOM_TOOL_OVERRIDES_SERVICE","createDoomToolOverridesService","DOOM_CONTEXT_CONTRIBUTIONS_SERVICE","createDoomContextContributionsService","DOOM_TOOL_SURFACE_SERVICE","createDoomToolSurface"],"sources":["../src/pi/cordisHost.ts"],"sourcesContent":["import { Context, type Fiber } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext, SessionStartEvent } from '@earendil-works/pi-coding-agent';\n\nimport { DOOM_CONTEXT_CONTRIBUTIONS_SERVICE } from '../schemas/contextContributions';\nimport { DOOM_TOOL_OVERRIDES_SERVICE } from '../schemas/toolOverrides';\nimport { DOOM_TOOL_SURFACE_SERVICE } from '../schemas/toolSurface';\nimport { createDoomContextContributionsService } from '../services/contextContributions';\nimport { createDoomToolOverridesService } from '../services/toolOverrides';\nimport { createDoomToolSurface } from '../services/toolSurface';\n\nexport const DOOM_CORDIS_HOST_ABI_VERSION = 1 as const;\nexport const DOOM_CORDIS_HOST_QUERY_CHANNEL = 'doom:cordis:host:v1:query';\nexport const DOOM_CORDIS_RUNTIME_SERVICE = 'doom/runtime';\nexport const DOOM_CORDIS_SESSION_SERVICE = 'doom/session';\nconst HOST_PROTOCOL = 'doom.cordis.host';\nconst DEFAULT_HOST_SOURCE = '@agimon-ai/doompi/cordis-host';\n\nexport type DoomCordisHostMode = 'composed' | 'standalone';\n\nexport interface DoomCordisRuntimeService {\n readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n readonly hostId: string;\n readonly generation: string;\n readonly mode: DoomCordisHostMode;\n}\n\nexport interface DoomCordisSessionService {\n readonly sessionId: string;\n readonly generation: string;\n readonly reason: SessionStartEvent['reason'];\n readonly context: ExtensionContext;\n}\n\nexport function requireDoomCordisSession(context: Context): DoomCordisSessionService {\n const session = context.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService | undefined;\n if (!session) throw new Error('Doom Cordis session service is unavailable.');\n return session;\n}\n\nexport interface DoomCordisHostConnection {\n readonly root: Context;\n readonly runtime: DoomCordisRuntimeService;\n /** Releases this consumer's lease. It never disposes a composed host. */\n dispose(): Promise<void>;\n}\n\nexport interface DoomCordisHostController {\n readonly root: Context;\n readonly runtime: DoomCordisRuntimeService;\n /** Recursively disposes the session and application plugin tree. */\n shutdown(): Promise<void>;\n}\n\nexport interface InstallDoomCordisHostOptions {\n readonly mode: DoomCordisHostMode;\n readonly source?: string;\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n 'doom/runtime': DoomCordisRuntimeService;\n 'doom/session': DoomCordisSessionService;\n }\n}\n\ninterface HostQuery {\n readonly protocol: typeof HOST_PROTOCOL;\n readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n readonly kind: 'query';\n readonly requestId: string;\n readonly source: string;\n readonly accept: (response: unknown) => void;\n}\n\ninterface HostResponder {\n readonly protocol: typeof HOST_PROTOCOL;\n readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n readonly hostId: string;\n readonly root: Context;\n readonly runtime: DoomCordisRuntimeService;\n readonly ready: Promise<void>;\n acquire(): DoomCordisHostConnection;\n shutdown(): Promise<void>;\n}\n\nfunction isRecord(value: unknown): value is Record<PropertyKey, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction asHostQuery(value: unknown): HostQuery | undefined {\n if (!isRecord(value)) return undefined;\n if (\n value.protocol !== HOST_PROTOCOL ||\n value.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION ||\n value.kind !== 'query' ||\n typeof value.requestId !== 'string' ||\n value.requestId.length === 0 ||\n typeof value.source !== 'string' ||\n value.source.length === 0 ||\n typeof value.accept !== 'function'\n ) {\n return undefined;\n }\n return value as unknown as HostQuery;\n}\n\nfunction asHostResponder(value: unknown): HostResponder {\n if (!isRecord(value)) throw new Error('The Doom Cordis host returned an invalid discovery response.');\n if (value.protocol !== HOST_PROTOCOL) {\n throw new Error('The Doom Cordis host returned an invalid protocol identifier.');\n }\n if (value.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION) {\n throw new Error(\n `The Doom Cordis host ABI is incompatible: expected ${String(DOOM_CORDIS_HOST_ABI_VERSION)}, received ${String(value.abiVersion)}.`,\n );\n }\n if (typeof value.hostId !== 'string' || value.hostId.length === 0) {\n throw new Error('The Doom Cordis host returned an invalid host identifier.');\n }\n if (!Context.is(value.root)) {\n throw new Error('The Doom Cordis host returned a value that is not a Cordis Context.');\n }\n if (\n !isRecord(value.runtime) ||\n value.runtime.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION ||\n value.runtime.hostId !== value.hostId ||\n typeof value.acquire !== 'function' ||\n typeof value.shutdown !== 'function' ||\n !isRecord(value.ready) ||\n typeof value.ready.then !== 'function'\n ) {\n throw new Error('The Doom Cordis host returned incomplete runtime metadata.');\n }\n return value as unknown as HostResponder;\n}\n\nfunction discoverHosts(pi: Pick<ExtensionAPI, 'events'>, source: string): HostResponder[] {\n const responses: HostResponder[] = [];\n const request: HostQuery = {\n protocol: HOST_PROTOCOL,\n abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n kind: 'query',\n requestId: `${source}:${crypto.randomUUID()}`,\n source,\n accept(response) {\n responses.push(asHostResponder(response));\n },\n };\n pi.events.emit(DOOM_CORDIS_HOST_QUERY_CHANNEL, request);\n return responses;\n}\n\nfunction exactlyOneHost(responses: readonly HostResponder[], source: string): HostResponder | undefined {\n if (responses.length <= 1) return responses[0];\n const hostIds = responses.map(({ hostId }) => hostId).join(', ');\n throw new Error(`Multiple Doom Cordis hosts answered ${source}: ${hostIds}.`);\n}\n\nfunction runtimeProvider(ctx: Context, service: DoomCordisRuntimeService): void {\n ctx.provide(DOOM_CORDIS_RUNTIME_SERVICE, service);\n ctx.provide(DOOM_TOOL_OVERRIDES_SERVICE, createDoomToolOverridesService(service.generation));\n}\n\nfunction sessionProvider(ctx: Context, service: DoomCordisSessionService, pi: ExtensionAPI): void {\n ctx.provide(DOOM_CORDIS_SESSION_SERVICE, service);\n ctx.provide(DOOM_CONTEXT_CONTRIBUTIONS_SERVICE, createDoomContextContributionsService(service.generation));\n ctx.provide(\n DOOM_TOOL_SURFACE_SERVICE,\n createDoomToolSurface({\n generation: service.generation,\n allTools: () => pi.getAllTools().map((tool) => tool.name),\n activeTools: () => pi.getActiveTools(),\n setActiveTools: (names) => pi.setActiveTools(names),\n }),\n );\n}\n\n/**\n * Installs the sole Cordis application root for one Pi extension runner.\n *\n * The versioned EventBus query is intentional: Pi may hand independently\n * loaded factories different ExtensionAPI wrapper objects, while their event\n * buses still address the same runner.\n */\nexport async function installDoomCordisHost(\n pi: ExtensionAPI,\n options: InstallDoomCordisHostOptions,\n): Promise<DoomCordisHostController> {\n const source = options.source ?? DEFAULT_HOST_SOURCE;\n const existing = exactlyOneHost(discoverHosts(pi, source), source);\n if (existing) throw new Error(`A Doom Cordis host is already installed: ${existing.hostId}.`);\n\n // This is the one intentional Context constructor for DoomPi extension code.\n const root = new Context();\n const runtime: DoomCordisRuntimeService = Object.freeze({\n abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n hostId: `doom-cordis:${crypto.randomUUID()}`,\n generation: `doom-runtime:${crypto.randomUUID()}`,\n mode: options.mode,\n });\n const runtimeFiber = root.plugin(runtimeProvider, runtime);\n const ready = runtimeFiber.await().then(() => undefined);\n let sessionFiber: Fiber | undefined;\n let sessionGeneration = 0;\n let sessionQueue = Promise.resolve();\n let leaseCount = 0;\n let shutdownPromise: Promise<void> | undefined;\n let responder: HostResponder;\n let disposeQuerySubscription = (): void => undefined;\n\n const controller: DoomCordisHostController = {\n root,\n runtime,\n shutdown() {\n shutdownPromise ??= (async () => {\n disposeQuerySubscription();\n await ready.catch(() => undefined);\n await sessionQueue.catch(() => undefined);\n await sessionFiber?.dispose();\n sessionFiber = undefined;\n await root.fiber.dispose();\n })();\n return shutdownPromise;\n },\n };\n\n responder = {\n protocol: HOST_PROTOCOL,\n abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n hostId: runtime.hostId,\n root,\n runtime,\n ready,\n acquire() {\n if (shutdownPromise) throw new Error(`The Doom Cordis host is shutting down: ${runtime.hostId}.`);\n leaseCount += 1;\n let released = false;\n return {\n root,\n runtime,\n async dispose() {\n if (released) return;\n released = true;\n leaseCount -= 1;\n if (options.mode === 'standalone' && leaseCount === 0) await controller.shutdown();\n },\n };\n },\n shutdown: () => controller.shutdown(),\n };\n try {\n disposeQuerySubscription = pi.events.on(DOOM_CORDIS_HOST_QUERY_CHANNEL, (value) => {\n const query = asHostQuery(value);\n if (query) query.accept(responder);\n });\n pi.on('session_start', (event, context) => {\n sessionQueue = sessionQueue\n .catch(() => undefined)\n .then(async () => {\n // Pi owns its listener table and can retain this callback across a\n // replacement. A start delivered after finalization belongs to the\n // stale graph and must not revive or fail the next one.\n if (shutdownPromise) return;\n await sessionFiber?.dispose();\n sessionGeneration += 1;\n const service: DoomCordisSessionService = Object.freeze({\n sessionId: context.sessionManager.getSessionId(),\n generation: `${runtime.generation}:${String(sessionGeneration)}`,\n reason: event.reason,\n context,\n });\n sessionFiber = root.plugin((ctx: Context, value: DoomCordisSessionService) => {\n sessionProvider(ctx, value, pi);\n }, service);\n await sessionFiber.await();\n });\n return sessionQueue;\n });\n if (options.mode === 'standalone') {\n pi.on('session_shutdown', () => {\n disposeQuerySubscription();\n });\n }\n await ready;\n } catch (error) {\n await controller.shutdown();\n throw error;\n }\n return controller;\n}\n\n/** Resolves the composed host installed by the canonical runtime. */\nexport async function connectDoomCordisHost(pi: ExtensionAPI, source: string): Promise<DoomCordisHostConnection> {\n const responder = exactlyOneHost(discoverHosts(pi, source), source);\n if (!responder)\n throw new Error('The composed Doom Cordis host is unavailable. Ensure cordisHost is the first extension.');\n await responder.ready;\n return responder.acquire();\n}\n\n/** Ends the host discovered on this runner. Repeated finalization is a no-op. */\nexport async function finalizeDoomCordisHost(pi: Pick<ExtensionAPI, 'events'>, source: string): Promise<void> {\n const responder = exactlyOneHost(discoverHosts(pi, source), source);\n await responder?.shutdown();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runtime-cordis-host.cjs","names":["Context","DOOM_TOOL_OVERRIDES_SERVICE","createDoomToolOverridesService","DOOM_CONTEXT_CONTRIBUTIONS_SERVICE","createDoomContextContributionsService","DOOM_TOOL_SURFACE_SERVICE","createDoomToolSurface"],"sources":["../src/pi/cordisHost.ts"],"sourcesContent":["import { Context, type Fiber } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext, SessionStartEvent } from '@earendil-works/pi-coding-agent';\n\nimport { DOOM_CONTEXT_CONTRIBUTIONS_SERVICE } from '../schemas/contextContributions';\nimport { DOOM_TOOL_OVERRIDES_SERVICE } from '../schemas/toolOverrides';\nimport { DOOM_TOOL_SURFACE_SERVICE } from '../schemas/toolSurface';\nimport { createDoomContextContributionsService } from '../services/contextContributions';\nimport { createDoomToolOverridesService } from '../services/toolOverrides';\nimport { createDoomToolSurface } from '../services/toolSurface';\n\nexport const DOOM_CORDIS_HOST_ABI_VERSION = 1 as const;\nexport const DOOM_CORDIS_HOST_QUERY_CHANNEL = 'doom:cordis:host:v1:query';\nexport const DOOM_CORDIS_RUNTIME_SERVICE = 'doom/runtime';\nexport const DOOM_CORDIS_SESSION_SERVICE = 'doom/session';\nconst HOST_PROTOCOL = 'doom.cordis.host';\nconst DEFAULT_HOST_SOURCE = '@agimon-ai/doompi/cordis-host';\n\nexport type DoomCordisHostMode = 'composed' | 'standalone';\n\nexport interface DoomCordisRuntimeService {\n readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n readonly hostId: string;\n readonly generation: string;\n readonly mode: DoomCordisHostMode;\n}\n\nexport interface DoomCordisSessionService {\n readonly sessionId: string;\n readonly generation: string;\n readonly reason: SessionStartEvent['reason'];\n readonly context: ExtensionContext;\n}\n\nexport function requireDoomCordisSession(context: Context): DoomCordisSessionService {\n const session = context.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService | undefined;\n if (!session) throw new Error('Doom Cordis session service is unavailable.');\n return session;\n}\n\nexport interface DoomCordisHostConnection {\n readonly root: Context;\n readonly runtime: DoomCordisRuntimeService;\n /** Releases this consumer's lease. It never disposes a composed host. */\n dispose(): Promise<void>;\n}\n\nexport interface DoomCordisHostController {\n readonly root: Context;\n readonly runtime: DoomCordisRuntimeService;\n /** Recursively disposes the session and application plugin tree. */\n shutdown(): Promise<void>;\n}\n\nexport interface InstallDoomCordisHostOptions {\n readonly mode: DoomCordisHostMode;\n readonly source?: string;\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n 'doom/runtime': DoomCordisRuntimeService;\n 'doom/session': DoomCordisSessionService;\n }\n}\n\ninterface HostQuery {\n readonly protocol: typeof HOST_PROTOCOL;\n readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n readonly kind: 'query';\n readonly requestId: string;\n readonly source: string;\n readonly accept: (response: unknown) => void;\n}\n\ninterface HostResponder {\n readonly protocol: typeof HOST_PROTOCOL;\n readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n readonly hostId: string;\n readonly root: Context;\n readonly runtime: DoomCordisRuntimeService;\n readonly ready: Promise<void>;\n acquire(): DoomCordisHostConnection;\n shutdown(): Promise<void>;\n}\n\nfunction isRecord(value: unknown): value is Record<PropertyKey, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction asHostQuery(value: unknown): HostQuery | undefined {\n if (!isRecord(value)) return undefined;\n if (\n value.protocol !== HOST_PROTOCOL ||\n value.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION ||\n value.kind !== 'query' ||\n typeof value.requestId !== 'string' ||\n value.requestId.length === 0 ||\n typeof value.source !== 'string' ||\n value.source.length === 0 ||\n typeof value.accept !== 'function'\n ) {\n return undefined;\n }\n return value as unknown as HostQuery;\n}\n\nfunction asHostResponder(value: unknown): HostResponder {\n if (!isRecord(value)) throw new Error('The Doom Cordis host returned an invalid discovery response.');\n if (value.protocol !== HOST_PROTOCOL) {\n throw new Error('The Doom Cordis host returned an invalid protocol identifier.');\n }\n if (value.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION) {\n throw new Error(\n `The Doom Cordis host ABI is incompatible: expected ${String(DOOM_CORDIS_HOST_ABI_VERSION)}, received ${String(value.abiVersion)}.`,\n );\n }\n if (typeof value.hostId !== 'string' || value.hostId.length === 0) {\n throw new Error('The Doom Cordis host returned an invalid host identifier.');\n }\n if (!Context.is(value.root)) {\n throw new Error('The Doom Cordis host returned a value that is not a Cordis Context.');\n }\n if (\n !isRecord(value.runtime) ||\n value.runtime.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION ||\n value.runtime.hostId !== value.hostId ||\n typeof value.acquire !== 'function' ||\n typeof value.shutdown !== 'function' ||\n !isRecord(value.ready) ||\n typeof value.ready.then !== 'function'\n ) {\n throw new Error('The Doom Cordis host returned incomplete runtime metadata.');\n }\n return value as unknown as HostResponder;\n}\n\nfunction discoverHosts(pi: Pick<ExtensionAPI, 'events'>, source: string): HostResponder[] {\n const responses: HostResponder[] = [];\n const request: HostQuery = {\n protocol: HOST_PROTOCOL,\n abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n kind: 'query',\n requestId: `${source}:${crypto.randomUUID()}`,\n source,\n accept(response) {\n responses.push(asHostResponder(response));\n },\n };\n pi.events.emit(DOOM_CORDIS_HOST_QUERY_CHANNEL, request);\n return responses;\n}\n\nfunction exactlyOneHost(responses: readonly HostResponder[], source: string): HostResponder | undefined {\n if (responses.length <= 1) return responses[0];\n const hostIds = responses.map(({ hostId }) => hostId).join(', ');\n throw new Error(`Multiple Doom Cordis hosts answered ${source}: ${hostIds}.`);\n}\n\nfunction runtimeProvider(ctx: Context, service: DoomCordisRuntimeService): void {\n ctx.provide(DOOM_CORDIS_RUNTIME_SERVICE, service);\n ctx.provide(DOOM_TOOL_OVERRIDES_SERVICE, createDoomToolOverridesService(service.generation));\n}\n\nfunction sessionProvider(ctx: Context, service: DoomCordisSessionService, pi: ExtensionAPI): void {\n ctx.provide(DOOM_CORDIS_SESSION_SERVICE, service);\n ctx.provide(DOOM_CONTEXT_CONTRIBUTIONS_SERVICE, createDoomContextContributionsService(service.generation));\n ctx.provide(\n DOOM_TOOL_SURFACE_SERVICE,\n createDoomToolSurface({\n generation: service.generation,\n allTools: () => pi.getAllTools().map((tool) => tool.name),\n activeTools: () => pi.getActiveTools(),\n setActiveTools: (names) => pi.setActiveTools(names),\n }),\n );\n}\n\n/**\n * Installs the sole Cordis application root for one Pi extension runner.\n *\n * The versioned EventBus query is intentional: Pi may hand independently\n * loaded factories different ExtensionAPI wrapper objects, while their event\n * buses still address the same runner.\n */\nexport async function installDoomCordisHost(\n pi: ExtensionAPI,\n options: InstallDoomCordisHostOptions,\n): Promise<DoomCordisHostController> {\n const source = options.source ?? DEFAULT_HOST_SOURCE;\n const existing = exactlyOneHost(discoverHosts(pi, source), source);\n if (existing) throw new Error(`A Doom Cordis host is already installed: ${existing.hostId}.`);\n\n // This is the one intentional Context constructor for DoomPi extension code.\n const root = new Context();\n const runtime: DoomCordisRuntimeService = Object.freeze({\n abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n hostId: `doom-cordis:${crypto.randomUUID()}`,\n generation: `doom-runtime:${crypto.randomUUID()}`,\n mode: options.mode,\n });\n const runtimeFiber = root.plugin(runtimeProvider, runtime);\n const ready = runtimeFiber.await().then(() => undefined);\n let sessionFiber: Fiber | undefined;\n let sessionGeneration = 0;\n let sessionQueue = Promise.resolve();\n let leaseCount = 0;\n let shutdownPromise: Promise<void> | undefined;\n let responder: HostResponder;\n let disposeQuerySubscription = (): void => undefined;\n\n const controller: DoomCordisHostController = {\n root,\n runtime,\n shutdown() {\n shutdownPromise ??= (async () => {\n disposeQuerySubscription();\n await ready.catch(() => undefined);\n await sessionQueue.catch(() => undefined);\n await sessionFiber?.dispose();\n sessionFiber = undefined;\n await root.fiber.dispose();\n })();\n return shutdownPromise;\n },\n };\n\n responder = {\n protocol: HOST_PROTOCOL,\n abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n hostId: runtime.hostId,\n root,\n runtime,\n ready,\n acquire() {\n if (shutdownPromise) throw new Error(`The Doom Cordis host is shutting down: ${runtime.hostId}.`);\n leaseCount += 1;\n let released = false;\n return {\n root,\n runtime,\n async dispose() {\n if (released) return;\n released = true;\n leaseCount -= 1;\n if (options.mode === 'standalone' && leaseCount === 0) await controller.shutdown();\n },\n };\n },\n shutdown: () => controller.shutdown(),\n };\n try {\n disposeQuerySubscription = pi.events.on(DOOM_CORDIS_HOST_QUERY_CHANNEL, (value) => {\n const query = asHostQuery(value);\n if (query) query.accept(responder);\n });\n pi.on('session_start', (event, context) => {\n sessionQueue = sessionQueue\n .catch(() => undefined)\n .then(async () => {\n // Pi owns its listener table and can retain this callback across a\n // replacement. A start delivered after finalization belongs to the\n // stale graph and must not revive or fail the next one.\n if (shutdownPromise) return;\n await sessionFiber?.dispose();\n sessionGeneration += 1;\n const service: DoomCordisSessionService = Object.freeze({\n sessionId: context.sessionManager.getSessionId(),\n generation: `${runtime.generation}:${String(sessionGeneration)}`,\n reason: event.reason,\n context,\n });\n sessionFiber = root.plugin((ctx: Context, value: DoomCordisSessionService) => {\n sessionProvider(ctx, value, pi);\n }, service);\n await sessionFiber.await();\n });\n return sessionQueue;\n });\n if (options.mode === 'standalone') {\n pi.on('session_shutdown', () => {\n disposeQuerySubscription();\n });\n }\n await ready;\n } catch (error) {\n await controller.shutdown();\n throw error;\n }\n return controller;\n}\n\n/** Resolves the composed host installed by the canonical runtime. */\nexport async function connectDoomCordisHost(\n pi: Pick<ExtensionAPI, 'events'>,\n source: string,\n): Promise<DoomCordisHostConnection> {\n const responder = exactlyOneHost(discoverHosts(pi, source), source);\n if (!responder)\n throw new Error('The composed Doom Cordis host is unavailable. Ensure cordisHost is the first extension.');\n await responder.ready;\n return responder.acquire();\n}\n\n/** Ends the host discovered on this runner. Repeated finalization is a no-op. */\nexport async function finalizeDoomCordisHost(pi: Pick<ExtensionAPI, 'events'>, source: string): Promise<void> {\n const responder = exactlyOneHost(discoverHosts(pi, source), source);\n await responder?.shutdown();\n}\n"],"mappings":"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"}
@@ -50,7 +50,7 @@ declare module '@deepseek-ai/cordis' {
50
50
  */
51
51
  export declare function installDoomCordisHost(pi: ExtensionAPI, options: InstallDoomCordisHostOptions): Promise<DoomCordisHostController>;
52
52
  /** Resolves the composed host installed by the canonical runtime. */
53
- export declare function connectDoomCordisHost(pi: ExtensionAPI, source: string): Promise<DoomCordisHostConnection>;
53
+ export declare function connectDoomCordisHost(pi: Pick<ExtensionAPI, 'events'>, source: string): Promise<DoomCordisHostConnection>;
54
54
  /** Ends the host discovered on this runner. Repeated finalization is a no-op. */
55
55
  export declare function finalizeDoomCordisHost(pi: Pick<ExtensionAPI, 'events'>, source: string): Promise<void>;
56
56
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-cordis-host.d.cts","names":[],"sources":["../src/pi/cordisHost.ts"],"mappings":";;;qBAUa;qBACA;qBACA;qBACA;YAID;iBAEK;WACN,mBAAmB;WACnB;WACA;WACA,MAAM;;iBAGA;WACN;WACA;WACA,QAAQ;WACR,SAAS;;wBAGJ,yBAAyB,SAAS,UAAU;iBAM3C;WACN,MAAM;WACN,SAAS;;EAElB,WAAW;;iBAGI;WACN,MAAM;WACN,SAAS;;EAElB,YAAY;;iBAGG;WACN,MAAM;WACN;;;YAIC;IACR,gBAAgB;IAChB,gBAAgB;;;;;;;;;;wBA2HE,sBACpB,IAAI,cACJ,SAAS,+BACR,QAAQ;;wBAyGW,sBAAsB,IAAI,cAAc,iBAAiB,QAAQ;;wBASjE,uBAAuB,IAAI,KAAK,yBAAyB,iBAAiB"}
1
+ {"version":3,"file":"runtime-cordis-host.d.cts","names":[],"sources":["../src/pi/cordisHost.ts"],"mappings":";;;qBAUa;qBACA;qBACA;qBACA;YAID;iBAEK;WACN,mBAAmB;WACnB;WACA;WACA,MAAM;;iBAGA;WACN;WACA;WACA,QAAQ;WACR,SAAS;;wBAGJ,yBAAyB,SAAS,UAAU;iBAM3C;WACN,MAAM;WACN,SAAS;;EAElB,WAAW;;iBAGI;WACN,MAAM;WACN,SAAS;;EAElB,YAAY;;iBAGG;WACN,MAAM;WACN;;;YAIC;IACR,gBAAgB;IAChB,gBAAgB;;;;;;;;;;wBA2HE,sBACpB,IAAI,cACJ,SAAS,+BACR,QAAQ;;wBAyGW,sBACpB,IAAI,KAAK,yBACT,iBACC,QAAQ;;wBASW,uBAAuB,IAAI,KAAK,yBAAyB,iBAAiB"}
@@ -50,7 +50,7 @@ declare module '@deepseek-ai/cordis' {
50
50
  */
51
51
  export declare function installDoomCordisHost(pi: ExtensionAPI, options: InstallDoomCordisHostOptions): Promise<DoomCordisHostController>;
52
52
  /** Resolves the composed host installed by the canonical runtime. */
53
- export declare function connectDoomCordisHost(pi: ExtensionAPI, source: string): Promise<DoomCordisHostConnection>;
53
+ export declare function connectDoomCordisHost(pi: Pick<ExtensionAPI, 'events'>, source: string): Promise<DoomCordisHostConnection>;
54
54
  /** Ends the host discovered on this runner. Repeated finalization is a no-op. */
55
55
  export declare function finalizeDoomCordisHost(pi: Pick<ExtensionAPI, 'events'>, source: string): Promise<void>;
56
56
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-cordis-host.d.mts","names":[],"sources":["../src/pi/cordisHost.ts"],"mappings":";;;qBAUa;qBACA;qBACA;qBACA;YAID;iBAEK;WACN,mBAAmB;WACnB;WACA;WACA,MAAM;;iBAGA;WACN;WACA;WACA,QAAQ;WACR,SAAS;;wBAGJ,yBAAyB,SAAS,UAAU;iBAM3C;WACN,MAAM;WACN,SAAS;;EAElB,WAAW;;iBAGI;WACN,MAAM;WACN,SAAS;;EAElB,YAAY;;iBAGG;WACN,MAAM;WACN;;;YAIC;IACR,gBAAgB;IAChB,gBAAgB;;;;;;;;;;wBA2HE,sBACpB,IAAI,cACJ,SAAS,+BACR,QAAQ;;wBAyGW,sBAAsB,IAAI,cAAc,iBAAiB,QAAQ;;wBASjE,uBAAuB,IAAI,KAAK,yBAAyB,iBAAiB"}
1
+ {"version":3,"file":"runtime-cordis-host.d.mts","names":[],"sources":["../src/pi/cordisHost.ts"],"mappings":";;;qBAUa;qBACA;qBACA;qBACA;YAID;iBAEK;WACN,mBAAmB;WACnB;WACA;WACA,MAAM;;iBAGA;WACN;WACA;WACA,QAAQ;WACR,SAAS;;wBAGJ,yBAAyB,SAAS,UAAU;iBAM3C;WACN,MAAM;WACN,SAAS;;EAElB,WAAW;;iBAGI;WACN,MAAM;WACN,SAAS;;EAElB,YAAY;;iBAGG;WACN,MAAM;WACN;;;YAIC;IACR,gBAAgB;IAChB,gBAAgB;;;;;;;;;;wBA2HE,sBACpB,IAAI,cACJ,SAAS,+BACR,QAAQ;;wBAyGW,sBACpB,IAAI,KAAK,yBACT,iBACC,QAAQ;;wBASW,uBAAuB,IAAI,KAAK,yBAAyB,iBAAiB"}
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-cordis-host.mjs","names":[],"sources":["../src/pi/cordisHost.ts"],"sourcesContent":["import { Context, type Fiber } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext, SessionStartEvent } from '@earendil-works/pi-coding-agent';\n\nimport { DOOM_CONTEXT_CONTRIBUTIONS_SERVICE } from '../schemas/contextContributions';\nimport { DOOM_TOOL_OVERRIDES_SERVICE } from '../schemas/toolOverrides';\nimport { DOOM_TOOL_SURFACE_SERVICE } from '../schemas/toolSurface';\nimport { createDoomContextContributionsService } from '../services/contextContributions';\nimport { createDoomToolOverridesService } from '../services/toolOverrides';\nimport { createDoomToolSurface } from '../services/toolSurface';\n\nexport const DOOM_CORDIS_HOST_ABI_VERSION = 1 as const;\nexport const DOOM_CORDIS_HOST_QUERY_CHANNEL = 'doom:cordis:host:v1:query';\nexport const DOOM_CORDIS_RUNTIME_SERVICE = 'doom/runtime';\nexport const DOOM_CORDIS_SESSION_SERVICE = 'doom/session';\nconst HOST_PROTOCOL = 'doom.cordis.host';\nconst DEFAULT_HOST_SOURCE = '@agimon-ai/doompi/cordis-host';\n\nexport type DoomCordisHostMode = 'composed' | 'standalone';\n\nexport interface DoomCordisRuntimeService {\n readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n readonly hostId: string;\n readonly generation: string;\n readonly mode: DoomCordisHostMode;\n}\n\nexport interface DoomCordisSessionService {\n readonly sessionId: string;\n readonly generation: string;\n readonly reason: SessionStartEvent['reason'];\n readonly context: ExtensionContext;\n}\n\nexport function requireDoomCordisSession(context: Context): DoomCordisSessionService {\n const session = context.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService | undefined;\n if (!session) throw new Error('Doom Cordis session service is unavailable.');\n return session;\n}\n\nexport interface DoomCordisHostConnection {\n readonly root: Context;\n readonly runtime: DoomCordisRuntimeService;\n /** Releases this consumer's lease. It never disposes a composed host. */\n dispose(): Promise<void>;\n}\n\nexport interface DoomCordisHostController {\n readonly root: Context;\n readonly runtime: DoomCordisRuntimeService;\n /** Recursively disposes the session and application plugin tree. */\n shutdown(): Promise<void>;\n}\n\nexport interface InstallDoomCordisHostOptions {\n readonly mode: DoomCordisHostMode;\n readonly source?: string;\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n 'doom/runtime': DoomCordisRuntimeService;\n 'doom/session': DoomCordisSessionService;\n }\n}\n\ninterface HostQuery {\n readonly protocol: typeof HOST_PROTOCOL;\n readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n readonly kind: 'query';\n readonly requestId: string;\n readonly source: string;\n readonly accept: (response: unknown) => void;\n}\n\ninterface HostResponder {\n readonly protocol: typeof HOST_PROTOCOL;\n readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n readonly hostId: string;\n readonly root: Context;\n readonly runtime: DoomCordisRuntimeService;\n readonly ready: Promise<void>;\n acquire(): DoomCordisHostConnection;\n shutdown(): Promise<void>;\n}\n\nfunction isRecord(value: unknown): value is Record<PropertyKey, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction asHostQuery(value: unknown): HostQuery | undefined {\n if (!isRecord(value)) return undefined;\n if (\n value.protocol !== HOST_PROTOCOL ||\n value.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION ||\n value.kind !== 'query' ||\n typeof value.requestId !== 'string' ||\n value.requestId.length === 0 ||\n typeof value.source !== 'string' ||\n value.source.length === 0 ||\n typeof value.accept !== 'function'\n ) {\n return undefined;\n }\n return value as unknown as HostQuery;\n}\n\nfunction asHostResponder(value: unknown): HostResponder {\n if (!isRecord(value)) throw new Error('The Doom Cordis host returned an invalid discovery response.');\n if (value.protocol !== HOST_PROTOCOL) {\n throw new Error('The Doom Cordis host returned an invalid protocol identifier.');\n }\n if (value.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION) {\n throw new Error(\n `The Doom Cordis host ABI is incompatible: expected ${String(DOOM_CORDIS_HOST_ABI_VERSION)}, received ${String(value.abiVersion)}.`,\n );\n }\n if (typeof value.hostId !== 'string' || value.hostId.length === 0) {\n throw new Error('The Doom Cordis host returned an invalid host identifier.');\n }\n if (!Context.is(value.root)) {\n throw new Error('The Doom Cordis host returned a value that is not a Cordis Context.');\n }\n if (\n !isRecord(value.runtime) ||\n value.runtime.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION ||\n value.runtime.hostId !== value.hostId ||\n typeof value.acquire !== 'function' ||\n typeof value.shutdown !== 'function' ||\n !isRecord(value.ready) ||\n typeof value.ready.then !== 'function'\n ) {\n throw new Error('The Doom Cordis host returned incomplete runtime metadata.');\n }\n return value as unknown as HostResponder;\n}\n\nfunction discoverHosts(pi: Pick<ExtensionAPI, 'events'>, source: string): HostResponder[] {\n const responses: HostResponder[] = [];\n const request: HostQuery = {\n protocol: HOST_PROTOCOL,\n abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n kind: 'query',\n requestId: `${source}:${crypto.randomUUID()}`,\n source,\n accept(response) {\n responses.push(asHostResponder(response));\n },\n };\n pi.events.emit(DOOM_CORDIS_HOST_QUERY_CHANNEL, request);\n return responses;\n}\n\nfunction exactlyOneHost(responses: readonly HostResponder[], source: string): HostResponder | undefined {\n if (responses.length <= 1) return responses[0];\n const hostIds = responses.map(({ hostId }) => hostId).join(', ');\n throw new Error(`Multiple Doom Cordis hosts answered ${source}: ${hostIds}.`);\n}\n\nfunction runtimeProvider(ctx: Context, service: DoomCordisRuntimeService): void {\n ctx.provide(DOOM_CORDIS_RUNTIME_SERVICE, service);\n ctx.provide(DOOM_TOOL_OVERRIDES_SERVICE, createDoomToolOverridesService(service.generation));\n}\n\nfunction sessionProvider(ctx: Context, service: DoomCordisSessionService, pi: ExtensionAPI): void {\n ctx.provide(DOOM_CORDIS_SESSION_SERVICE, service);\n ctx.provide(DOOM_CONTEXT_CONTRIBUTIONS_SERVICE, createDoomContextContributionsService(service.generation));\n ctx.provide(\n DOOM_TOOL_SURFACE_SERVICE,\n createDoomToolSurface({\n generation: service.generation,\n allTools: () => pi.getAllTools().map((tool) => tool.name),\n activeTools: () => pi.getActiveTools(),\n setActiveTools: (names) => pi.setActiveTools(names),\n }),\n );\n}\n\n/**\n * Installs the sole Cordis application root for one Pi extension runner.\n *\n * The versioned EventBus query is intentional: Pi may hand independently\n * loaded factories different ExtensionAPI wrapper objects, while their event\n * buses still address the same runner.\n */\nexport async function installDoomCordisHost(\n pi: ExtensionAPI,\n options: InstallDoomCordisHostOptions,\n): Promise<DoomCordisHostController> {\n const source = options.source ?? DEFAULT_HOST_SOURCE;\n const existing = exactlyOneHost(discoverHosts(pi, source), source);\n if (existing) throw new Error(`A Doom Cordis host is already installed: ${existing.hostId}.`);\n\n // This is the one intentional Context constructor for DoomPi extension code.\n const root = new Context();\n const runtime: DoomCordisRuntimeService = Object.freeze({\n abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n hostId: `doom-cordis:${crypto.randomUUID()}`,\n generation: `doom-runtime:${crypto.randomUUID()}`,\n mode: options.mode,\n });\n const runtimeFiber = root.plugin(runtimeProvider, runtime);\n const ready = runtimeFiber.await().then(() => undefined);\n let sessionFiber: Fiber | undefined;\n let sessionGeneration = 0;\n let sessionQueue = Promise.resolve();\n let leaseCount = 0;\n let shutdownPromise: Promise<void> | undefined;\n let responder: HostResponder;\n let disposeQuerySubscription = (): void => undefined;\n\n const controller: DoomCordisHostController = {\n root,\n runtime,\n shutdown() {\n shutdownPromise ??= (async () => {\n disposeQuerySubscription();\n await ready.catch(() => undefined);\n await sessionQueue.catch(() => undefined);\n await sessionFiber?.dispose();\n sessionFiber = undefined;\n await root.fiber.dispose();\n })();\n return shutdownPromise;\n },\n };\n\n responder = {\n protocol: HOST_PROTOCOL,\n abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n hostId: runtime.hostId,\n root,\n runtime,\n ready,\n acquire() {\n if (shutdownPromise) throw new Error(`The Doom Cordis host is shutting down: ${runtime.hostId}.`);\n leaseCount += 1;\n let released = false;\n return {\n root,\n runtime,\n async dispose() {\n if (released) return;\n released = true;\n leaseCount -= 1;\n if (options.mode === 'standalone' && leaseCount === 0) await controller.shutdown();\n },\n };\n },\n shutdown: () => controller.shutdown(),\n };\n try {\n disposeQuerySubscription = pi.events.on(DOOM_CORDIS_HOST_QUERY_CHANNEL, (value) => {\n const query = asHostQuery(value);\n if (query) query.accept(responder);\n });\n pi.on('session_start', (event, context) => {\n sessionQueue = sessionQueue\n .catch(() => undefined)\n .then(async () => {\n // Pi owns its listener table and can retain this callback across a\n // replacement. A start delivered after finalization belongs to the\n // stale graph and must not revive or fail the next one.\n if (shutdownPromise) return;\n await sessionFiber?.dispose();\n sessionGeneration += 1;\n const service: DoomCordisSessionService = Object.freeze({\n sessionId: context.sessionManager.getSessionId(),\n generation: `${runtime.generation}:${String(sessionGeneration)}`,\n reason: event.reason,\n context,\n });\n sessionFiber = root.plugin((ctx: Context, value: DoomCordisSessionService) => {\n sessionProvider(ctx, value, pi);\n }, service);\n await sessionFiber.await();\n });\n return sessionQueue;\n });\n if (options.mode === 'standalone') {\n pi.on('session_shutdown', () => {\n disposeQuerySubscription();\n });\n }\n await ready;\n } catch (error) {\n await controller.shutdown();\n throw error;\n }\n return controller;\n}\n\n/** Resolves the composed host installed by the canonical runtime. */\nexport async function connectDoomCordisHost(pi: ExtensionAPI, source: string): Promise<DoomCordisHostConnection> {\n const responder = exactlyOneHost(discoverHosts(pi, source), source);\n if (!responder)\n throw new Error('The composed Doom Cordis host is unavailable. Ensure cordisHost is the first extension.');\n await responder.ready;\n return responder.acquire();\n}\n\n/** Ends the host discovered on this runner. Repeated finalization is a no-op. */\nexport async function finalizeDoomCordisHost(pi: Pick<ExtensionAPI, 'events'>, source: string): Promise<void> {\n const responder = exactlyOneHost(discoverHosts(pi, source), source);\n await responder?.shutdown();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runtime-cordis-host.mjs","names":[],"sources":["../src/pi/cordisHost.ts"],"sourcesContent":["import { Context, type Fiber } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext, SessionStartEvent } from '@earendil-works/pi-coding-agent';\n\nimport { DOOM_CONTEXT_CONTRIBUTIONS_SERVICE } from '../schemas/contextContributions';\nimport { DOOM_TOOL_OVERRIDES_SERVICE } from '../schemas/toolOverrides';\nimport { DOOM_TOOL_SURFACE_SERVICE } from '../schemas/toolSurface';\nimport { createDoomContextContributionsService } from '../services/contextContributions';\nimport { createDoomToolOverridesService } from '../services/toolOverrides';\nimport { createDoomToolSurface } from '../services/toolSurface';\n\nexport const DOOM_CORDIS_HOST_ABI_VERSION = 1 as const;\nexport const DOOM_CORDIS_HOST_QUERY_CHANNEL = 'doom:cordis:host:v1:query';\nexport const DOOM_CORDIS_RUNTIME_SERVICE = 'doom/runtime';\nexport const DOOM_CORDIS_SESSION_SERVICE = 'doom/session';\nconst HOST_PROTOCOL = 'doom.cordis.host';\nconst DEFAULT_HOST_SOURCE = '@agimon-ai/doompi/cordis-host';\n\nexport type DoomCordisHostMode = 'composed' | 'standalone';\n\nexport interface DoomCordisRuntimeService {\n readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n readonly hostId: string;\n readonly generation: string;\n readonly mode: DoomCordisHostMode;\n}\n\nexport interface DoomCordisSessionService {\n readonly sessionId: string;\n readonly generation: string;\n readonly reason: SessionStartEvent['reason'];\n readonly context: ExtensionContext;\n}\n\nexport function requireDoomCordisSession(context: Context): DoomCordisSessionService {\n const session = context.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService | undefined;\n if (!session) throw new Error('Doom Cordis session service is unavailable.');\n return session;\n}\n\nexport interface DoomCordisHostConnection {\n readonly root: Context;\n readonly runtime: DoomCordisRuntimeService;\n /** Releases this consumer's lease. It never disposes a composed host. */\n dispose(): Promise<void>;\n}\n\nexport interface DoomCordisHostController {\n readonly root: Context;\n readonly runtime: DoomCordisRuntimeService;\n /** Recursively disposes the session and application plugin tree. */\n shutdown(): Promise<void>;\n}\n\nexport interface InstallDoomCordisHostOptions {\n readonly mode: DoomCordisHostMode;\n readonly source?: string;\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n 'doom/runtime': DoomCordisRuntimeService;\n 'doom/session': DoomCordisSessionService;\n }\n}\n\ninterface HostQuery {\n readonly protocol: typeof HOST_PROTOCOL;\n readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n readonly kind: 'query';\n readonly requestId: string;\n readonly source: string;\n readonly accept: (response: unknown) => void;\n}\n\ninterface HostResponder {\n readonly protocol: typeof HOST_PROTOCOL;\n readonly abiVersion: typeof DOOM_CORDIS_HOST_ABI_VERSION;\n readonly hostId: string;\n readonly root: Context;\n readonly runtime: DoomCordisRuntimeService;\n readonly ready: Promise<void>;\n acquire(): DoomCordisHostConnection;\n shutdown(): Promise<void>;\n}\n\nfunction isRecord(value: unknown): value is Record<PropertyKey, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction asHostQuery(value: unknown): HostQuery | undefined {\n if (!isRecord(value)) return undefined;\n if (\n value.protocol !== HOST_PROTOCOL ||\n value.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION ||\n value.kind !== 'query' ||\n typeof value.requestId !== 'string' ||\n value.requestId.length === 0 ||\n typeof value.source !== 'string' ||\n value.source.length === 0 ||\n typeof value.accept !== 'function'\n ) {\n return undefined;\n }\n return value as unknown as HostQuery;\n}\n\nfunction asHostResponder(value: unknown): HostResponder {\n if (!isRecord(value)) throw new Error('The Doom Cordis host returned an invalid discovery response.');\n if (value.protocol !== HOST_PROTOCOL) {\n throw new Error('The Doom Cordis host returned an invalid protocol identifier.');\n }\n if (value.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION) {\n throw new Error(\n `The Doom Cordis host ABI is incompatible: expected ${String(DOOM_CORDIS_HOST_ABI_VERSION)}, received ${String(value.abiVersion)}.`,\n );\n }\n if (typeof value.hostId !== 'string' || value.hostId.length === 0) {\n throw new Error('The Doom Cordis host returned an invalid host identifier.');\n }\n if (!Context.is(value.root)) {\n throw new Error('The Doom Cordis host returned a value that is not a Cordis Context.');\n }\n if (\n !isRecord(value.runtime) ||\n value.runtime.abiVersion !== DOOM_CORDIS_HOST_ABI_VERSION ||\n value.runtime.hostId !== value.hostId ||\n typeof value.acquire !== 'function' ||\n typeof value.shutdown !== 'function' ||\n !isRecord(value.ready) ||\n typeof value.ready.then !== 'function'\n ) {\n throw new Error('The Doom Cordis host returned incomplete runtime metadata.');\n }\n return value as unknown as HostResponder;\n}\n\nfunction discoverHosts(pi: Pick<ExtensionAPI, 'events'>, source: string): HostResponder[] {\n const responses: HostResponder[] = [];\n const request: HostQuery = {\n protocol: HOST_PROTOCOL,\n abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n kind: 'query',\n requestId: `${source}:${crypto.randomUUID()}`,\n source,\n accept(response) {\n responses.push(asHostResponder(response));\n },\n };\n pi.events.emit(DOOM_CORDIS_HOST_QUERY_CHANNEL, request);\n return responses;\n}\n\nfunction exactlyOneHost(responses: readonly HostResponder[], source: string): HostResponder | undefined {\n if (responses.length <= 1) return responses[0];\n const hostIds = responses.map(({ hostId }) => hostId).join(', ');\n throw new Error(`Multiple Doom Cordis hosts answered ${source}: ${hostIds}.`);\n}\n\nfunction runtimeProvider(ctx: Context, service: DoomCordisRuntimeService): void {\n ctx.provide(DOOM_CORDIS_RUNTIME_SERVICE, service);\n ctx.provide(DOOM_TOOL_OVERRIDES_SERVICE, createDoomToolOverridesService(service.generation));\n}\n\nfunction sessionProvider(ctx: Context, service: DoomCordisSessionService, pi: ExtensionAPI): void {\n ctx.provide(DOOM_CORDIS_SESSION_SERVICE, service);\n ctx.provide(DOOM_CONTEXT_CONTRIBUTIONS_SERVICE, createDoomContextContributionsService(service.generation));\n ctx.provide(\n DOOM_TOOL_SURFACE_SERVICE,\n createDoomToolSurface({\n generation: service.generation,\n allTools: () => pi.getAllTools().map((tool) => tool.name),\n activeTools: () => pi.getActiveTools(),\n setActiveTools: (names) => pi.setActiveTools(names),\n }),\n );\n}\n\n/**\n * Installs the sole Cordis application root for one Pi extension runner.\n *\n * The versioned EventBus query is intentional: Pi may hand independently\n * loaded factories different ExtensionAPI wrapper objects, while their event\n * buses still address the same runner.\n */\nexport async function installDoomCordisHost(\n pi: ExtensionAPI,\n options: InstallDoomCordisHostOptions,\n): Promise<DoomCordisHostController> {\n const source = options.source ?? DEFAULT_HOST_SOURCE;\n const existing = exactlyOneHost(discoverHosts(pi, source), source);\n if (existing) throw new Error(`A Doom Cordis host is already installed: ${existing.hostId}.`);\n\n // This is the one intentional Context constructor for DoomPi extension code.\n const root = new Context();\n const runtime: DoomCordisRuntimeService = Object.freeze({\n abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n hostId: `doom-cordis:${crypto.randomUUID()}`,\n generation: `doom-runtime:${crypto.randomUUID()}`,\n mode: options.mode,\n });\n const runtimeFiber = root.plugin(runtimeProvider, runtime);\n const ready = runtimeFiber.await().then(() => undefined);\n let sessionFiber: Fiber | undefined;\n let sessionGeneration = 0;\n let sessionQueue = Promise.resolve();\n let leaseCount = 0;\n let shutdownPromise: Promise<void> | undefined;\n let responder: HostResponder;\n let disposeQuerySubscription = (): void => undefined;\n\n const controller: DoomCordisHostController = {\n root,\n runtime,\n shutdown() {\n shutdownPromise ??= (async () => {\n disposeQuerySubscription();\n await ready.catch(() => undefined);\n await sessionQueue.catch(() => undefined);\n await sessionFiber?.dispose();\n sessionFiber = undefined;\n await root.fiber.dispose();\n })();\n return shutdownPromise;\n },\n };\n\n responder = {\n protocol: HOST_PROTOCOL,\n abiVersion: DOOM_CORDIS_HOST_ABI_VERSION,\n hostId: runtime.hostId,\n root,\n runtime,\n ready,\n acquire() {\n if (shutdownPromise) throw new Error(`The Doom Cordis host is shutting down: ${runtime.hostId}.`);\n leaseCount += 1;\n let released = false;\n return {\n root,\n runtime,\n async dispose() {\n if (released) return;\n released = true;\n leaseCount -= 1;\n if (options.mode === 'standalone' && leaseCount === 0) await controller.shutdown();\n },\n };\n },\n shutdown: () => controller.shutdown(),\n };\n try {\n disposeQuerySubscription = pi.events.on(DOOM_CORDIS_HOST_QUERY_CHANNEL, (value) => {\n const query = asHostQuery(value);\n if (query) query.accept(responder);\n });\n pi.on('session_start', (event, context) => {\n sessionQueue = sessionQueue\n .catch(() => undefined)\n .then(async () => {\n // Pi owns its listener table and can retain this callback across a\n // replacement. A start delivered after finalization belongs to the\n // stale graph and must not revive or fail the next one.\n if (shutdownPromise) return;\n await sessionFiber?.dispose();\n sessionGeneration += 1;\n const service: DoomCordisSessionService = Object.freeze({\n sessionId: context.sessionManager.getSessionId(),\n generation: `${runtime.generation}:${String(sessionGeneration)}`,\n reason: event.reason,\n context,\n });\n sessionFiber = root.plugin((ctx: Context, value: DoomCordisSessionService) => {\n sessionProvider(ctx, value, pi);\n }, service);\n await sessionFiber.await();\n });\n return sessionQueue;\n });\n if (options.mode === 'standalone') {\n pi.on('session_shutdown', () => {\n disposeQuerySubscription();\n });\n }\n await ready;\n } catch (error) {\n await controller.shutdown();\n throw error;\n }\n return controller;\n}\n\n/** Resolves the composed host installed by the canonical runtime. */\nexport async function connectDoomCordisHost(\n pi: Pick<ExtensionAPI, 'events'>,\n source: string,\n): Promise<DoomCordisHostConnection> {\n const responder = exactlyOneHost(discoverHosts(pi, source), source);\n if (!responder)\n throw new Error('The composed Doom Cordis host is unavailable. Ensure cordisHost is the first extension.');\n await responder.ready;\n return responder.acquire();\n}\n\n/** Ends the host discovered on this runner. Repeated finalization is a no-op. */\nexport async function finalizeDoomCordisHost(pi: Pick<ExtensionAPI, 'events'>, source: string): Promise<void> {\n const responder = exactlyOneHost(discoverHosts(pi, source), source);\n await responder?.shutdown();\n}\n"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
3
+ let node_crypto = require("node:crypto");
3
4
  let node_fs = require("node:fs");
4
5
  node_fs = require_runtime.__toESM(node_fs, 1);
5
6
  let node_path = require("node:path");
6
7
  node_path = require_runtime.__toESM(node_path, 1);
7
- let node_crypto = require("node:crypto");
8
8
  //#region src/services/runtimeIdentity/index.ts
9
9
  /** Canonical on-disk identity used for activation deduplication and fingerprints. */
10
10
  function canonicalModulePath(target) {
@@ -1,6 +1,6 @@
1
+ import { createHash } from "node:crypto";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
- import { createHash } from "node:crypto";
4
4
  //#region src/services/runtimeIdentity/index.ts
5
5
  /** Canonical on-disk identity used for activation deduplication and fingerprints. */
6
6
  function canonicalModulePath(target) {
@@ -1,10 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
3
+ let node_crypto = require("node:crypto");
3
4
  let node_fs = require("node:fs");
4
5
  node_fs = require_runtime.__toESM(node_fs, 1);
5
6
  let node_path = require("node:path");
6
7
  node_path = require_runtime.__toESM(node_path, 1);
7
- let node_crypto = require("node:crypto");
8
8
  //#region src/services/json/index.ts
9
9
  const ATOMIC_FILE_SUFFIX = ".tmp";
10
10
  function isRecord(value) {
@@ -1,6 +1,6 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
- import { randomUUID } from "node:crypto";
4
4
  //#region src/services/json/index.ts
5
5
  const ATOMIC_FILE_SUFFIX = ".tmp";
6
6
  function isRecord(value) {
@@ -176,7 +176,7 @@ function definePiExtension(definition, factory) {
176
176
  });
177
177
  }
178
178
  const on = pi.on.bind(pi);
179
- for (const [event, handler] of Object.entries(contributions.events ?? {})) on(event, handler);
179
+ for (const [event, handler] of Object.entries(contributions.events ?? {})) lifecycle.own(on(event, handler));
180
180
  for (const item of contributions.providers ?? []) pi.registerProvider(...item);
181
181
  for (const item of contributions.messageRenderers ?? []) pi.registerMessageRenderer(...item);
182
182
  for (const item of contributions.entryRenderers ?? []) pi.registerEntryRenderer(...item);
@@ -216,8 +216,11 @@ function definePiExtension(definition, factory) {
216
216
  await install(context, pi, options, connection.runtime);
217
217
  });
218
218
  let completion;
219
+ let unsubscribeShutdown;
219
220
  const dispose = () => completion ??= (async () => {
220
221
  try {
222
+ unsubscribeShutdown?.();
223
+ unsubscribeShutdown = void 0;
221
224
  await fiber.dispose();
222
225
  } finally {
223
226
  await connection.dispose();
@@ -225,7 +228,8 @@ function definePiExtension(definition, factory) {
225
228
  })();
226
229
  try {
227
230
  await fiber;
228
- pi.on("session_shutdown", dispose);
231
+ const shutdownRegistration = pi.on("session_shutdown", dispose);
232
+ if (typeof shutdownRegistration === "function") unsubscribeShutdown = shutdownRegistration;
229
233
  } catch (error) {
230
234
  try {
231
235
  await dispose();
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-pi-extension.cjs","names":["createPluginLifecycle","DOOM_HELP_SERVICE","requireDoomHelpService","DOOM_TOOL_SURFACE_SERVICE","requireDoomToolSurface","DOOM_TOOL_OVERRIDES_SERVICE","requireDoomToolOverrides","connectDoomCordisHost"],"sources":["../src/extensions/piExtension.ts"],"sourcesContent":["import type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ToolDefinition } from '@earendil-works/pi-coding-agent';\nimport type { TSchema } from 'typebox';\n\nimport { connectDoomCordisHost, type DoomCordisRuntimeService } from '../pi/cordisHost';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService, type DoomHelpService } from '../schemas/help';\nimport type { PiToolDeclaration } from '../schemas/piTool';\nimport type { DoomPluginTool, DoomPluginCommand } from '../schemas/pluginContributions';\nimport {\n DOOM_TOOL_OVERRIDES_SERVICE,\n requireDoomToolOverrides,\n type DoomToolOverrideClaim,\n} from '../schemas/toolOverrides';\nimport type { DoomToolRestrictionDefinition } from '../schemas/toolSurface';\nimport { DOOM_TOOL_SURFACE_SERVICE, requireDoomToolSurface } from '../schemas/toolSurface';\nimport { createPluginLifecycle, type PluginLifecycleHooks } from '../services/pluginLifecycle';\n\n/** Extract native overloads without copying Pi event payload or result contracts. */\ntype NativeEventOverloads = ExtensionAPI['on'] extends {\n (event: infer E1 extends string, handler: infer H1): void;\n (event: infer E2 extends string, handler: infer H2): void;\n (event: infer E3 extends string, handler: infer H3): void;\n (event: infer E4 extends string, handler: infer H4): void;\n (event: infer E5 extends string, handler: infer H5): void;\n (event: infer E6 extends string, handler: infer H6): void;\n (event: infer E7 extends string, handler: infer H7): void;\n (event: infer E8 extends string, handler: infer H8): void;\n (event: infer E9 extends string, handler: infer H9): void;\n (event: infer E10 extends string, handler: infer H10): void;\n (event: infer E11 extends string, handler: infer H11): void;\n (event: infer E12 extends string, handler: infer H12): void;\n (event: infer E13 extends string, handler: infer H13): void;\n (event: infer E14 extends string, handler: infer H14): void;\n (event: infer E15 extends string, handler: infer H15): void;\n (event: infer E16 extends string, handler: infer H16): void;\n (event: infer E17 extends string, handler: infer H17): void;\n (event: infer E18 extends string, handler: infer H18): void;\n (event: infer E19 extends string, handler: infer H19): void;\n (event: infer E20 extends string, handler: infer H20): void;\n (event: infer E21 extends string, handler: infer H21): void;\n (event: infer E22 extends string, handler: infer H22): void;\n (event: infer E23 extends string, handler: infer H23): void;\n (event: infer E24 extends string, handler: infer H24): void;\n (event: infer E25 extends string, handler: infer H25): void;\n (event: infer E26 extends string, handler: infer H26): void;\n (event: infer E27 extends string, handler: infer H27): void;\n (event: infer E28 extends string, handler: infer H28): void;\n (event: infer E29 extends string, handler: infer H29): void;\n (event: infer E30 extends string, handler: infer H30): void;\n (event: infer E31 extends string, handler: infer H31): void;\n (event: infer E32 extends string, handler: infer H32): void;\n (event: infer E33 extends string, handler: infer H33): void;\n (event: infer E34 extends string, handler: infer H34): void;\n (event: infer E35 extends string, handler: infer H35): void;\n (event: infer E36 extends string, handler: infer H36): void;\n (event: infer E37 extends string, handler: infer H37): void;\n (event: infer E38 extends string, handler: infer H38): void;\n (event: infer E39 extends string, handler: infer H39): void;\n (event: infer E40 extends string, handler: infer H40): void;\n}\n ?\n | [E1, H1]\n | [E2, H2]\n | [E3, H3]\n | [E4, H4]\n | [E5, H5]\n | [E6, H6]\n | [E7, H7]\n | [E8, H8]\n | [E9, H9]\n | [E10, H10]\n | [E11, H11]\n | [E12, H12]\n | [E13, H13]\n | [E14, H14]\n | [E15, H15]\n | [E16, H16]\n | [E17, H17]\n | [E18, H18]\n | [E19, H19]\n | [E20, H20]\n | [E21, H21]\n | [E22, H22]\n | [E23, H23]\n | [E24, H24]\n | [E25, H25]\n | [E26, H26]\n | [E27, H27]\n | [E28, H28]\n | [E29, H29]\n | [E30, H30]\n | [E31, H31]\n | [E32, H32]\n | [E33, H33]\n | [E34, H34]\n | [E35, H35]\n | [E36, H36]\n | [E37, H37]\n | [E38, H38]\n | [E39, H39]\n | [E40, H40]\n : never;\n\nexport type PiEventHandlers = {\n readonly [Pair in NativeEventOverloads as Pair[0]]?: Pair[1];\n};\n\nexport interface PiPluginContext<TOptions = undefined> {\n readonly context: Context;\n readonly pi: ExtensionAPI;\n readonly options: TOptions | undefined;\n readonly runtime: DoomCordisRuntimeService | undefined;\n readonly signal: AbortSignal;\n}\n\nexport type PiToolRestriction = DoomToolRestrictionDefinition;\n\nexport interface PiPluginTool extends DoomPluginTool {\n readonly pi?: Pick<Parameters<ExtensionAPI['registerTool']>[0], 'renderShell' | 'renderCall' | 'renderResult'>;\n}\n\nexport interface PiToolOverride extends DoomToolOverrideClaim {\n /** Contributions registered only while this provider owns the override. */\n readonly tools: readonly string[];\n readonly replacements: readonly (PiToolDeclaration | Parameters<ExtensionAPI['registerTool']>[0])[];\n}\n\nexport type PiToolContribution = PiPluginTool | PiToolDeclaration | Parameters<ExtensionAPI['registerTool']>[0];\n\n/** Snapshots reuse immutable declarations. A registered name cannot adopt a different declaration until remount. */\nexport interface PiToolCollection {\n snapshot(): readonly PiToolContribution[];\n subscribe(listener: () => void): () => void;\n}\n\nexport interface PiPluginContributions<TOptions = undefined> extends PluginLifecycleHooks<PiPluginContext<TOptions>> {\n readonly services?: readonly Parameters<Context['plugin']>[0][];\n readonly tools?: readonly PiToolContribution[] | PiToolCollection;\n readonly commands?: readonly (DoomPluginCommand | readonly [...Parameters<ExtensionAPI['registerCommand']>])[];\n readonly events?: PiEventHandlers;\n readonly resources?: readonly Parameters<DoomHelpService['register']>[0][];\n readonly toolRestrictions?: readonly PiToolRestriction[];\n readonly toolOverrides?: readonly PiToolOverride[];\n readonly providers?: readonly (readonly [...Parameters<ExtensionAPI['registerProvider']>])[];\n readonly messageRenderers?: readonly (readonly [...Parameters<ExtensionAPI['registerMessageRenderer']>])[];\n readonly entryRenderers?: readonly (readonly [...Parameters<ExtensionAPI['registerEntryRenderer']>])[];\n readonly markdownTransformers?: readonly Parameters<ExtensionAPI['registerMarkdownTransformer']>[0][];\n readonly shortcuts?: readonly (readonly [...Parameters<ExtensionAPI['registerShortcut']>])[];\n readonly flags?: readonly (readonly [...Parameters<ExtensionAPI['registerFlag']>])[];\n}\n\nexport interface PiExtensionDefinition<TOptions = undefined> extends PiPluginContributions<TOptions> {\n readonly name: string;\n}\n\nexport interface DefinedPiExtension<TOptions = undefined> {\n (pi: ExtensionAPI, options?: TOptions): Promise<void>;\n install(\n this: void,\n context: Context,\n pi: ExtensionAPI,\n options?: TOptions,\n runtime?: DoomCordisRuntimeService,\n ): Promise<void>;\n}\n\nexport function definePiExtension<TOptions = undefined>(\n definition: PiExtensionDefinition<TOptions>,\n): DefinedPiExtension<TOptions>;\nexport function definePiExtension<TOptions = undefined>(\n name: string,\n factory: (\n context: PiPluginContext<TOptions>,\n ) => PiPluginContributions<TOptions> | Promise<PiPluginContributions<TOptions>>,\n): DefinedPiExtension<TOptions>;\nexport function definePiExtension<TOptions = undefined>(\n definition: string | PiExtensionDefinition<TOptions>,\n factory?: (\n context: PiPluginContext<TOptions>,\n ) => PiPluginContributions<TOptions> | Promise<PiPluginContributions<TOptions>>,\n): DefinedPiExtension<TOptions> {\n const name = typeof definition === 'string' ? definition : definition.name;\n const install = async (\n context: Context,\n pi: ExtensionAPI,\n options?: TOptions,\n runtime?: DoomCordisRuntimeService,\n ): Promise<void> => {\n const lifecycle = createPluginLifecycle((signal): PiPluginContext<TOptions> => ({\n context,\n pi,\n options,\n runtime,\n signal,\n }));\n context.effect(() => () => lifecycle.dispose(), `${name}/lifecycle`);\n const tool = (item: PiToolContribution, executionSignal?: () => AbortSignal): void => {\n if ('kind' in item && item.kind === 'pi-tool') {\n item.register(pi, executionSignal);\n return;\n }\n if (!('kind' in item)) {\n if (!executionSignal) {\n pi.registerTool(item);\n return;\n }\n pi.registerTool({\n ...item,\n async execute(toolCallId, input, signal, update, context) {\n const current = executionSignal();\n current.throwIfAborted();\n return item.execute(\n toolCallId,\n input,\n signal ? AbortSignal.any([signal, current]) : current,\n update,\n context,\n );\n },\n });\n return;\n }\n pi.registerTool({\n ...item.pi,\n name: item.name,\n label: item.label ?? item.name,\n description: item.description,\n parameters: item.parameters,\n promptSnippet: item.promptSnippet,\n promptGuidelines: item.promptGuidelines ? [...item.promptGuidelines] : undefined,\n executionMode: item.executionMode === 'serial' ? 'sequential' : item.executionMode,\n async execute(toolCallId, input, signal, update, execution) {\n const current = executionSignal?.() ?? lifecycle.context.signal;\n current.throwIfAborted();\n const result = await item.execute(input, {\n toolCallId,\n cwd: execution.cwd,\n signal: signal ? AbortSignal.any([signal, current]) : current,\n update: (result) => update?.({ ...result, details: result.details }),\n notify: (request) => {\n if (execution.hasUI) execution.ui.notify(request.body, request.level ?? 'info');\n },\n });\n return { ...result, details: result.details };\n },\n });\n };\n await lifecycle.mount(\n () => (typeof definition === 'string' ? factory!(lifecycle.context) : definition),\n async (contributions) => {\n for (const service of contributions.services ?? []) {\n const fiber = context.plugin(service);\n lifecycle.own(() => fiber.dispose());\n await fiber;\n }\n const tools = contributions.tools;\n if (tools && 'snapshot' in tools) {\n const registered = new Map<string, { readonly declaration: PiToolContribution; active?: AbortController }>();\n let disposed = false;\n let subscribing = true;\n let reconciling = false;\n let pending = false;\n const deactivate = (): void => {\n for (const entry of registered.values()) {\n entry.active?.abort();\n entry.active = undefined;\n }\n };\n lifecycle.own(() => {\n disposed = true;\n deactivate();\n });\n const reconcile = (): void => {\n pending = true;\n if (disposed || lifecycle.context.signal.aborted) {\n deactivate();\n return;\n }\n if (reconciling || subscribing) return;\n reconciling = true;\n try {\n do {\n pending = false;\n const snapshot = tools.snapshot();\n const names = new Map<string, PiToolContribution>();\n for (const declaration of snapshot) {\n if (names.has(declaration.name)) throw new Error(`Duplicate live tool name: ${declaration.name}`);\n names.set(declaration.name, declaration);\n }\n for (const [name, entry] of registered) {\n if (names.get(name) !== entry.declaration) {\n entry.active?.abort();\n entry.active = undefined;\n }\n }\n for (const declaration of snapshot) {\n let entry = registered.get(declaration.name);\n if (entry && entry.declaration !== declaration) entry = undefined;\n if (entry) {\n entry.active ??= new AbortController();\n continue;\n }\n entry = { declaration, active: new AbortController() };\n const current = entry;\n registered.set(declaration.name, entry);\n try {\n tool(declaration, () => {\n lifecycle.context.signal.throwIfAborted();\n if (!current.active || disposed)\n throw new Error(`Tool ${declaration.name} is unavailable in this plugin generation.`);\n return AbortSignal.any([lifecycle.context.signal, current.active.signal]);\n });\n } catch (error) {\n registered.delete(declaration.name);\n throw error;\n }\n }\n } while (pending && !disposed);\n } catch (error) {\n deactivate();\n throw error;\n } finally {\n reconciling = false;\n }\n };\n const unsubscribe = tools.subscribe(() => {\n try {\n reconcile();\n } catch {\n /* An invalid update leaves registered tools unavailable. */\n }\n });\n lifecycle.own(() => {\n disposed = true;\n deactivate();\n unsubscribe();\n });\n subscribing = false;\n reconcile();\n } else {\n for (const item of tools ?? []) tool(item);\n }\n for (const item of contributions.commands ?? []) {\n if (!('kind' in item)) {\n pi.registerCommand(...item);\n continue;\n }\n pi.registerCommand(item.name, {\n description: item.description,\n async handler(args, execution) {\n lifecycle.context.signal.throwIfAborted();\n await item.execute(args, {\n cwd: execution.cwd,\n signal: lifecycle.context.signal,\n notify: (request) => {\n if (execution.hasUI) execution.ui.notify(request.body, request.level ?? 'info');\n },\n });\n },\n });\n }\n // The public mapped handlers retain each native event's input and return type.\n const on = pi.on.bind(pi) as (event: string, handler: unknown) => void;\n for (const [event, handler] of Object.entries(contributions.events ?? {})) on(event, handler);\n for (const item of contributions.providers ?? []) pi.registerProvider(...item);\n for (const item of contributions.messageRenderers ?? []) pi.registerMessageRenderer(...item);\n for (const item of contributions.entryRenderers ?? []) pi.registerEntryRenderer(...item);\n for (const item of contributions.markdownTransformers ?? []) pi.registerMarkdownTransformer(item);\n for (const item of contributions.shortcuts ?? []) pi.registerShortcut(...item);\n for (const item of contributions.flags ?? []) pi.registerFlag(...item);\n const bind = (service: string, register: (child: Context) => void): void => {\n const fiber = context.inject([service], register);\n lifecycle.own(() => fiber.dispose());\n };\n if (contributions.resources?.length)\n bind(DOOM_HELP_SERVICE, (child) => {\n for (const resource of contributions.resources!) {\n const handle = requireDoomHelpService(child).register(resource);\n child.effect(() => () => handle.dispose());\n }\n });\n if (contributions.toolRestrictions?.length)\n bind(DOOM_TOOL_SURFACE_SERVICE, (child) => {\n for (const restriction of contributions.toolRestrictions!) {\n const handle = requireDoomToolSurface(child).register(restriction);\n child.effect(() => () => handle.dispose());\n const unsubscribe = restriction.subscribe?.(() => handle.update(restriction.restrict));\n if (unsubscribe) child.effect(() => unsubscribe);\n }\n });\n if (contributions.toolOverrides?.length)\n bind(DOOM_TOOL_OVERRIDES_SERVICE, (child) => {\n for (const override of contributions.toolOverrides!) {\n const handle = requireDoomToolOverrides(child).claim(override);\n child.effect(() => () => handle.dispose());\n if (handle.granted) for (const replacement of override.replacements) tool(replacement);\n }\n });\n },\n );\n };\n const activate = async (pi: ExtensionAPI, options?: TOptions): Promise<void> => {\n const connection = await connectDoomCordisHost(pi, name);\n const fiber = connection.root.plugin(async (context) => {\n await install(context, pi, options, connection.runtime);\n });\n let completion: Promise<void> | undefined;\n const dispose = (): Promise<void> =>\n (completion ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })());\n try {\n await fiber;\n pi.on('session_shutdown', dispose);\n } catch (error) {\n try {\n await dispose();\n } catch (cleanupError) {\n throw new AggregateError([error, cleanupError], `${name} startup and cleanup failed.`);\n }\n throw error;\n }\n };\n return Object.assign(activate, { install });\n}\n\n/**\n * What a CLI tool's presentation file supplies.\n *\n * The terminal is a frontend. `renderCall` and `renderResult` draw a tool call\n * into the TUI exactly as a cockpit tool renderer draws it into the browser,\n * so they are authored on the frontend side under a `.cli` platform suffix and\n * never alongside the tool's `execute`.\n */\nexport type PiToolRenderers<TParameters extends TSchema = TSchema, TDetails = unknown, TState = unknown> = Pick<\n ToolDefinition<TParameters, TDetails, TState>,\n 'renderShell' | 'renderCall' | 'renderResult'\n>;\n\n/** A terminal renderer declaration or a factory resolved from its session mount. */\nexport type PiToolRendererFile<\n TContext = unknown,\n TParameters extends TSchema = TSchema,\n TDetails = unknown,\n TState = unknown,\n> =\n | PiToolRenderers<TParameters, TDetails, TState>\n | ((context: TContext) => PiToolRenderers<TParameters, TDetails, TState>);\n/**\n * `(frontend)/tool/<name>.cli.tsx`. The filename names the tool these draw.\n *\n * Generic over the tool's schema, because a renderer reads the call it is\n * drawing: state the parameter type and `renderResult` sees the real arguments\n * instead of `unknown`, which is what the single-file version got for free.\n */\nexport function definePiToolRenderer<TParameters extends TSchema = TSchema, TDetails = unknown, TState = unknown>(\n file: PiToolRenderers<TParameters, TDetails, TState>,\n): PiToolRenderers<TParameters, TDetails, TState> {\n return file;\n}\n\n/** `(frontend)/tool/<name>.cli.tsx` when the renderer needs mount context. */\nexport function defineCliToolRenderer<\n TContext,\n TParameters extends TSchema = TSchema,\n TDetails = unknown,\n TState = unknown,\n>(\n file: PiToolRendererFile<TContext, TParameters, TDetails, TState>,\n): PiToolRendererFile<TContext, TParameters, TDetails, TState> {\n return file;\n}\n\n/**\n * `(frontend)/message/<name>.cli.tsx`. A renderer for one custom message type.\n *\n * The type is stated rather than derived from the filename, because it is a\n * protocol constant shared with whatever emits the message. A path-derived\n * name would silently stop matching the moment either side was renamed.\n */\nexport function defineMessageRenderer(\n messageType: Parameters<ExtensionAPI['registerMessageRenderer']>[0],\n render: Parameters<ExtensionAPI['registerMessageRenderer']>[1],\n): readonly [...Parameters<ExtensionAPI['registerMessageRenderer']>] {\n return [messageType, render];\n}\n\n/**\n * Attaches a presentation file's renderers to the tool its filename names.\n *\n * Three tool shapes reach a contribution array and each carries renderers in a\n * different place, so a generated entry cannot spell this merge itself. A\n * native Pi definition takes them at the top level, the portable shape nests\n * them under `pi`, and `definePiTool` owns its own registration, so that one\n * is reached by narrowing the host it registers against. `register` asks for\n * nothing but `registerTool`, which is what makes the last case a two-line\n * shim rather than a proxy over the whole extension API.\n */\nexport function withPiRenderers(tool: PiToolContribution, renderers: PiToolRenderers): PiToolContribution {\n if (!('kind' in tool)) return { ...tool, ...renderers };\n if (tool.kind !== 'pi-tool') return { ...tool, pi: { ...tool.pi, ...renderers } };\n return {\n ...tool,\n register(pi, executionSignal) {\n tool.register(\n { registerTool: (definition) => pi.registerTool({ ...definition, ...renderers }) },\n executionSignal,\n );\n },\n };\n}\n\n/**\n * Splits routed tool files into the two arrays the Pi host keeps apart.\n *\n * A tool that replaces a name Pi already ships is not a second registration,\n * it is a claim the override service arbitrates, and only the winner\n * registers. The folder convention keeps both in `tool/`, because from the\n * author's side both are \"this package provides grep\"; which one it is lives\n * in the value, through `definePiTool(definition, { overrides: true })`.\n *\n * A generated entry therefore cannot decide this from the path, and calls\n * this instead. Every static claim from one package is one atomic set, which\n * is what the override service expects. A live collection stays intact so its\n * subscription continues to drive registration changes.\n */\nexport function piToolContributions(\n source: string,\n items: readonly PiToolContribution[] | PiToolCollection,\n): Pick<PiPluginContributions, 'tools' | 'toolOverrides'> {\n if (!Array.isArray(items)) return { tools: items as PiToolCollection };\n const added: PiToolContribution[] = [];\n const claimed: PiToolDeclaration[] = [];\n for (const item of items) {\n // Only a native declaration may claim a name: a replacement is handed to\n // Pi's registry directly, which the portable shape is not.\n if ('kind' in item && item.kind === 'pi-tool' && item.overrides === true) claimed.push(item);\n else added.push(item);\n }\n if (claimed.length === 0) return { tools: added };\n return {\n tools: added,\n toolOverrides: [{ source, tools: claimed.map((item) => item.name), replacements: claimed }],\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"runtime-pi-extension.cjs","names":["createPluginLifecycle","DOOM_HELP_SERVICE","requireDoomHelpService","DOOM_TOOL_SURFACE_SERVICE","requireDoomToolSurface","DOOM_TOOL_OVERRIDES_SERVICE","requireDoomToolOverrides","connectDoomCordisHost"],"sources":["../src/extensions/piExtension.ts"],"sourcesContent":["import type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ToolDefinition } from '@earendil-works/pi-coding-agent';\nimport type { TSchema } from 'typebox';\n\nimport { connectDoomCordisHost, type DoomCordisRuntimeService } from '../pi/cordisHost';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService, type DoomHelpService } from '../schemas/help';\nimport type { PiToolDeclaration } from '../schemas/piTool';\nimport type { DoomPluginTool, DoomPluginCommand } from '../schemas/pluginContributions';\nimport {\n DOOM_TOOL_OVERRIDES_SERVICE,\n requireDoomToolOverrides,\n type DoomToolOverrideClaim,\n} from '../schemas/toolOverrides';\nimport type { DoomToolRestrictionDefinition } from '../schemas/toolSurface';\nimport { DOOM_TOOL_SURFACE_SERVICE, requireDoomToolSurface } from '../schemas/toolSurface';\nimport { createPluginLifecycle, type PluginLifecycleHooks } from '../services/pluginLifecycle';\n\n/** Extract native overloads without copying Pi event payload or result contracts. */\ntype NativeEventOverloads = ExtensionAPI['on'] extends {\n (event: infer E1 extends string, handler: infer H1): void;\n (event: infer E2 extends string, handler: infer H2): void;\n (event: infer E3 extends string, handler: infer H3): void;\n (event: infer E4 extends string, handler: infer H4): void;\n (event: infer E5 extends string, handler: infer H5): void;\n (event: infer E6 extends string, handler: infer H6): void;\n (event: infer E7 extends string, handler: infer H7): void;\n (event: infer E8 extends string, handler: infer H8): void;\n (event: infer E9 extends string, handler: infer H9): void;\n (event: infer E10 extends string, handler: infer H10): void;\n (event: infer E11 extends string, handler: infer H11): void;\n (event: infer E12 extends string, handler: infer H12): void;\n (event: infer E13 extends string, handler: infer H13): void;\n (event: infer E14 extends string, handler: infer H14): void;\n (event: infer E15 extends string, handler: infer H15): void;\n (event: infer E16 extends string, handler: infer H16): void;\n (event: infer E17 extends string, handler: infer H17): void;\n (event: infer E18 extends string, handler: infer H18): void;\n (event: infer E19 extends string, handler: infer H19): void;\n (event: infer E20 extends string, handler: infer H20): void;\n (event: infer E21 extends string, handler: infer H21): void;\n (event: infer E22 extends string, handler: infer H22): void;\n (event: infer E23 extends string, handler: infer H23): void;\n (event: infer E24 extends string, handler: infer H24): void;\n (event: infer E25 extends string, handler: infer H25): void;\n (event: infer E26 extends string, handler: infer H26): void;\n (event: infer E27 extends string, handler: infer H27): void;\n (event: infer E28 extends string, handler: infer H28): void;\n (event: infer E29 extends string, handler: infer H29): void;\n (event: infer E30 extends string, handler: infer H30): void;\n (event: infer E31 extends string, handler: infer H31): void;\n (event: infer E32 extends string, handler: infer H32): void;\n (event: infer E33 extends string, handler: infer H33): void;\n (event: infer E34 extends string, handler: infer H34): void;\n (event: infer E35 extends string, handler: infer H35): void;\n (event: infer E36 extends string, handler: infer H36): void;\n (event: infer E37 extends string, handler: infer H37): void;\n (event: infer E38 extends string, handler: infer H38): void;\n (event: infer E39 extends string, handler: infer H39): void;\n (event: infer E40 extends string, handler: infer H40): void;\n}\n ?\n | [E1, H1]\n | [E2, H2]\n | [E3, H3]\n | [E4, H4]\n | [E5, H5]\n | [E6, H6]\n | [E7, H7]\n | [E8, H8]\n | [E9, H9]\n | [E10, H10]\n | [E11, H11]\n | [E12, H12]\n | [E13, H13]\n | [E14, H14]\n | [E15, H15]\n | [E16, H16]\n | [E17, H17]\n | [E18, H18]\n | [E19, H19]\n | [E20, H20]\n | [E21, H21]\n | [E22, H22]\n | [E23, H23]\n | [E24, H24]\n | [E25, H25]\n | [E26, H26]\n | [E27, H27]\n | [E28, H28]\n | [E29, H29]\n | [E30, H30]\n | [E31, H31]\n | [E32, H32]\n | [E33, H33]\n | [E34, H34]\n | [E35, H35]\n | [E36, H36]\n | [E37, H37]\n | [E38, H38]\n | [E39, H39]\n | [E40, H40]\n : never;\n\nexport type PiEventHandlers = {\n readonly [Pair in NativeEventOverloads as Pair[0]]?: Pair[1];\n};\n\nexport interface PiPluginContext<TOptions = undefined> {\n readonly context: Context;\n readonly pi: ExtensionAPI;\n readonly options: TOptions | undefined;\n readonly runtime: DoomCordisRuntimeService | undefined;\n readonly signal: AbortSignal;\n}\n\nexport type PiToolRestriction = DoomToolRestrictionDefinition;\n\nexport interface PiPluginTool extends DoomPluginTool {\n readonly pi?: Pick<Parameters<ExtensionAPI['registerTool']>[0], 'renderShell' | 'renderCall' | 'renderResult'>;\n}\n\nexport interface PiToolOverride extends DoomToolOverrideClaim {\n /** Contributions registered only while this provider owns the override. */\n readonly tools: readonly string[];\n readonly replacements: readonly (PiToolDeclaration | Parameters<ExtensionAPI['registerTool']>[0])[];\n}\n\nexport type PiToolContribution = PiPluginTool | PiToolDeclaration | Parameters<ExtensionAPI['registerTool']>[0];\n\n/** Snapshots reuse immutable declarations. A registered name cannot adopt a different declaration until remount. */\nexport interface PiToolCollection {\n snapshot(): readonly PiToolContribution[];\n subscribe(listener: () => void): () => void;\n}\n\nexport interface PiPluginContributions<TOptions = undefined> extends PluginLifecycleHooks<PiPluginContext<TOptions>> {\n readonly services?: readonly Parameters<Context['plugin']>[0][];\n readonly tools?: readonly PiToolContribution[] | PiToolCollection;\n readonly commands?: readonly (DoomPluginCommand | readonly [...Parameters<ExtensionAPI['registerCommand']>])[];\n readonly events?: PiEventHandlers;\n readonly resources?: readonly Parameters<DoomHelpService['register']>[0][];\n readonly toolRestrictions?: readonly PiToolRestriction[];\n readonly toolOverrides?: readonly PiToolOverride[];\n readonly providers?: readonly (readonly [...Parameters<ExtensionAPI['registerProvider']>])[];\n readonly messageRenderers?: readonly (readonly [...Parameters<ExtensionAPI['registerMessageRenderer']>])[];\n readonly entryRenderers?: readonly (readonly [...Parameters<ExtensionAPI['registerEntryRenderer']>])[];\n readonly markdownTransformers?: readonly Parameters<ExtensionAPI['registerMarkdownTransformer']>[0][];\n readonly shortcuts?: readonly (readonly [...Parameters<ExtensionAPI['registerShortcut']>])[];\n readonly flags?: readonly (readonly [...Parameters<ExtensionAPI['registerFlag']>])[];\n}\n\nexport interface PiExtensionDefinition<TOptions = undefined> extends PiPluginContributions<TOptions> {\n readonly name: string;\n}\n\nexport interface DefinedPiExtension<TOptions = undefined> {\n (pi: ExtensionAPI, options?: TOptions): Promise<void>;\n install(\n this: void,\n context: Context,\n pi: ExtensionAPI,\n options?: TOptions,\n runtime?: DoomCordisRuntimeService,\n ): Promise<void>;\n}\n\nexport function definePiExtension<TOptions = undefined>(\n definition: PiExtensionDefinition<TOptions>,\n): DefinedPiExtension<TOptions>;\nexport function definePiExtension<TOptions = undefined>(\n name: string,\n factory: (\n context: PiPluginContext<TOptions>,\n ) => PiPluginContributions<TOptions> | Promise<PiPluginContributions<TOptions>>,\n): DefinedPiExtension<TOptions>;\nexport function definePiExtension<TOptions = undefined>(\n definition: string | PiExtensionDefinition<TOptions>,\n factory?: (\n context: PiPluginContext<TOptions>,\n ) => PiPluginContributions<TOptions> | Promise<PiPluginContributions<TOptions>>,\n): DefinedPiExtension<TOptions> {\n const name = typeof definition === 'string' ? definition : definition.name;\n const install = async (\n context: Context,\n pi: ExtensionAPI,\n options?: TOptions,\n runtime?: DoomCordisRuntimeService,\n ): Promise<void> => {\n const lifecycle = createPluginLifecycle((signal): PiPluginContext<TOptions> => ({\n context,\n pi,\n options,\n runtime,\n signal,\n }));\n context.effect(() => () => lifecycle.dispose(), `${name}/lifecycle`);\n const tool = (item: PiToolContribution, executionSignal?: () => AbortSignal): void => {\n if ('kind' in item && item.kind === 'pi-tool') {\n item.register(pi, executionSignal);\n return;\n }\n if (!('kind' in item)) {\n if (!executionSignal) {\n pi.registerTool(item);\n return;\n }\n pi.registerTool({\n ...item,\n async execute(toolCallId, input, signal, update, context) {\n const current = executionSignal();\n current.throwIfAborted();\n return item.execute(\n toolCallId,\n input,\n signal ? AbortSignal.any([signal, current]) : current,\n update,\n context,\n );\n },\n });\n return;\n }\n pi.registerTool({\n ...item.pi,\n name: item.name,\n label: item.label ?? item.name,\n description: item.description,\n parameters: item.parameters,\n promptSnippet: item.promptSnippet,\n promptGuidelines: item.promptGuidelines ? [...item.promptGuidelines] : undefined,\n executionMode: item.executionMode === 'serial' ? 'sequential' : item.executionMode,\n async execute(toolCallId, input, signal, update, execution) {\n const current = executionSignal?.() ?? lifecycle.context.signal;\n current.throwIfAborted();\n const result = await item.execute(input, {\n toolCallId,\n cwd: execution.cwd,\n signal: signal ? AbortSignal.any([signal, current]) : current,\n update: (result) => update?.({ ...result, details: result.details }),\n notify: (request) => {\n if (execution.hasUI) execution.ui.notify(request.body, request.level ?? 'info');\n },\n });\n return { ...result, details: result.details };\n },\n });\n };\n await lifecycle.mount(\n () => (typeof definition === 'string' ? factory!(lifecycle.context) : definition),\n async (contributions) => {\n for (const service of contributions.services ?? []) {\n const fiber = context.plugin(service);\n lifecycle.own(() => fiber.dispose());\n await fiber;\n }\n const tools = contributions.tools;\n if (tools && 'snapshot' in tools) {\n const registered = new Map<string, { readonly declaration: PiToolContribution; active?: AbortController }>();\n let disposed = false;\n let subscribing = true;\n let reconciling = false;\n let pending = false;\n const deactivate = (): void => {\n for (const entry of registered.values()) {\n entry.active?.abort();\n entry.active = undefined;\n }\n };\n lifecycle.own(() => {\n disposed = true;\n deactivate();\n });\n const reconcile = (): void => {\n pending = true;\n if (disposed || lifecycle.context.signal.aborted) {\n deactivate();\n return;\n }\n if (reconciling || subscribing) return;\n reconciling = true;\n try {\n do {\n pending = false;\n const snapshot = tools.snapshot();\n const names = new Map<string, PiToolContribution>();\n for (const declaration of snapshot) {\n if (names.has(declaration.name)) throw new Error(`Duplicate live tool name: ${declaration.name}`);\n names.set(declaration.name, declaration);\n }\n for (const [name, entry] of registered) {\n if (names.get(name) !== entry.declaration) {\n entry.active?.abort();\n entry.active = undefined;\n }\n }\n for (const declaration of snapshot) {\n let entry = registered.get(declaration.name);\n if (entry && entry.declaration !== declaration) entry = undefined;\n if (entry) {\n entry.active ??= new AbortController();\n continue;\n }\n entry = { declaration, active: new AbortController() };\n const current = entry;\n registered.set(declaration.name, entry);\n try {\n tool(declaration, () => {\n lifecycle.context.signal.throwIfAborted();\n if (!current.active || disposed)\n throw new Error(`Tool ${declaration.name} is unavailable in this plugin generation.`);\n return AbortSignal.any([lifecycle.context.signal, current.active.signal]);\n });\n } catch (error) {\n registered.delete(declaration.name);\n throw error;\n }\n }\n } while (pending && !disposed);\n } catch (error) {\n deactivate();\n throw error;\n } finally {\n reconciling = false;\n }\n };\n const unsubscribe = tools.subscribe(() => {\n try {\n reconcile();\n } catch {\n /* An invalid update leaves registered tools unavailable. */\n }\n });\n lifecycle.own(() => {\n disposed = true;\n deactivate();\n unsubscribe();\n });\n subscribing = false;\n reconcile();\n } else {\n for (const item of tools ?? []) tool(item);\n }\n for (const item of contributions.commands ?? []) {\n if (!('kind' in item)) {\n pi.registerCommand(...item);\n continue;\n }\n pi.registerCommand(item.name, {\n description: item.description,\n async handler(args, execution) {\n lifecycle.context.signal.throwIfAborted();\n await item.execute(args, {\n cwd: execution.cwd,\n signal: lifecycle.context.signal,\n notify: (request) => {\n if (execution.hasUI) execution.ui.notify(request.body, request.level ?? 'info');\n },\n });\n },\n });\n }\n // The public mapped handlers retain each native event's input and return type.\n const on = pi.on.bind(pi) as (event: string, handler: unknown) => ReturnType<ExtensionAPI['on']>;\n for (const [event, handler] of Object.entries(contributions.events ?? {})) lifecycle.own(on(event, handler));\n for (const item of contributions.providers ?? []) pi.registerProvider(...item);\n for (const item of contributions.messageRenderers ?? []) pi.registerMessageRenderer(...item);\n for (const item of contributions.entryRenderers ?? []) pi.registerEntryRenderer(...item);\n for (const item of contributions.markdownTransformers ?? []) pi.registerMarkdownTransformer(item);\n for (const item of contributions.shortcuts ?? []) pi.registerShortcut(...item);\n for (const item of contributions.flags ?? []) pi.registerFlag(...item);\n const bind = (service: string, register: (child: Context) => void): void => {\n const fiber = context.inject([service], register);\n lifecycle.own(() => fiber.dispose());\n };\n if (contributions.resources?.length)\n bind(DOOM_HELP_SERVICE, (child) => {\n for (const resource of contributions.resources!) {\n const handle = requireDoomHelpService(child).register(resource);\n child.effect(() => () => handle.dispose());\n }\n });\n if (contributions.toolRestrictions?.length)\n bind(DOOM_TOOL_SURFACE_SERVICE, (child) => {\n for (const restriction of contributions.toolRestrictions!) {\n const handle = requireDoomToolSurface(child).register(restriction);\n child.effect(() => () => handle.dispose());\n const unsubscribe = restriction.subscribe?.(() => handle.update(restriction.restrict));\n if (unsubscribe) child.effect(() => unsubscribe);\n }\n });\n if (contributions.toolOverrides?.length)\n bind(DOOM_TOOL_OVERRIDES_SERVICE, (child) => {\n for (const override of contributions.toolOverrides!) {\n const handle = requireDoomToolOverrides(child).claim(override);\n child.effect(() => () => handle.dispose());\n if (handle.granted) for (const replacement of override.replacements) tool(replacement);\n }\n });\n },\n );\n };\n const activate = async (pi: ExtensionAPI, options?: TOptions): Promise<void> => {\n const connection = await connectDoomCordisHost(pi, name);\n const fiber = connection.root.plugin(async (context) => {\n await install(context, pi, options, connection.runtime);\n });\n let completion: Promise<void> | undefined;\n let unsubscribeShutdown: (() => void) | undefined;\n const dispose = (): Promise<void> =>\n (completion ??= (async () => {\n try {\n unsubscribeShutdown?.();\n unsubscribeShutdown = undefined;\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })());\n try {\n await fiber;\n const shutdownRegistration = pi.on('session_shutdown', dispose) as unknown;\n if (typeof shutdownRegistration === 'function') unsubscribeShutdown = shutdownRegistration as () => void;\n } catch (error) {\n try {\n await dispose();\n } catch (cleanupError) {\n throw new AggregateError([error, cleanupError], `${name} startup and cleanup failed.`);\n }\n throw error;\n }\n };\n return Object.assign(activate, { install });\n}\n\n/**\n * What a CLI tool's presentation file supplies.\n *\n * The terminal is a frontend. `renderCall` and `renderResult` draw a tool call\n * into the TUI exactly as a cockpit tool renderer draws it into the browser,\n * so they are authored on the frontend side under a `.cli` platform suffix and\n * never alongside the tool's `execute`.\n */\nexport type PiToolRenderers<TParameters extends TSchema = TSchema, TDetails = unknown, TState = unknown> = Pick<\n ToolDefinition<TParameters, TDetails, TState>,\n 'renderShell' | 'renderCall' | 'renderResult'\n>;\n\n/** A terminal renderer declaration or a factory resolved from its session mount. */\nexport type PiToolRendererFile<\n TContext = unknown,\n TParameters extends TSchema = TSchema,\n TDetails = unknown,\n TState = unknown,\n> =\n | PiToolRenderers<TParameters, TDetails, TState>\n | ((context: TContext) => PiToolRenderers<TParameters, TDetails, TState>);\n/**\n * `(frontend)/tool/<name>.cli.tsx`. The filename names the tool these draw.\n *\n * Generic over the tool's schema, because a renderer reads the call it is\n * drawing: state the parameter type and `renderResult` sees the real arguments\n * instead of `unknown`, which is what the single-file version got for free.\n */\nexport function definePiToolRenderer<TParameters extends TSchema = TSchema, TDetails = unknown, TState = unknown>(\n file: PiToolRenderers<TParameters, TDetails, TState>,\n): PiToolRenderers<TParameters, TDetails, TState> {\n return file;\n}\n\n/** `(frontend)/tool/<name>.cli.tsx` when the renderer needs mount context. */\nexport function defineCliToolRenderer<\n TContext,\n TParameters extends TSchema = TSchema,\n TDetails = unknown,\n TState = unknown,\n>(\n file: PiToolRendererFile<TContext, TParameters, TDetails, TState>,\n): PiToolRendererFile<TContext, TParameters, TDetails, TState> {\n return file;\n}\n\n/**\n * `(frontend)/message/<name>.cli.tsx`. A renderer for one custom message type.\n *\n * The type is stated rather than derived from the filename, because it is a\n * protocol constant shared with whatever emits the message. A path-derived\n * name would silently stop matching the moment either side was renamed.\n */\nexport function defineMessageRenderer(\n messageType: Parameters<ExtensionAPI['registerMessageRenderer']>[0],\n render: Parameters<ExtensionAPI['registerMessageRenderer']>[1],\n): readonly [...Parameters<ExtensionAPI['registerMessageRenderer']>] {\n return [messageType, render];\n}\n\n/**\n * Attaches a presentation file's renderers to the tool its filename names.\n *\n * Three tool shapes reach a contribution array and each carries renderers in a\n * different place, so a generated entry cannot spell this merge itself. A\n * native Pi definition takes them at the top level, the portable shape nests\n * them under `pi`, and `definePiTool` owns its own registration, so that one\n * is reached by narrowing the host it registers against. `register` asks for\n * nothing but `registerTool`, which is what makes the last case a two-line\n * shim rather than a proxy over the whole extension API.\n */\nexport function withPiRenderers(tool: PiToolContribution, renderers: PiToolRenderers): PiToolContribution {\n if (!('kind' in tool)) return { ...tool, ...renderers };\n if (tool.kind !== 'pi-tool') return { ...tool, pi: { ...tool.pi, ...renderers } };\n return {\n ...tool,\n register(pi, executionSignal) {\n tool.register(\n { registerTool: (definition) => pi.registerTool({ ...definition, ...renderers }) },\n executionSignal,\n );\n },\n };\n}\n\n/**\n * Splits routed tool files into the two arrays the Pi host keeps apart.\n *\n * A tool that replaces a name Pi already ships is not a second registration,\n * it is a claim the override service arbitrates, and only the winner\n * registers. The folder convention keeps both in `tool/`, because from the\n * author's side both are \"this package provides grep\"; which one it is lives\n * in the value, through `definePiTool(definition, { overrides: true })`.\n *\n * A generated entry therefore cannot decide this from the path, and calls\n * this instead. Every static claim from one package is one atomic set, which\n * is what the override service expects. A live collection stays intact so its\n * subscription continues to drive registration changes.\n */\nexport function piToolContributions(\n source: string,\n items: readonly PiToolContribution[] | PiToolCollection,\n): Pick<PiPluginContributions, 'tools' | 'toolOverrides'> {\n if (!Array.isArray(items)) return { tools: items as PiToolCollection };\n const added: PiToolContribution[] = [];\n const claimed: PiToolDeclaration[] = [];\n for (const item of items) {\n // Only a native declaration may claim a name: a replacement is handed to\n // Pi's registry directly, which the portable shape is not.\n if ('kind' in item && item.kind === 'pi-tool' && item.overrides === true) claimed.push(item);\n else added.push(item);\n }\n if (claimed.length === 0) return { tools: added };\n return {\n tools: added,\n toolOverrides: [{ source, tools: claimed.map((item) => item.name), replacements: claimed }],\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-pi-extension.d.cts","names":[],"sources":["../src/extensions/piExtension.ts"],"mappings":"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"}
1
+ {"version":3,"file":"runtime-pi-extension.d.cts","names":[],"sources":["../src/extensions/piExtension.ts"],"mappings":"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"}