@greppleai/grip 0.0.1 → 0.1.3
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.
- package/CHANGELOG.md +93 -0
- package/README.md +13 -7
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +4 -2
- package/dist/bun/cli.js.map +1 -1
- package/dist/bun/embedded-assets.d.ts +3 -0
- package/dist/bun/embedded-assets.d.ts.map +1 -0
- package/dist/bun/embedded-assets.js +52 -0
- package/dist/bun/embedded-assets.js.map +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +17 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/remote-workspace-protocol/contract.d.ts +13 -4
- package/dist/core/remote-workspace-protocol/contract.d.ts.map +1 -1
- package/dist/core/remote-workspace-protocol/contract.js +17 -1
- package/dist/core/remote-workspace-protocol/contract.js.map +1 -1
- package/dist/core/remote-workspace-protocol/guest-shared-client.d.ts +16 -0
- package/dist/core/remote-workspace-protocol/guest-shared-client.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-client.js +54 -0
- package/dist/core/remote-workspace-protocol/guest-shared-client.js.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.d.ts +23 -0
- package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.js +112 -0
- package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.js.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-uplink.d.ts +30 -0
- package/dist/core/remote-workspace-protocol/guest-shared-uplink.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-uplink.js +243 -0
- package/dist/core/remote-workspace-protocol/guest-shared-uplink.js.map +1 -0
- package/dist/core/remote-workspace-protocol/lima-owner-directory.d.ts +3 -0
- package/dist/core/remote-workspace-protocol/lima-owner-directory.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/lima-owner-directory.js +31 -0
- package/dist/core/remote-workspace-protocol/lima-owner-directory.js.map +1 -0
- package/dist/core/remote-workspace-protocol/lima-owner-lock.d.ts +9 -0
- package/dist/core/remote-workspace-protocol/lima-owner-lock.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/lima-owner-lock.js +89 -0
- package/dist/core/remote-workspace-protocol/lima-owner-lock.js.map +1 -0
- package/dist/core/remote-workspace-protocol/session.d.ts +2 -0
- package/dist/core/remote-workspace-protocol/session.d.ts.map +1 -1
- package/dist/core/remote-workspace-protocol/session.js +3 -0
- package/dist/core/remote-workspace-protocol/session.js.map +1 -1
- package/dist/core/remote-workspace-protocol/shared-transport.d.ts +71 -0
- package/dist/core/remote-workspace-protocol/shared-transport.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/shared-transport.js +68 -0
- package/dist/core/remote-workspace-protocol/shared-transport.js.map +1 -0
- package/dist/core/sandbox/command.d.ts +2 -4
- package/dist/core/sandbox/command.d.ts.map +1 -1
- package/dist/core/sandbox/command.js +24 -46
- package/dist/core/sandbox/command.js.map +1 -1
- package/dist/core/sandbox/config.d.ts +91 -0
- package/dist/core/sandbox/config.d.ts.map +1 -0
- package/dist/core/sandbox/config.js +88 -0
- package/dist/core/sandbox/config.js.map +1 -0
- package/dist/core/sandbox/lima-guest-binary.d.ts +11 -0
- package/dist/core/sandbox/lima-guest-binary.d.ts.map +1 -0
- package/dist/core/sandbox/lima-guest-binary.js +140 -0
- package/dist/core/sandbox/lima-guest-binary.js.map +1 -0
- package/dist/core/sandbox/lima.d.ts +22 -3
- package/dist/core/sandbox/lima.d.ts.map +1 -1
- package/dist/core/sandbox/lima.js +434 -42
- package/dist/core/sandbox/lima.js.map +1 -1
- package/dist/core/sandbox/shared-lima-roots.d.ts +11 -0
- package/dist/core/sandbox/shared-lima-roots.d.ts.map +1 -0
- package/dist/core/sandbox/shared-lima-roots.js +25 -0
- package/dist/core/sandbox/shared-lima-roots.js.map +1 -0
- package/dist/core/sandbox/update-shared-guest-binary.d.ts +8 -0
- package/dist/core/sandbox/update-shared-guest-binary.d.ts.map +1 -0
- package/dist/core/sandbox/update-shared-guest-binary.js +100 -0
- package/dist/core/sandbox/update-shared-guest-binary.js.map +1 -0
- package/dist/core/settings-manager.d.ts +1 -5
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +25 -14
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts +2 -0
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +9 -0
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/operations.d.ts +13 -2
- package/dist/core/tools/operations.d.ts.map +1 -1
- package/dist/core/tools/operations.js +132 -36
- package/dist/core/tools/operations.js.map +1 -1
- package/dist/daemon/activity-budget.d.ts +10 -0
- package/dist/daemon/activity-budget.d.ts.map +1 -0
- package/dist/daemon/activity-budget.js +34 -0
- package/dist/daemon/activity-budget.js.map +1 -0
- package/dist/daemon/config.d.ts +2 -0
- package/dist/daemon/config.d.ts.map +1 -1
- package/dist/daemon/config.js +15 -0
- package/dist/daemon/config.js.map +1 -1
- package/dist/daemon/outbound-budget.d.ts +10 -0
- package/dist/daemon/outbound-budget.d.ts.map +1 -0
- package/dist/daemon/outbound-budget.js +30 -0
- package/dist/daemon/outbound-budget.js.map +1 -0
- package/dist/daemon/security.d.ts.map +1 -1
- package/dist/daemon/security.js +3 -1
- package/dist/daemon/security.js.map +1 -1
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +104 -24
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/shared-connection.d.ts +33 -0
- package/dist/daemon/shared-connection.d.ts.map +1 -0
- package/dist/daemon/shared-connection.js +271 -0
- package/dist/daemon/shared-connection.js.map +1 -0
- package/dist/daemon/shared-revision.d.ts +5 -0
- package/dist/daemon/shared-revision.d.ts.map +1 -0
- package/dist/daemon/shared-revision.js +61 -0
- package/dist/daemon/shared-revision.js.map +1 -0
- package/dist/daemon/shared-roots.d.ts +9 -0
- package/dist/daemon/shared-roots.d.ts.map +1 -0
- package/dist/daemon/shared-roots.js +33 -0
- package/dist/daemon/shared-roots.js.map +1 -0
- package/dist/daemon/workspace-runtime.d.ts +1 -1
- package/dist/daemon/workspace-runtime.d.ts.map +1 -1
- package/dist/daemon/workspace-runtime.js +42 -10
- package/dist/daemon/workspace-runtime.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +5 -3
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +13 -3
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +4 -3
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +5 -4
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +4 -3
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +5 -4
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +4 -3
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/hook-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/hook-execution.js +3 -2
- package/dist/modes/interactive/components/hook-execution.js.map +1 -1
- package/dist/modes/interactive/components/message-card.d.ts +9 -0
- package/dist/modes/interactive/components/message-card.d.ts.map +1 -0
- package/dist/modes/interactive/components/message-card.js +24 -0
- package/dist/modes/interactive/components/message-card.js.map +1 -0
- package/dist/modes/interactive/components/skill-invocation-message.d.ts +4 -3
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +5 -4
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +27 -21
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +3 -2
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +0 -3
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +55 -44
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +3 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +10 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js +1 -1
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/image-resize.d.ts.map +1 -1
- package/dist/utils/image-resize.js +5 -0
- package/dist/utils/image-resize.js.map +1 -1
- package/dist/utils/photon.d.ts.map +1 -1
- package/dist/utils/photon.js +1 -0
- package/dist/utils/photon.js.map +1 -1
- package/docs/daemon.md +6 -4
- package/docs/docs.json +1 -1
- package/docs/index.md +8 -12
- package/docs/packages.md +3 -3
- package/docs/quickstart.md +11 -19
- package/docs/sandbox.md +40 -117
- package/docs/settings.md +12 -11
- package/docs/termux.md +1 -18
- package/docs/usage.md +2 -2
- package/examples/README.md +0 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +5 -6
- package/dist/core/sandbox/docker.d.ts +0 -169
- package/dist/core/sandbox/docker.d.ts.map +0 -1
- package/dist/core/sandbox/docker.js +0 -633
- package/dist/core/sandbox/docker.js.map +0 -1
- package/examples/sandbox-docker/Dockerfile +0 -274
- package/examples/sandbox-docker/README.md +0 -88
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/daemon/server.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAS9E,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE/E,MAAM,WAAW,YAAY;IAC5B,MAAM,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACvC,OAAO,IAAI,mBAAmB,GAAG,SAAS,CAAC;IAC3C,KAAK,IAAI,iBAAiB,CAAC;IAC3B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AA2FD,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,mBAAmB,GAAG,YAAY,CA4TnF","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport {\n\tcreateServer as createHttpServer,\n\ttype Server as HttpServer,\n\ttype IncomingMessage,\n\ttype ServerResponse,\n} from \"node:http\";\nimport { createServer as createHttpsServer } from \"node:https\";\nimport type { AddressInfo, Socket } from \"node:net\";\nimport WebSocket, { type RawData, WebSocketServer } from \"ws\";\nimport type { RemoteWorkspaceServerMessage } from \"../core/remote-workspace-protocol/contract.ts\";\nimport {\n\ttype RemoteWorkspaceProtocolCloseCode,\n\ttype RemoteWorkspaceProtocolCloseReason,\n\ttype RemoteWorkspaceServerHandler,\n\tRemoteWorkspaceServerProtocol,\n} from \"../core/remote-workspace-protocol/session.ts\";\nimport { DAEMON_WEBSOCKET_PATH, type DaemonConfiguration } from \"./config.ts\";\nimport {\n\tcreateDaemonAuthorization,\n\trejectDaemonUpgrade,\n\tvalidateDaemonNetworkPolicy,\n\tvalidateDaemonUpgrade,\n} from \"./security.ts\";\nimport { createDaemonWorkspaceRuntime } from \"./workspace-runtime.ts\";\n\nexport interface DaemonServerAddress {\n\treadonly host: string;\n\treadonly port: number;\n\treadonly secure: boolean;\n\treadonly url: string;\n\treadonly workspaceRoot: string;\n\treadonly workspaceId: string;\n}\n\nexport type DaemonServerState = \"created\" | \"listening\" | \"closing\" | \"closed\";\n\nexport interface DaemonServer {\n\tlisten(): Promise<DaemonServerAddress>;\n\taddress(): DaemonServerAddress | undefined;\n\tstate(): DaemonServerState;\n\treload(): Promise<void>;\n\tclose(): Promise<void>;\n\tforceClose(): Promise<void>;\n}\n\ninterface DaemonConnection {\n\treadonly websocket: WebSocket;\n\treadonly protocol: RemoteWorkspaceServerProtocol;\n\tclosed: Promise<void>;\n}\n\nfunction websocketPayload(data: RawData): Uint8Array {\n\tif (Buffer.isBuffer(data)) return Uint8Array.from(data);\n\tif (Array.isArray(data)) return Uint8Array.from(Buffer.concat(data));\n\treturn Uint8Array.from(new Uint8Array(data));\n}\n\nfunction websocketCloseCode(code: RemoteWorkspaceProtocolCloseCode): number {\n\tswitch (code) {\n\t\tcase \"normal\":\n\t\t\treturn 1000;\n\t\tcase \"protocol_error\":\n\t\t\treturn 1002;\n\t\tcase \"invalid_payload\":\n\t\t\treturn 1007;\n\t\tcase \"policy_violation\":\n\t\t\treturn 1008;\n\t\tcase \"message_too_large\":\n\t\t\treturn 1009;\n\t}\n}\n\nfunction waitForWebSocketClose(websocket: WebSocket): Promise<void> {\n\tif (websocket.readyState === WebSocket.CLOSED) return Promise.resolve();\n\treturn new Promise((resolve) => websocket.once(\"close\", () => resolve()));\n}\n\nfunction waitWithin(promise: Promise<unknown>, timeoutMs: number, forceSignal: AbortSignal): Promise<boolean> {\n\tif (forceSignal.aborted) return Promise.resolve(false);\n\treturn new Promise((resolve) => {\n\t\tlet settled = false;\n\t\tconst finish = (value: boolean) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timer);\n\t\t\tforceSignal.removeEventListener(\"abort\", onForce);\n\t\t\tresolve(value);\n\t\t};\n\t\tconst timer = setTimeout(() => finish(false), timeoutMs);\n\t\tconst onForce = () => finish(false);\n\t\tforceSignal.addEventListener(\"abort\", onForce, { once: true });\n\t\tpromise.then(\n\t\t\t() => finish(true),\n\t\t\t() => finish(false),\n\t\t);\n\t});\n}\nfunction terminateDaemonConnections(connections: Iterable<DaemonConnection>): void {\n\tfor (const connection of connections) connection.websocket.terminate();\n}\n\nfunction beginDaemonConnectionDrain(\n\tconnections: readonly DaemonConnection[],\n\thostDisposal: Promise<void>,\n): Promise<void> {\n\treturn Promise.allSettled([...connections.map((connection) => connection.protocol.beginDrain()), hostDisposal]).then(\n\t\t() => undefined,\n\t);\n}\n\nfunction disconnectDaemonConnections(connections: readonly DaemonConnection[]): Promise<void> {\n\treturn Promise.allSettled(connections.map((connection) => connection.protocol.disconnect())).then(() => undefined);\n}\n\nfunction requestDaemonConnectionClose(connections: readonly DaemonConnection[]): void {\n\tfor (const connection of connections) {\n\t\tif (connection.websocket.readyState === WebSocket.OPEN) {\n\t\t\tconnection.websocket.close(1001, \"Daemon shutting down\");\n\t\t}\n\t}\n}\n\nfunction waitForDaemonConnectionsClosed(connections: readonly DaemonConnection[]): Promise<void> {\n\treturn Promise.allSettled(connections.map((connection) => connection.closed)).then(() => undefined);\n}\n\nfunction destroyDaemonSockets(sockets: ReadonlySet<Socket>): void {\n\tfor (const socket of sockets) socket.destroy();\n}\n\nfunction formatHostForUrl(host: string): string {\n\treturn host.includes(\":\") && !host.startsWith(\"[\") ? `[${host}]` : host;\n}\n\nexport function createDaemonServer(configuration: DaemonConfiguration): DaemonServer {\n\tvalidateDaemonNetworkPolicy(configuration);\n\tconst authorization = createDaemonAuthorization(configuration.token);\n\tconst workspaceId = randomBytes(18).toString(\"base64url\");\n\tconst workspace = Object.freeze({\n\t\tid: workspaceId,\n\t\troot: configuration.workspaceRoot,\n\t\tpathFlavor: process.platform === \"win32\" ? (\"windows\" as const) : (\"posix\" as const),\n\t});\n\tlet runtime = createDaemonWorkspaceRuntime(configuration);\n\tlet runtimeHandler = runtime.createHandler(workspace);\n\tconst handler: RemoteWorkspaceServerHandler = {\n\t\thandleRequest: (request, context) => runtimeHandler.handleRequest(request, context),\n\t\tvalidateToolArguments: (toolName, value) => runtimeHandler.validateToolArguments(toolName, value),\n\t};\n\tconst requestListener = (_request: IncomingMessage, response: ServerResponse) => {\n\t\tresponse.writeHead(404, { \"content-type\": \"text/plain\" });\n\t\tresponse.end(\"Not found\\n\");\n\t};\n\tconst server: HttpServer = configuration.tls\n\t\t? createHttpsServer(\n\t\t\t\t{\n\t\t\t\t\tcert: configuration.tls.cert,\n\t\t\t\t\tkey: configuration.tls.key,\n\t\t\t\t\tpassphrase: configuration.tls.passphrase,\n\t\t\t\t\tminVersion: \"TLSv1.2\",\n\t\t\t\t},\n\t\t\t\trequestListener,\n\t\t\t)\n\t\t: createHttpServer(requestListener);\n\tconst websocketServer = new WebSocketServer({\n\t\tnoServer: true,\n\t\tclientTracking: false,\n\t\tmaxPayload: configuration.protocolLimits.maxMessageBytes,\n\t\tperMessageDeflate: false,\n\t\tallowSynchronousEvents: false,\n\t\tskipUTF8Validation: false,\n\t});\n\tconst sockets = new Set<Socket>();\n\tconst pendingSockets = new Set<Socket>();\n\tconst pendingSocketTimers = new Map<Socket, NodeJS.Timeout>();\n\tconst pendingSocketDeadlines = new Map<Socket, number>();\n\tconst connections = new Set<DaemonConnection>();\n\tlet catalog = runtime.catalog;\n\tlet reloadQueue: Promise<void> = Promise.resolve();\n\tconst forceController = new AbortController();\n\tlet currentAddress: DaemonServerAddress | undefined;\n\tlet listenPromise: Promise<DaemonServerAddress> | undefined;\n\tlet rejectListen: ((error: Error) => void) | undefined;\n\tlet closePromise: Promise<void> | undefined;\n\tlet serverState: DaemonServerState = \"created\";\n\n\tconst stopListener = (): Promise<void> => {\n\t\tif (!server.listening && !listenPromise) return Promise.resolve();\n\t\treturn new Promise((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tconst cleanup = () => {\n\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\tserver.off(\"close\", onClose);\n\t\t\t};\n\t\t\tconst finish = () => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst closeListener = () => server.close(() => finish());\n\t\t\tconst onListening = () => closeListener();\n\t\t\tconst onError = () => finish();\n\t\t\tconst onClose = () => finish();\n\t\t\tserver.once(\"error\", onError);\n\t\t\tserver.once(\"close\", onClose);\n\t\t\tif (server.listening) closeListener();\n\t\t\telse server.once(\"listening\", onListening);\n\t\t});\n\t};\n\n\tserver.on(\"connection\", (socket: Socket) => {\n\t\tif (serverState !== \"listening\" || pendingSockets.size >= configuration.maxPendingConnections) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tsockets.add(socket);\n\t\tpendingSockets.add(socket);\n\t\tconst pendingTimer = setTimeout(() => socket.destroy(), configuration.handshakeTimeoutMs);\n\t\tpendingTimer.unref?.();\n\t\tpendingSocketTimers.set(socket, pendingTimer);\n\t\tpendingSocketDeadlines.set(socket, performance.now() + configuration.handshakeTimeoutMs);\n\t\tsocket.once(\"close\", () => {\n\t\t\tsockets.delete(socket);\n\t\t\tpendingSockets.delete(socket);\n\t\t\tconst timer = pendingSocketTimers.get(socket);\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tpendingSocketTimers.delete(socket);\n\t\t\tpendingSocketDeadlines.delete(socket);\n\t\t});\n\t});\n\n\tserver.on(\"upgrade\", (request, socket, head) => {\n\t\tconst netSocket = socket as Socket;\n\t\tconst decision = validateDaemonUpgrade(request, configuration, authorization);\n\t\tif (!decision.accepted) {\n\t\t\trejectDaemonUpgrade(netSocket, decision);\n\t\t\treturn;\n\t\t}\n\t\tif (serverState !== \"listening\" || connections.size >= configuration.maxConnections) {\n\t\t\trejectDaemonUpgrade(netSocket, { accepted: false, status: 503, message: \"Service unavailable\" });\n\t\t\treturn;\n\t\t}\n\t\tconst handshakeDeadline = pendingSocketDeadlines.get(netSocket) ?? performance.now();\n\t\tpendingSockets.delete(netSocket);\n\t\tconst pendingTimer = pendingSocketTimers.get(netSocket);\n\t\tif (pendingTimer) clearTimeout(pendingTimer);\n\t\tpendingSocketTimers.delete(netSocket);\n\t\tpendingSocketDeadlines.delete(netSocket);\n\t\twebsocketServer.handleUpgrade(request, socket, head, (websocket) => {\n\t\t\tif (serverState !== \"listening\") {\n\t\t\t\twebsocket.terminate();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst remainingHandshakeMs = Math.max(0, handshakeDeadline - performance.now());\n\t\t\tconst handshakeTimer = setTimeout(() => {\n\t\t\t\twebsocket.terminate();\n\t\t\t}, remainingHandshakeMs);\n\t\t\thandshakeTimer.unref?.();\n\t\t\tlet protocol: RemoteWorkspaceServerProtocol;\n\t\t\tconst transport = {\n\t\t\t\tsend(message: RemoteWorkspaceServerMessage): Promise<void> {\n\t\t\t\t\tif (websocket.readyState !== WebSocket.OPEN) return Promise.reject(new Error(\"WebSocket is not open\"));\n\t\t\t\t\tconst serialized = JSON.stringify(message);\n\t\t\t\t\tconst bytes = Buffer.byteLength(serialized, \"utf8\");\n\t\t\t\t\tif (websocket.bufferedAmount + bytes > configuration.protocolLimits.maxPendingOutboundBytes) {\n\t\t\t\t\t\treturn Promise.reject(new Error(\"WebSocket outbound buffer exceeds daemon limits\"));\n\t\t\t\t\t}\n\t\t\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\t\t\twebsocket.send(serialized, { binary: false, compress: false }, (error) => {\n\t\t\t\t\t\t\tif (error) reject(error);\n\t\t\t\t\t\t\telse resolve();\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tclose(reason: RemoteWorkspaceProtocolCloseReason): Promise<void> {\n\t\t\t\t\tif (websocket.readyState === WebSocket.CLOSED) return Promise.resolve();\n\t\t\t\t\twebsocket.close(websocketCloseCode(reason.code), reason.message);\n\t\t\t\t\treturn waitForWebSocketClose(websocket);\n\t\t\t\t},\n\t\t\t};\n\t\t\tprotocol = new RemoteWorkspaceServerProtocol(transport, handler, {\n\t\t\t\tworkspace,\n\t\t\t\tcatalog,\n\t\t\t\tcapabilities: runtime.capabilities,\n\t\t\t\tlimits: configuration.protocolLimits,\n\t\t\t\tbackendLabel: \"grip-workspace-daemon\",\n\t\t\t});\n\t\t\tconst connection: DaemonConnection = {\n\t\t\t\twebsocket,\n\t\t\t\tprotocol,\n\t\t\t\tclosed: Promise.resolve(),\n\t\t\t};\n\t\t\tconnection.closed = waitForWebSocketClose(websocket).then(async () => {\n\t\t\t\tclearTimeout(handshakeTimer);\n\t\t\t\tconnections.delete(connection);\n\t\t\t\tawait protocol.disconnect().catch(() => undefined);\n\t\t\t});\n\t\t\tconnections.add(connection);\n\t\t\twebsocket.on(\"message\", (data, isBinary) => {\n\t\t\t\tif (isBinary) {\n\t\t\t\t\twebsocket.close(1003, \"Text protocol required\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tvoid protocol\n\t\t\t\t\t.receive(websocketPayload(data))\n\t\t\t\t\t.then(() => {\n\t\t\t\t\t\tif (protocol.handshakeComplete) clearTimeout(handshakeTimer);\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => undefined);\n\t\t\t});\n\t\t\twebsocket.on(\"error\", () => undefined);\n\t\t});\n\t});\n\n\tconst api: DaemonServer = {\n\t\tlisten(): Promise<DaemonServerAddress> {\n\t\t\tif (serverState === \"closing\" || serverState === \"closed\")\n\t\t\t\treturn Promise.reject(new Error(\"Daemon server is closing\"));\n\t\t\tif (currentAddress) return Promise.resolve(currentAddress);\n\t\t\tif (listenPromise) return listenPromise;\n\t\t\tlistenPromise = new Promise((resolve, reject) => {\n\t\t\t\trejectListen = reject;\n\t\t\t\tconst onError = (error: Error) => {\n\t\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\t\tlistenPromise = undefined;\n\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\t\t\t\tconst onListening = () => {\n\t\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\t\tif (serverState !== \"created\") {\n\t\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\t\treject(new Error(\"Daemon server closed before listening completed\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst address = server.address();\n\t\t\t\t\tif (!address || typeof address === \"string\") {\n\t\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\t\treject(new Error(\"Daemon listener did not return a TCP address\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tserverState = \"listening\";\n\t\t\t\t\tconst port = (address as AddressInfo).port;\n\t\t\t\t\tcurrentAddress = Object.freeze({\n\t\t\t\t\t\thost: configuration.host,\n\t\t\t\t\t\tport,\n\t\t\t\t\t\tsecure: configuration.tls !== undefined,\n\t\t\t\t\t\turl: `${configuration.tls ? \"wss\" : \"ws\"}://${formatHostForUrl(configuration.host)}:${port}${DAEMON_WEBSOCKET_PATH}`,\n\t\t\t\t\t\tworkspaceRoot: configuration.workspaceRoot,\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t});\n\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\tresolve(currentAddress);\n\t\t\t\t};\n\t\t\t\tserver.once(\"error\", onError);\n\t\t\t\tserver.once(\"listening\", onListening);\n\t\t\t\tserver.listen(configuration.port, configuration.host);\n\t\t\t});\n\t\t\treturn listenPromise;\n\t\t},\n\t\taddress(): DaemonServerAddress | undefined {\n\t\t\treturn currentAddress ? { ...currentAddress } : undefined;\n\t\t},\n\t\tstate(): DaemonServerState {\n\t\t\treturn serverState;\n\t\t},\n\t\treload(): Promise<void> {\n\t\t\tif (serverState !== \"listening\") return Promise.reject(new Error(\"Daemon server is not listening\"));\n\t\t\treloadQueue = reloadQueue.then(async () => {\n\t\t\t\tif (serverState !== \"listening\") throw new Error(\"Daemon server is not listening\");\n\t\t\t\tconst candidate = createDaemonWorkspaceRuntime(configuration);\n\t\t\t\tconst candidateCatalog = { ...candidate.catalog, generation: catalog.generation + 1 };\n\t\t\t\tconst previous = runtime;\n\t\t\t\tconst previousCatalog = catalog;\n\t\t\t\truntime = candidate;\n\t\t\t\truntimeHandler = candidate.createHandler(workspace);\n\t\t\t\tcatalog = candidateCatalog;\n\t\t\t\ttry {\n\t\t\t\t\tawait Promise.all([...connections].map((connection) => connection.protocol.publishCatalog(catalog)));\n\t\t\t\t} catch (error) {\n\t\t\t\t\truntime = previous;\n\t\t\t\t\truntimeHandler = previous.createHandler(workspace);\n\t\t\t\t\tcatalog = previousCatalog;\n\t\t\t\t\tawait Promise.allSettled([...connections].map((connection) => connection.protocol.disconnect()));\n\t\t\t\t\tawait candidate.dispose();\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tawait previous.retire();\n\t\t\t});\n\t\t\treturn reloadQueue;\n\t\t},\n\t\tclose(): Promise<void> {\n\t\t\tif (closePromise) return closePromise;\n\t\t\tserverState = \"closing\";\n\t\t\trejectListen?.(new Error(\"Daemon server closed before listening completed\"));\n\t\t\trejectListen = undefined;\n\t\t\tconst deadline = performance.now() + configuration.shutdownTimeoutMs;\n\t\t\tconst remainingMs = () => Math.max(0, deadline - performance.now());\n\t\t\tclosePromise = (async () => {\n\t\t\t\tconst listenerClose = stopListener();\n\t\t\t\tconst snapshot = [...connections];\n\t\t\t\tawait waitWithin(\n\t\t\t\t\treloadQueue.catch(() => undefined),\n\t\t\t\t\tremainingMs(),\n\t\t\t\t\tforceController.signal,\n\t\t\t\t);\n\t\t\t\tconst hostDisposal = runtime.dispose();\n\t\t\t\tconst drained = await waitWithin(\n\t\t\t\t\tbeginDaemonConnectionDrain(snapshot, hostDisposal),\n\t\t\t\t\tremainingMs(),\n\t\t\t\t\tforceController.signal,\n\t\t\t\t);\n\t\t\t\tif (!drained) terminateDaemonConnections(snapshot);\n\t\t\t\tawait waitWithin(disconnectDaemonConnections(snapshot), remainingMs(), forceController.signal);\n\t\t\t\trequestDaemonConnectionClose(snapshot);\n\t\t\t\tconst socketsClosed = await waitWithin(\n\t\t\t\t\twaitForDaemonConnectionsClosed(snapshot),\n\t\t\t\t\tremainingMs(),\n\t\t\t\t\tforceController.signal,\n\t\t\t\t);\n\t\t\t\tif (!socketsClosed) terminateDaemonConnections(snapshot);\n\t\t\t\tdestroyDaemonSockets(sockets);\n\t\t\t\tawait listenerClose;\n\t\t\t\tconnections.clear();\n\t\t\t})().finally(() => {\n\t\t\t\tterminateDaemonConnections(connections);\n\t\t\t\tdestroyDaemonSockets(sockets);\n\t\t\t\tfor (const timer of pendingSocketTimers.values()) clearTimeout(timer);\n\t\t\t\tpendingSocketTimers.clear();\n\t\t\t\tpendingSocketDeadlines.clear();\n\t\t\t\tpendingSockets.clear();\n\t\t\t\tconnections.clear();\n\t\t\t\tcurrentAddress = undefined;\n\t\t\t\tserverState = \"closed\";\n\t\t\t});\n\t\t\treturn closePromise;\n\t\t},\n\t\tforceClose(): Promise<void> {\n\t\t\tif (serverState === \"closed\") return closePromise ?? Promise.resolve();\n\t\t\tserverState = \"closing\";\n\t\t\tforceController.abort();\n\t\t\tcurrentAddress = undefined;\n\t\t\tterminateDaemonConnections(connections);\n\t\t\tdestroyDaemonSockets(sockets);\n\t\t\treturn api.close();\n\t\t},\n\t};\n\treturn api;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/daemon/server.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAY9E,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE/E,MAAM,WAAW,YAAY;IAC5B,MAAM,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACvC,OAAO,IAAI,mBAAmB,GAAG,SAAS,CAAC;IAC3C,KAAK,IAAI,iBAAiB,CAAC;IAC3B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AA2FD,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,mBAAmB,GAAG,YAAY,CA4YnF","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport {\n\tcreateServer as createHttpServer,\n\ttype Server as HttpServer,\n\ttype IncomingMessage,\n\ttype ServerResponse,\n} from \"node:http\";\nimport { createServer as createHttpsServer } from \"node:https\";\nimport type { AddressInfo, Socket } from \"node:net\";\nimport WebSocket, { type RawData, WebSocketServer } from \"ws\";\nimport type { RemoteWorkspaceServerMessage } from \"../core/remote-workspace-protocol/contract.ts\";\nimport {\n\ttype RemoteWorkspaceProtocolCloseCode,\n\ttype RemoteWorkspaceProtocolCloseReason,\n\tRemoteWorkspaceRequestError,\n\ttype RemoteWorkspaceServerHandler,\n\tRemoteWorkspaceServerProtocol,\n} from \"../core/remote-workspace-protocol/session.ts\";\nimport {\n\tSHARED_WORKSPACE_WEBSOCKET_PROTOCOL,\n\ttype SharedWorkspaceServerFrame,\n} from \"../core/remote-workspace-protocol/shared-transport.ts\";\nimport { DaemonActivityBudget } from \"./activity-budget.ts\";\nimport { DAEMON_WEBSOCKET_PATH, type DaemonConfiguration } from \"./config.ts\";\nimport { DaemonOutboundBudget } from \"./outbound-budget.ts\";\nimport {\n\tcreateDaemonAuthorization,\n\trejectDaemonUpgrade,\n\tvalidateDaemonNetworkPolicy,\n\tvalidateDaemonUpgrade,\n} from \"./security.ts\";\nimport { SharedDaemonConnection } from \"./shared-connection.ts\";\nimport { SharedWorkspaceRoots } from \"./shared-roots.ts\";\nimport { createDaemonWorkspaceRuntime } from \"./workspace-runtime.ts\";\n\nexport interface DaemonServerAddress {\n\treadonly host: string;\n\treadonly port: number;\n\treadonly secure: boolean;\n\treadonly url: string;\n\treadonly workspaceRoot: string;\n\treadonly workspaceId: string;\n}\n\nexport type DaemonServerState = \"created\" | \"listening\" | \"closing\" | \"closed\";\n\nexport interface DaemonServer {\n\tlisten(): Promise<DaemonServerAddress>;\n\taddress(): DaemonServerAddress | undefined;\n\tstate(): DaemonServerState;\n\treload(): Promise<void>;\n\tclose(): Promise<void>;\n\tforceClose(): Promise<void>;\n}\n\ninterface DaemonConnection {\n\treadonly websocket: WebSocket;\n\treadonly protocol: RemoteWorkspaceServerProtocol | SharedDaemonConnection;\n\tclosed: Promise<void>;\n}\n\nfunction websocketPayload(data: RawData): Uint8Array {\n\tif (Buffer.isBuffer(data)) return Uint8Array.from(data);\n\tif (Array.isArray(data)) return Uint8Array.from(Buffer.concat(data));\n\treturn Uint8Array.from(new Uint8Array(data));\n}\n\nfunction websocketCloseCode(code: RemoteWorkspaceProtocolCloseCode): number {\n\tswitch (code) {\n\t\tcase \"normal\":\n\t\t\treturn 1000;\n\t\tcase \"protocol_error\":\n\t\t\treturn 1002;\n\t\tcase \"invalid_payload\":\n\t\t\treturn 1007;\n\t\tcase \"policy_violation\":\n\t\t\treturn 1008;\n\t\tcase \"message_too_large\":\n\t\t\treturn 1009;\n\t}\n}\n\nfunction waitForWebSocketClose(websocket: WebSocket): Promise<void> {\n\tif (websocket.readyState === WebSocket.CLOSED) return Promise.resolve();\n\treturn new Promise((resolve) => websocket.once(\"close\", () => resolve()));\n}\n\nfunction waitWithin(promise: Promise<unknown>, timeoutMs: number, forceSignal: AbortSignal): Promise<boolean> {\n\tif (forceSignal.aborted) return Promise.resolve(false);\n\treturn new Promise((resolve) => {\n\t\tlet settled = false;\n\t\tconst finish = (value: boolean) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timer);\n\t\t\tforceSignal.removeEventListener(\"abort\", onForce);\n\t\t\tresolve(value);\n\t\t};\n\t\tconst timer = setTimeout(() => finish(false), timeoutMs);\n\t\tconst onForce = () => finish(false);\n\t\tforceSignal.addEventListener(\"abort\", onForce, { once: true });\n\t\tpromise.then(\n\t\t\t() => finish(true),\n\t\t\t() => finish(false),\n\t\t);\n\t});\n}\nfunction terminateDaemonConnections(connections: Iterable<DaemonConnection>): void {\n\tfor (const connection of connections) connection.websocket.terminate();\n}\n\nfunction beginDaemonConnectionDrain(\n\tconnections: readonly DaemonConnection[],\n\thostDisposal: Promise<void>,\n): Promise<void> {\n\treturn Promise.allSettled([...connections.map((connection) => connection.protocol.beginDrain()), hostDisposal]).then(\n\t\t() => undefined,\n\t);\n}\n\nfunction disconnectDaemonConnections(connections: readonly DaemonConnection[]): Promise<void> {\n\treturn Promise.allSettled(connections.map((connection) => connection.protocol.disconnect())).then(() => undefined);\n}\n\nfunction requestDaemonConnectionClose(connections: readonly DaemonConnection[]): void {\n\tfor (const connection of connections) {\n\t\tif (connection.websocket.readyState === WebSocket.OPEN) {\n\t\t\tconnection.websocket.close(1001, \"Daemon shutting down\");\n\t\t}\n\t}\n}\n\nfunction waitForDaemonConnectionsClosed(connections: readonly DaemonConnection[]): Promise<void> {\n\treturn Promise.allSettled(connections.map((connection) => connection.closed)).then(() => undefined);\n}\n\nfunction destroyDaemonSockets(sockets: ReadonlySet<Socket>): void {\n\tfor (const socket of sockets) socket.destroy();\n}\n\nfunction formatHostForUrl(host: string): string {\n\treturn host.includes(\":\") && !host.startsWith(\"[\") ? `[${host}]` : host;\n}\n\nexport function createDaemonServer(configuration: DaemonConfiguration): DaemonServer {\n\tvalidateDaemonNetworkPolicy(configuration);\n\tconst authorization = createDaemonAuthorization(configuration.token);\n\tconst activityBudget = new DaemonActivityBudget(configuration);\n\tconst workspaceId = randomBytes(18).toString(\"base64url\");\n\tconst workspace = Object.freeze({\n\t\tid: workspaceId,\n\t\troot: configuration.workspaceRoot,\n\t\tpathFlavor: process.platform === \"win32\" ? (\"windows\" as const) : (\"posix\" as const),\n\t});\n\tlet runtime = createDaemonWorkspaceRuntime(configuration);\n\tlet runtimeHandler = runtime.createHandler(workspace);\n\tconst handler: RemoteWorkspaceServerHandler = {\n\t\thandleRequest: (request, context) => {\n\t\t\tif (typeof request.params === \"object\" && request.params !== null && \"expectedRevision\" in request.params) {\n\t\t\t\tthrow new RemoteWorkspaceRequestError({\n\t\t\t\t\tcode: \"invalid_request\",\n\t\t\t\t\tmessage: \"Revision preconditions require the v2 shared workspace protocol\",\n\t\t\t\t\texecutionState: \"not_started\",\n\t\t\t\t\tretryable: false,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn activityBudget.run(request.method, () => runtimeHandler.handleRequest(request, context));\n\t\t},\n\t\tvalidateToolArguments: (toolName, value) => runtimeHandler.validateToolArguments(toolName, value),\n\t};\n\tconst requestListener = (_request: IncomingMessage, response: ServerResponse) => {\n\t\tresponse.writeHead(404, { \"content-type\": \"text/plain\" });\n\t\tresponse.end(\"Not found\\n\");\n\t};\n\tconst server: HttpServer = configuration.tls\n\t\t? createHttpsServer(\n\t\t\t\t{\n\t\t\t\t\tcert: configuration.tls.cert,\n\t\t\t\t\tkey: configuration.tls.key,\n\t\t\t\t\tpassphrase: configuration.tls.passphrase,\n\t\t\t\t\tminVersion: \"TLSv1.2\",\n\t\t\t\t},\n\t\t\t\trequestListener,\n\t\t\t)\n\t\t: createHttpServer(requestListener);\n\tconst websocketServer = new WebSocketServer({\n\t\tnoServer: true,\n\t\tclientTracking: false,\n\t\tmaxPayload: configuration.protocolLimits.maxMessageBytes,\n\t\tperMessageDeflate: false,\n\t\tallowSynchronousEvents: false,\n\t\tskipUTF8Validation: false,\n\t});\n\tconst sockets = new Set<Socket>();\n\tconst pendingSockets = new Set<Socket>();\n\tconst pendingSocketTimers = new Map<Socket, NodeJS.Timeout>();\n\tconst pendingSocketDeadlines = new Map<Socket, number>();\n\tconst connections = new Set<DaemonConnection>();\n\tlet sharedRoots: SharedWorkspaceRoots | undefined;\n\tlet catalog = runtime.catalog;\n\tlet reloadQueue: Promise<void> = Promise.resolve();\n\tconst forceController = new AbortController();\n\tlet currentAddress: DaemonServerAddress | undefined;\n\tlet listenPromise: Promise<DaemonServerAddress> | undefined;\n\tlet rejectListen: ((error: Error) => void) | undefined;\n\tlet closePromise: Promise<void> | undefined;\n\tlet serverState: DaemonServerState = \"created\";\n\n\tconst stopListener = (): Promise<void> => {\n\t\tif (!server.listening && !listenPromise) return Promise.resolve();\n\t\treturn new Promise((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tconst cleanup = () => {\n\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\tserver.off(\"close\", onClose);\n\t\t\t};\n\t\t\tconst finish = () => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst closeListener = () => server.close(() => finish());\n\t\t\tconst onListening = () => closeListener();\n\t\t\tconst onError = () => finish();\n\t\t\tconst onClose = () => finish();\n\t\t\tserver.once(\"error\", onError);\n\t\t\tserver.once(\"close\", onClose);\n\t\t\tif (server.listening) closeListener();\n\t\t\telse server.once(\"listening\", onListening);\n\t\t});\n\t};\n\n\tserver.on(\"connection\", (socket: Socket) => {\n\t\tif (serverState !== \"listening\" || pendingSockets.size >= configuration.maxPendingConnections) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tsockets.add(socket);\n\t\tpendingSockets.add(socket);\n\t\tconst pendingTimer = setTimeout(() => socket.destroy(), configuration.handshakeTimeoutMs);\n\t\tpendingTimer.unref?.();\n\t\tpendingSocketTimers.set(socket, pendingTimer);\n\t\tpendingSocketDeadlines.set(socket, performance.now() + configuration.handshakeTimeoutMs);\n\t\tsocket.once(\"close\", () => {\n\t\t\tsockets.delete(socket);\n\t\t\tpendingSockets.delete(socket);\n\t\t\tconst timer = pendingSocketTimers.get(socket);\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tpendingSocketTimers.delete(socket);\n\t\t\tpendingSocketDeadlines.delete(socket);\n\t\t});\n\t});\n\n\tserver.on(\"upgrade\", (request, socket, head) => {\n\t\tconst netSocket = socket as Socket;\n\t\tconst decision = validateDaemonUpgrade(request, configuration, authorization);\n\t\tif (!decision.accepted) {\n\t\t\trejectDaemonUpgrade(netSocket, decision);\n\t\t\treturn;\n\t\t}\n\t\tif (serverState !== \"listening\" || connections.size >= configuration.maxConnections) {\n\t\t\trejectDaemonUpgrade(netSocket, { accepted: false, status: 503, message: \"Service unavailable\" });\n\t\t\treturn;\n\t\t}\n\t\tconst handshakeDeadline = pendingSocketDeadlines.get(netSocket) ?? performance.now();\n\t\tpendingSockets.delete(netSocket);\n\t\tconst pendingTimer = pendingSocketTimers.get(netSocket);\n\t\tif (pendingTimer) clearTimeout(pendingTimer);\n\t\tpendingSocketTimers.delete(netSocket);\n\t\tpendingSocketDeadlines.delete(netSocket);\n\t\twebsocketServer.handleUpgrade(request, socket, head, (websocket) => {\n\t\t\tif (serverState !== \"listening\") {\n\t\t\t\twebsocket.terminate();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst remainingHandshakeMs = Math.max(0, handshakeDeadline - performance.now());\n\t\t\tconst handshakeTimer = setTimeout(() => {\n\t\t\t\twebsocket.terminate();\n\t\t\t}, remainingHandshakeMs);\n\t\t\thandshakeTimer.unref?.();\n\t\t\tlet protocol: RemoteWorkspaceServerProtocol;\n\t\t\tconst outboundBudget = new DaemonOutboundBudget(\n\t\t\t\tconfiguration.protocolLimits.maxPendingOutboundBytes,\n\t\t\t\tconfiguration.protocolLimits.maxPendingOutboundMessages,\n\t\t\t);\n\t\t\tconst transport = {\n\t\t\t\tsend(message: RemoteWorkspaceServerMessage | SharedWorkspaceServerFrame): Promise<void> {\n\t\t\t\t\tif (websocket.readyState !== WebSocket.OPEN) return Promise.reject(new Error(\"WebSocket is not open\"));\n\t\t\t\t\tconst serialized = JSON.stringify(message);\n\t\t\t\t\tconst bytes = Buffer.byteLength(serialized, \"utf8\");\n\t\t\t\t\tlet release: () => void;\n\t\t\t\t\ttry {\n\t\t\t\t\t\trelease = outboundBudget.reserve(bytes, websocket.bufferedAmount);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t// No channel can safely continue after a physical send is dropped.\n\t\t\t\t\t\twebsocket.terminate();\n\t\t\t\t\t\treturn Promise.reject(error);\n\t\t\t\t\t}\n\t\t\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\t// A stalled callback must not pin physical reservations indefinitely. A\n\t\t\t\t\t\t// timed-out send makes the entire uplink uncertain, not just its channel.\n\t\t\t\t\t\tconst deadline = setTimeout(() => {\n\t\t\t\t\t\t\twebsocket.terminate();\n\t\t\t\t\t\t\treject(new Error(\"WebSocket outbound send timed out\"));\n\t\t\t\t\t\t}, configuration.protocolLimits.maxTransportSendMs);\n\t\t\t\t\t\tdeadline.unref?.();\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\twebsocket.send(serialized, { binary: false, compress: false }, (error) => {\n\t\t\t\t\t\t\t\tclearTimeout(deadline);\n\t\t\t\t\t\t\t\tif (error) {\n\t\t\t\t\t\t\t\t\twebsocket.terminate();\n\t\t\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t\t\t} else resolve();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tclearTimeout(deadline);\n\t\t\t\t\t\t\twebsocket.terminate();\n\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t}).finally(release);\n\t\t\t\t},\n\t\t\t\tclose(reason: RemoteWorkspaceProtocolCloseReason): Promise<void> {\n\t\t\t\t\tif (websocket.readyState === WebSocket.CLOSED) return Promise.resolve();\n\t\t\t\t\twebsocket.close(websocketCloseCode(reason.code), reason.message);\n\t\t\t\t\treturn waitForWebSocketClose(websocket);\n\t\t\t\t},\n\t\t\t};\n\t\t\tif (websocket.protocol === SHARED_WORKSPACE_WEBSOCKET_PROTOCOL) {\n\t\t\t\tif (!sharedRoots) {\n\t\t\t\t\twebsocket.close(1008, \"Shared parent unavailable\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst shared = new SharedDaemonConnection(\n\t\t\t\t\tconfiguration,\n\t\t\t\t\tsharedRoots,\n\t\t\t\t\ttransport.send,\n\t\t\t\t\t(code, reason) => {\n\t\t\t\t\t\tif (websocket.readyState === WebSocket.OPEN) websocket.close(code, reason);\n\t\t\t\t\t},\n\t\t\t\t\tactivityBudget,\n\t\t\t\t);\n\t\t\t\tconst connection: DaemonConnection = { websocket, protocol: shared, closed: Promise.resolve() };\n\t\t\t\tconnection.closed = waitForWebSocketClose(websocket).then(async () => {\n\t\t\t\t\tclearTimeout(handshakeTimer);\n\t\t\t\t\tconnections.delete(connection);\n\t\t\t\t\tawait shared.disconnect().catch(() => undefined);\n\t\t\t\t});\n\t\t\t\tconnections.add(connection);\n\t\t\t\twebsocket.on(\"message\", (data, isBinary) => {\n\t\t\t\t\tif (isBinary) {\n\t\t\t\t\t\twebsocket.close(1003, \"Text protocol required\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tvoid shared.receive(websocketPayload(data)).then(() => {\n\t\t\t\t\t\tif (shared.handshakeComplete) clearTimeout(handshakeTimer);\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\twebsocket.on(\"error\", () => undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tprotocol = new RemoteWorkspaceServerProtocol(transport, handler, {\n\t\t\t\tworkspace,\n\t\t\t\tcatalog,\n\t\t\t\tcapabilities: runtime.capabilities,\n\t\t\t\tlimits: configuration.protocolLimits,\n\t\t\t\tbackendLabel: \"grip-workspace-daemon\",\n\t\t\t});\n\t\t\tconst connection: DaemonConnection = {\n\t\t\t\twebsocket,\n\t\t\t\tprotocol,\n\t\t\t\tclosed: Promise.resolve(),\n\t\t\t};\n\t\t\tconnection.closed = waitForWebSocketClose(websocket).then(async () => {\n\t\t\t\tclearTimeout(handshakeTimer);\n\t\t\t\tconnections.delete(connection);\n\t\t\t\tawait protocol.disconnect().catch(() => undefined);\n\t\t\t});\n\t\t\tconnections.add(connection);\n\t\t\twebsocket.on(\"message\", (data, isBinary) => {\n\t\t\t\tif (isBinary) {\n\t\t\t\t\twebsocket.close(1003, \"Text protocol required\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tvoid protocol\n\t\t\t\t\t.receive(websocketPayload(data))\n\t\t\t\t\t.then(() => {\n\t\t\t\t\t\tif (protocol.handshakeComplete) clearTimeout(handshakeTimer);\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => undefined);\n\t\t\t});\n\t\t\twebsocket.on(\"error\", () => undefined);\n\t\t});\n\t});\n\n\tconst api: DaemonServer = {\n\t\tlisten(): Promise<DaemonServerAddress> {\n\t\t\tif (serverState === \"closing\" || serverState === \"closed\")\n\t\t\t\treturn Promise.reject(new Error(\"Daemon server is closing\"));\n\t\t\tif (currentAddress) return Promise.resolve(currentAddress);\n\t\t\tif (listenPromise) return listenPromise;\n\t\t\tif (configuration.sharedParentRoot && !sharedRoots) {\n\t\t\t\treturn SharedWorkspaceRoots.fromParent(configuration.sharedParentRoot).then((roots) => {\n\t\t\t\t\tsharedRoots = roots;\n\t\t\t\t\treturn api.listen();\n\t\t\t\t});\n\t\t\t}\n\t\t\tlistenPromise = new Promise((resolve, reject) => {\n\t\t\t\trejectListen = reject;\n\t\t\t\tconst onError = (error: Error) => {\n\t\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\t\tlistenPromise = undefined;\n\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\t\t\t\tconst onListening = () => {\n\t\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\t\tif (serverState !== \"created\") {\n\t\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\t\treject(new Error(\"Daemon server closed before listening completed\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst address = server.address();\n\t\t\t\t\tif (!address || typeof address === \"string\") {\n\t\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\t\treject(new Error(\"Daemon listener did not return a TCP address\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tserverState = \"listening\";\n\t\t\t\t\tconst port = (address as AddressInfo).port;\n\t\t\t\t\tcurrentAddress = Object.freeze({\n\t\t\t\t\t\thost: configuration.host,\n\t\t\t\t\t\tport,\n\t\t\t\t\t\tsecure: configuration.tls !== undefined,\n\t\t\t\t\t\turl: `${configuration.tls ? \"wss\" : \"ws\"}://${formatHostForUrl(configuration.host)}:${port}${DAEMON_WEBSOCKET_PATH}`,\n\t\t\t\t\t\tworkspaceRoot: configuration.workspaceRoot,\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t});\n\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\tresolve(currentAddress);\n\t\t\t\t};\n\t\t\t\tserver.once(\"error\", onError);\n\t\t\t\tserver.once(\"listening\", onListening);\n\t\t\t\tserver.listen(configuration.port, configuration.host);\n\t\t\t});\n\t\t\treturn listenPromise;\n\t\t},\n\t\taddress(): DaemonServerAddress | undefined {\n\t\t\treturn currentAddress ? { ...currentAddress } : undefined;\n\t\t},\n\t\tstate(): DaemonServerState {\n\t\t\treturn serverState;\n\t\t},\n\t\treload(): Promise<void> {\n\t\t\tif (serverState !== \"listening\") return Promise.reject(new Error(\"Daemon server is not listening\"));\n\t\t\treloadQueue = reloadQueue\n\t\t\t\t.catch(() => undefined)\n\t\t\t\t.then(async () => {\n\t\t\t\t\tif (serverState !== \"listening\") throw new Error(\"Daemon server is not listening\");\n\t\t\t\t\tconst candidate = createDaemonWorkspaceRuntime(configuration);\n\t\t\t\t\tconst candidateCatalog = { ...candidate.catalog, generation: catalog.generation + 1 };\n\t\t\t\t\tconst previous = runtime;\n\t\t\t\t\truntime = candidate;\n\t\t\t\t\truntimeHandler = candidate.createHandler(workspace);\n\t\t\t\t\tcatalog = candidateCatalog;\n\t\t\t\t\tconst snapshot = [...connections];\n\t\t\t\t\tconst outcomes = await Promise.allSettled(\n\t\t\t\t\t\tsnapshot.map(async (connection) => {\n\t\t\t\t\t\t\tif (connection.protocol instanceof SharedDaemonConnection) await connection.protocol.reload();\n\t\t\t\t\t\t\telse await connection.protocol.publishCatalog(catalog);\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tconst failed = snapshot.filter((_connection, index) => outcomes[index]?.status === \"rejected\");\n\t\t\t\t\t// A failed publication may have been sent already. Do not roll back successful peers\n\t\t\t\t\t// to an older generation: close only transports whose catalog is uncertain.\n\t\t\t\t\tfor (const connection of failed) connection.websocket.terminate();\n\t\t\t\t\tawait previous.retire();\n\t\t\t\t\tif (failed.length > 0)\n\t\t\t\t\t\tthrow new Error(`Daemon catalog reload failed for ${failed.length} connection(s)`);\n\t\t\t\t});\n\t\t\treturn reloadQueue;\n\t\t},\n\t\tclose(): Promise<void> {\n\t\t\tif (closePromise) return closePromise;\n\t\t\tserverState = \"closing\";\n\t\t\trejectListen?.(new Error(\"Daemon server closed before listening completed\"));\n\t\t\trejectListen = undefined;\n\t\t\tconst deadline = performance.now() + configuration.shutdownTimeoutMs;\n\t\t\tconst remainingMs = () => Math.max(0, deadline - performance.now());\n\t\t\tclosePromise = (async () => {\n\t\t\t\tconst listenerClose = stopListener();\n\t\t\t\tconst snapshot = [...connections];\n\t\t\t\tawait waitWithin(\n\t\t\t\t\treloadQueue.catch(() => undefined),\n\t\t\t\t\tremainingMs(),\n\t\t\t\t\tforceController.signal,\n\t\t\t\t);\n\t\t\t\tconst hostDisposal = runtime.dispose();\n\t\t\t\tconst drained = await waitWithin(\n\t\t\t\t\tbeginDaemonConnectionDrain(snapshot, hostDisposal),\n\t\t\t\t\tremainingMs(),\n\t\t\t\t\tforceController.signal,\n\t\t\t\t);\n\t\t\t\tif (!drained) terminateDaemonConnections(snapshot);\n\t\t\t\tawait waitWithin(disconnectDaemonConnections(snapshot), remainingMs(), forceController.signal);\n\t\t\t\trequestDaemonConnectionClose(snapshot);\n\t\t\t\tconst socketsClosed = await waitWithin(\n\t\t\t\t\twaitForDaemonConnectionsClosed(snapshot),\n\t\t\t\t\tremainingMs(),\n\t\t\t\t\tforceController.signal,\n\t\t\t\t);\n\t\t\t\tif (!socketsClosed) terminateDaemonConnections(snapshot);\n\t\t\t\tdestroyDaemonSockets(sockets);\n\t\t\t\tawait listenerClose;\n\t\t\t\tconnections.clear();\n\t\t\t})().finally(() => {\n\t\t\t\tterminateDaemonConnections(connections);\n\t\t\t\tdestroyDaemonSockets(sockets);\n\t\t\t\tfor (const timer of pendingSocketTimers.values()) clearTimeout(timer);\n\t\t\t\tpendingSocketTimers.clear();\n\t\t\t\tpendingSocketDeadlines.clear();\n\t\t\t\tpendingSockets.clear();\n\t\t\t\tconnections.clear();\n\t\t\t\tcurrentAddress = undefined;\n\t\t\t\tserverState = \"closed\";\n\t\t\t});\n\t\t\treturn closePromise;\n\t\t},\n\t\tforceClose(): Promise<void> {\n\t\t\tif (serverState === \"closed\") return closePromise ?? Promise.resolve();\n\t\t\tserverState = \"closing\";\n\t\t\tforceController.abort();\n\t\t\tcurrentAddress = undefined;\n\t\t\tterminateDaemonConnections(connections);\n\t\t\tdestroyDaemonSockets(sockets);\n\t\t\treturn api.close();\n\t\t},\n\t};\n\treturn api;\n}\n"]}
|
package/dist/daemon/server.js
CHANGED
|
@@ -2,9 +2,14 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import { createServer as createHttpServer, } from "node:http";
|
|
3
3
|
import { createServer as createHttpsServer } from "node:https";
|
|
4
4
|
import WebSocket, { WebSocketServer } from "ws";
|
|
5
|
-
import { RemoteWorkspaceServerProtocol, } from "../core/remote-workspace-protocol/session.js";
|
|
5
|
+
import { RemoteWorkspaceRequestError, RemoteWorkspaceServerProtocol, } from "../core/remote-workspace-protocol/session.js";
|
|
6
|
+
import { SHARED_WORKSPACE_WEBSOCKET_PROTOCOL, } from "../core/remote-workspace-protocol/shared-transport.js";
|
|
7
|
+
import { DaemonActivityBudget } from "./activity-budget.js";
|
|
6
8
|
import { DAEMON_WEBSOCKET_PATH } from "./config.js";
|
|
9
|
+
import { DaemonOutboundBudget } from "./outbound-budget.js";
|
|
7
10
|
import { createDaemonAuthorization, rejectDaemonUpgrade, validateDaemonNetworkPolicy, validateDaemonUpgrade, } from "./security.js";
|
|
11
|
+
import { SharedDaemonConnection } from "./shared-connection.js";
|
|
12
|
+
import { SharedWorkspaceRoots } from "./shared-roots.js";
|
|
8
13
|
import { createDaemonWorkspaceRuntime } from "./workspace-runtime.js";
|
|
9
14
|
function websocketPayload(data) {
|
|
10
15
|
if (Buffer.isBuffer(data))
|
|
@@ -81,6 +86,7 @@ function formatHostForUrl(host) {
|
|
|
81
86
|
export function createDaemonServer(configuration) {
|
|
82
87
|
validateDaemonNetworkPolicy(configuration);
|
|
83
88
|
const authorization = createDaemonAuthorization(configuration.token);
|
|
89
|
+
const activityBudget = new DaemonActivityBudget(configuration);
|
|
84
90
|
const workspaceId = randomBytes(18).toString("base64url");
|
|
85
91
|
const workspace = Object.freeze({
|
|
86
92
|
id: workspaceId,
|
|
@@ -90,7 +96,17 @@ export function createDaemonServer(configuration) {
|
|
|
90
96
|
let runtime = createDaemonWorkspaceRuntime(configuration);
|
|
91
97
|
let runtimeHandler = runtime.createHandler(workspace);
|
|
92
98
|
const handler = {
|
|
93
|
-
handleRequest: (request, context) =>
|
|
99
|
+
handleRequest: (request, context) => {
|
|
100
|
+
if (typeof request.params === "object" && request.params !== null && "expectedRevision" in request.params) {
|
|
101
|
+
throw new RemoteWorkspaceRequestError({
|
|
102
|
+
code: "invalid_request",
|
|
103
|
+
message: "Revision preconditions require the v2 shared workspace protocol",
|
|
104
|
+
executionState: "not_started",
|
|
105
|
+
retryable: false,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return activityBudget.run(request.method, () => runtimeHandler.handleRequest(request, context));
|
|
109
|
+
},
|
|
94
110
|
validateToolArguments: (toolName, value) => runtimeHandler.validateToolArguments(toolName, value),
|
|
95
111
|
};
|
|
96
112
|
const requestListener = (_request, response) => {
|
|
@@ -118,6 +134,7 @@ export function createDaemonServer(configuration) {
|
|
|
118
134
|
const pendingSocketTimers = new Map();
|
|
119
135
|
const pendingSocketDeadlines = new Map();
|
|
120
136
|
const connections = new Set();
|
|
137
|
+
let sharedRoots;
|
|
121
138
|
let catalog = runtime.catalog;
|
|
122
139
|
let reloadQueue = Promise.resolve();
|
|
123
140
|
const forceController = new AbortController();
|
|
@@ -205,23 +222,47 @@ export function createDaemonServer(configuration) {
|
|
|
205
222
|
}, remainingHandshakeMs);
|
|
206
223
|
handshakeTimer.unref?.();
|
|
207
224
|
let protocol;
|
|
225
|
+
const outboundBudget = new DaemonOutboundBudget(configuration.protocolLimits.maxPendingOutboundBytes, configuration.protocolLimits.maxPendingOutboundMessages);
|
|
208
226
|
const transport = {
|
|
209
227
|
send(message) {
|
|
210
228
|
if (websocket.readyState !== WebSocket.OPEN)
|
|
211
229
|
return Promise.reject(new Error("WebSocket is not open"));
|
|
212
230
|
const serialized = JSON.stringify(message);
|
|
213
231
|
const bytes = Buffer.byteLength(serialized, "utf8");
|
|
214
|
-
|
|
215
|
-
|
|
232
|
+
let release;
|
|
233
|
+
try {
|
|
234
|
+
release = outboundBudget.reserve(bytes, websocket.bufferedAmount);
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
// No channel can safely continue after a physical send is dropped.
|
|
238
|
+
websocket.terminate();
|
|
239
|
+
return Promise.reject(error);
|
|
216
240
|
}
|
|
217
241
|
return new Promise((resolve, reject) => {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
});
|
|
224
|
-
|
|
242
|
+
// A stalled callback must not pin physical reservations indefinitely. A
|
|
243
|
+
// timed-out send makes the entire uplink uncertain, not just its channel.
|
|
244
|
+
const deadline = setTimeout(() => {
|
|
245
|
+
websocket.terminate();
|
|
246
|
+
reject(new Error("WebSocket outbound send timed out"));
|
|
247
|
+
}, configuration.protocolLimits.maxTransportSendMs);
|
|
248
|
+
deadline.unref?.();
|
|
249
|
+
try {
|
|
250
|
+
websocket.send(serialized, { binary: false, compress: false }, (error) => {
|
|
251
|
+
clearTimeout(deadline);
|
|
252
|
+
if (error) {
|
|
253
|
+
websocket.terminate();
|
|
254
|
+
reject(error);
|
|
255
|
+
}
|
|
256
|
+
else
|
|
257
|
+
resolve();
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
clearTimeout(deadline);
|
|
262
|
+
websocket.terminate();
|
|
263
|
+
reject(error);
|
|
264
|
+
}
|
|
265
|
+
}).finally(release);
|
|
225
266
|
},
|
|
226
267
|
close(reason) {
|
|
227
268
|
if (websocket.readyState === WebSocket.CLOSED)
|
|
@@ -230,6 +271,35 @@ export function createDaemonServer(configuration) {
|
|
|
230
271
|
return waitForWebSocketClose(websocket);
|
|
231
272
|
},
|
|
232
273
|
};
|
|
274
|
+
if (websocket.protocol === SHARED_WORKSPACE_WEBSOCKET_PROTOCOL) {
|
|
275
|
+
if (!sharedRoots) {
|
|
276
|
+
websocket.close(1008, "Shared parent unavailable");
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const shared = new SharedDaemonConnection(configuration, sharedRoots, transport.send, (code, reason) => {
|
|
280
|
+
if (websocket.readyState === WebSocket.OPEN)
|
|
281
|
+
websocket.close(code, reason);
|
|
282
|
+
}, activityBudget);
|
|
283
|
+
const connection = { websocket, protocol: shared, closed: Promise.resolve() };
|
|
284
|
+
connection.closed = waitForWebSocketClose(websocket).then(async () => {
|
|
285
|
+
clearTimeout(handshakeTimer);
|
|
286
|
+
connections.delete(connection);
|
|
287
|
+
await shared.disconnect().catch(() => undefined);
|
|
288
|
+
});
|
|
289
|
+
connections.add(connection);
|
|
290
|
+
websocket.on("message", (data, isBinary) => {
|
|
291
|
+
if (isBinary) {
|
|
292
|
+
websocket.close(1003, "Text protocol required");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
void shared.receive(websocketPayload(data)).then(() => {
|
|
296
|
+
if (shared.handshakeComplete)
|
|
297
|
+
clearTimeout(handshakeTimer);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
websocket.on("error", () => undefined);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
233
303
|
protocol = new RemoteWorkspaceServerProtocol(transport, handler, {
|
|
234
304
|
workspace,
|
|
235
305
|
catalog,
|
|
@@ -272,6 +342,12 @@ export function createDaemonServer(configuration) {
|
|
|
272
342
|
return Promise.resolve(currentAddress);
|
|
273
343
|
if (listenPromise)
|
|
274
344
|
return listenPromise;
|
|
345
|
+
if (configuration.sharedParentRoot && !sharedRoots) {
|
|
346
|
+
return SharedWorkspaceRoots.fromParent(configuration.sharedParentRoot).then((roots) => {
|
|
347
|
+
sharedRoots = roots;
|
|
348
|
+
return api.listen();
|
|
349
|
+
});
|
|
350
|
+
}
|
|
275
351
|
listenPromise = new Promise((resolve, reject) => {
|
|
276
352
|
rejectListen = reject;
|
|
277
353
|
const onError = (error) => {
|
|
@@ -321,28 +397,32 @@ export function createDaemonServer(configuration) {
|
|
|
321
397
|
reload() {
|
|
322
398
|
if (serverState !== "listening")
|
|
323
399
|
return Promise.reject(new Error("Daemon server is not listening"));
|
|
324
|
-
reloadQueue = reloadQueue
|
|
400
|
+
reloadQueue = reloadQueue
|
|
401
|
+
.catch(() => undefined)
|
|
402
|
+
.then(async () => {
|
|
325
403
|
if (serverState !== "listening")
|
|
326
404
|
throw new Error("Daemon server is not listening");
|
|
327
405
|
const candidate = createDaemonWorkspaceRuntime(configuration);
|
|
328
406
|
const candidateCatalog = { ...candidate.catalog, generation: catalog.generation + 1 };
|
|
329
407
|
const previous = runtime;
|
|
330
|
-
const previousCatalog = catalog;
|
|
331
408
|
runtime = candidate;
|
|
332
409
|
runtimeHandler = candidate.createHandler(workspace);
|
|
333
410
|
catalog = candidateCatalog;
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
411
|
+
const snapshot = [...connections];
|
|
412
|
+
const outcomes = await Promise.allSettled(snapshot.map(async (connection) => {
|
|
413
|
+
if (connection.protocol instanceof SharedDaemonConnection)
|
|
414
|
+
await connection.protocol.reload();
|
|
415
|
+
else
|
|
416
|
+
await connection.protocol.publishCatalog(catalog);
|
|
417
|
+
}));
|
|
418
|
+
const failed = snapshot.filter((_connection, index) => outcomes[index]?.status === "rejected");
|
|
419
|
+
// A failed publication may have been sent already. Do not roll back successful peers
|
|
420
|
+
// to an older generation: close only transports whose catalog is uncertain.
|
|
421
|
+
for (const connection of failed)
|
|
422
|
+
connection.websocket.terminate();
|
|
345
423
|
await previous.retire();
|
|
424
|
+
if (failed.length > 0)
|
|
425
|
+
throw new Error(`Daemon catalog reload failed for ${failed.length} connection(s)`);
|
|
346
426
|
});
|
|
347
427
|
return reloadQueue;
|
|
348
428
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/daemon/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport {\n\tcreateServer as createHttpServer,\n\ttype Server as HttpServer,\n\ttype IncomingMessage,\n\ttype ServerResponse,\n} from \"node:http\";\nimport { createServer as createHttpsServer } from \"node:https\";\nimport type { AddressInfo, Socket } from \"node:net\";\nimport WebSocket, { type RawData, WebSocketServer } from \"ws\";\nimport type { RemoteWorkspaceServerMessage } from \"../core/remote-workspace-protocol/contract.ts\";\nimport {\n\ttype RemoteWorkspaceProtocolCloseCode,\n\ttype RemoteWorkspaceProtocolCloseReason,\n\ttype RemoteWorkspaceServerHandler,\n\tRemoteWorkspaceServerProtocol,\n} from \"../core/remote-workspace-protocol/session.ts\";\nimport { DAEMON_WEBSOCKET_PATH, type DaemonConfiguration } from \"./config.ts\";\nimport {\n\tcreateDaemonAuthorization,\n\trejectDaemonUpgrade,\n\tvalidateDaemonNetworkPolicy,\n\tvalidateDaemonUpgrade,\n} from \"./security.ts\";\nimport { createDaemonWorkspaceRuntime } from \"./workspace-runtime.ts\";\n\nexport interface DaemonServerAddress {\n\treadonly host: string;\n\treadonly port: number;\n\treadonly secure: boolean;\n\treadonly url: string;\n\treadonly workspaceRoot: string;\n\treadonly workspaceId: string;\n}\n\nexport type DaemonServerState = \"created\" | \"listening\" | \"closing\" | \"closed\";\n\nexport interface DaemonServer {\n\tlisten(): Promise<DaemonServerAddress>;\n\taddress(): DaemonServerAddress | undefined;\n\tstate(): DaemonServerState;\n\treload(): Promise<void>;\n\tclose(): Promise<void>;\n\tforceClose(): Promise<void>;\n}\n\ninterface DaemonConnection {\n\treadonly websocket: WebSocket;\n\treadonly protocol: RemoteWorkspaceServerProtocol;\n\tclosed: Promise<void>;\n}\n\nfunction websocketPayload(data: RawData): Uint8Array {\n\tif (Buffer.isBuffer(data)) return Uint8Array.from(data);\n\tif (Array.isArray(data)) return Uint8Array.from(Buffer.concat(data));\n\treturn Uint8Array.from(new Uint8Array(data));\n}\n\nfunction websocketCloseCode(code: RemoteWorkspaceProtocolCloseCode): number {\n\tswitch (code) {\n\t\tcase \"normal\":\n\t\t\treturn 1000;\n\t\tcase \"protocol_error\":\n\t\t\treturn 1002;\n\t\tcase \"invalid_payload\":\n\t\t\treturn 1007;\n\t\tcase \"policy_violation\":\n\t\t\treturn 1008;\n\t\tcase \"message_too_large\":\n\t\t\treturn 1009;\n\t}\n}\n\nfunction waitForWebSocketClose(websocket: WebSocket): Promise<void> {\n\tif (websocket.readyState === WebSocket.CLOSED) return Promise.resolve();\n\treturn new Promise((resolve) => websocket.once(\"close\", () => resolve()));\n}\n\nfunction waitWithin(promise: Promise<unknown>, timeoutMs: number, forceSignal: AbortSignal): Promise<boolean> {\n\tif (forceSignal.aborted) return Promise.resolve(false);\n\treturn new Promise((resolve) => {\n\t\tlet settled = false;\n\t\tconst finish = (value: boolean) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timer);\n\t\t\tforceSignal.removeEventListener(\"abort\", onForce);\n\t\t\tresolve(value);\n\t\t};\n\t\tconst timer = setTimeout(() => finish(false), timeoutMs);\n\t\tconst onForce = () => finish(false);\n\t\tforceSignal.addEventListener(\"abort\", onForce, { once: true });\n\t\tpromise.then(\n\t\t\t() => finish(true),\n\t\t\t() => finish(false),\n\t\t);\n\t});\n}\nfunction terminateDaemonConnections(connections: Iterable<DaemonConnection>): void {\n\tfor (const connection of connections) connection.websocket.terminate();\n}\n\nfunction beginDaemonConnectionDrain(\n\tconnections: readonly DaemonConnection[],\n\thostDisposal: Promise<void>,\n): Promise<void> {\n\treturn Promise.allSettled([...connections.map((connection) => connection.protocol.beginDrain()), hostDisposal]).then(\n\t\t() => undefined,\n\t);\n}\n\nfunction disconnectDaemonConnections(connections: readonly DaemonConnection[]): Promise<void> {\n\treturn Promise.allSettled(connections.map((connection) => connection.protocol.disconnect())).then(() => undefined);\n}\n\nfunction requestDaemonConnectionClose(connections: readonly DaemonConnection[]): void {\n\tfor (const connection of connections) {\n\t\tif (connection.websocket.readyState === WebSocket.OPEN) {\n\t\t\tconnection.websocket.close(1001, \"Daemon shutting down\");\n\t\t}\n\t}\n}\n\nfunction waitForDaemonConnectionsClosed(connections: readonly DaemonConnection[]): Promise<void> {\n\treturn Promise.allSettled(connections.map((connection) => connection.closed)).then(() => undefined);\n}\n\nfunction destroyDaemonSockets(sockets: ReadonlySet<Socket>): void {\n\tfor (const socket of sockets) socket.destroy();\n}\n\nfunction formatHostForUrl(host: string): string {\n\treturn host.includes(\":\") && !host.startsWith(\"[\") ? `[${host}]` : host;\n}\n\nexport function createDaemonServer(configuration: DaemonConfiguration): DaemonServer {\n\tvalidateDaemonNetworkPolicy(configuration);\n\tconst authorization = createDaemonAuthorization(configuration.token);\n\tconst workspaceId = randomBytes(18).toString(\"base64url\");\n\tconst workspace = Object.freeze({\n\t\tid: workspaceId,\n\t\troot: configuration.workspaceRoot,\n\t\tpathFlavor: process.platform === \"win32\" ? (\"windows\" as const) : (\"posix\" as const),\n\t});\n\tlet runtime = createDaemonWorkspaceRuntime(configuration);\n\tlet runtimeHandler = runtime.createHandler(workspace);\n\tconst handler: RemoteWorkspaceServerHandler = {\n\t\thandleRequest: (request, context) => runtimeHandler.handleRequest(request, context),\n\t\tvalidateToolArguments: (toolName, value) => runtimeHandler.validateToolArguments(toolName, value),\n\t};\n\tconst requestListener = (_request: IncomingMessage, response: ServerResponse) => {\n\t\tresponse.writeHead(404, { \"content-type\": \"text/plain\" });\n\t\tresponse.end(\"Not found\\n\");\n\t};\n\tconst server: HttpServer = configuration.tls\n\t\t? createHttpsServer(\n\t\t\t\t{\n\t\t\t\t\tcert: configuration.tls.cert,\n\t\t\t\t\tkey: configuration.tls.key,\n\t\t\t\t\tpassphrase: configuration.tls.passphrase,\n\t\t\t\t\tminVersion: \"TLSv1.2\",\n\t\t\t\t},\n\t\t\t\trequestListener,\n\t\t\t)\n\t\t: createHttpServer(requestListener);\n\tconst websocketServer = new WebSocketServer({\n\t\tnoServer: true,\n\t\tclientTracking: false,\n\t\tmaxPayload: configuration.protocolLimits.maxMessageBytes,\n\t\tperMessageDeflate: false,\n\t\tallowSynchronousEvents: false,\n\t\tskipUTF8Validation: false,\n\t});\n\tconst sockets = new Set<Socket>();\n\tconst pendingSockets = new Set<Socket>();\n\tconst pendingSocketTimers = new Map<Socket, NodeJS.Timeout>();\n\tconst pendingSocketDeadlines = new Map<Socket, number>();\n\tconst connections = new Set<DaemonConnection>();\n\tlet catalog = runtime.catalog;\n\tlet reloadQueue: Promise<void> = Promise.resolve();\n\tconst forceController = new AbortController();\n\tlet currentAddress: DaemonServerAddress | undefined;\n\tlet listenPromise: Promise<DaemonServerAddress> | undefined;\n\tlet rejectListen: ((error: Error) => void) | undefined;\n\tlet closePromise: Promise<void> | undefined;\n\tlet serverState: DaemonServerState = \"created\";\n\n\tconst stopListener = (): Promise<void> => {\n\t\tif (!server.listening && !listenPromise) return Promise.resolve();\n\t\treturn new Promise((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tconst cleanup = () => {\n\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\tserver.off(\"close\", onClose);\n\t\t\t};\n\t\t\tconst finish = () => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst closeListener = () => server.close(() => finish());\n\t\t\tconst onListening = () => closeListener();\n\t\t\tconst onError = () => finish();\n\t\t\tconst onClose = () => finish();\n\t\t\tserver.once(\"error\", onError);\n\t\t\tserver.once(\"close\", onClose);\n\t\t\tif (server.listening) closeListener();\n\t\t\telse server.once(\"listening\", onListening);\n\t\t});\n\t};\n\n\tserver.on(\"connection\", (socket: Socket) => {\n\t\tif (serverState !== \"listening\" || pendingSockets.size >= configuration.maxPendingConnections) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tsockets.add(socket);\n\t\tpendingSockets.add(socket);\n\t\tconst pendingTimer = setTimeout(() => socket.destroy(), configuration.handshakeTimeoutMs);\n\t\tpendingTimer.unref?.();\n\t\tpendingSocketTimers.set(socket, pendingTimer);\n\t\tpendingSocketDeadlines.set(socket, performance.now() + configuration.handshakeTimeoutMs);\n\t\tsocket.once(\"close\", () => {\n\t\t\tsockets.delete(socket);\n\t\t\tpendingSockets.delete(socket);\n\t\t\tconst timer = pendingSocketTimers.get(socket);\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tpendingSocketTimers.delete(socket);\n\t\t\tpendingSocketDeadlines.delete(socket);\n\t\t});\n\t});\n\n\tserver.on(\"upgrade\", (request, socket, head) => {\n\t\tconst netSocket = socket as Socket;\n\t\tconst decision = validateDaemonUpgrade(request, configuration, authorization);\n\t\tif (!decision.accepted) {\n\t\t\trejectDaemonUpgrade(netSocket, decision);\n\t\t\treturn;\n\t\t}\n\t\tif (serverState !== \"listening\" || connections.size >= configuration.maxConnections) {\n\t\t\trejectDaemonUpgrade(netSocket, { accepted: false, status: 503, message: \"Service unavailable\" });\n\t\t\treturn;\n\t\t}\n\t\tconst handshakeDeadline = pendingSocketDeadlines.get(netSocket) ?? performance.now();\n\t\tpendingSockets.delete(netSocket);\n\t\tconst pendingTimer = pendingSocketTimers.get(netSocket);\n\t\tif (pendingTimer) clearTimeout(pendingTimer);\n\t\tpendingSocketTimers.delete(netSocket);\n\t\tpendingSocketDeadlines.delete(netSocket);\n\t\twebsocketServer.handleUpgrade(request, socket, head, (websocket) => {\n\t\t\tif (serverState !== \"listening\") {\n\t\t\t\twebsocket.terminate();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst remainingHandshakeMs = Math.max(0, handshakeDeadline - performance.now());\n\t\t\tconst handshakeTimer = setTimeout(() => {\n\t\t\t\twebsocket.terminate();\n\t\t\t}, remainingHandshakeMs);\n\t\t\thandshakeTimer.unref?.();\n\t\t\tlet protocol: RemoteWorkspaceServerProtocol;\n\t\t\tconst transport = {\n\t\t\t\tsend(message: RemoteWorkspaceServerMessage): Promise<void> {\n\t\t\t\t\tif (websocket.readyState !== WebSocket.OPEN) return Promise.reject(new Error(\"WebSocket is not open\"));\n\t\t\t\t\tconst serialized = JSON.stringify(message);\n\t\t\t\t\tconst bytes = Buffer.byteLength(serialized, \"utf8\");\n\t\t\t\t\tif (websocket.bufferedAmount + bytes > configuration.protocolLimits.maxPendingOutboundBytes) {\n\t\t\t\t\t\treturn Promise.reject(new Error(\"WebSocket outbound buffer exceeds daemon limits\"));\n\t\t\t\t\t}\n\t\t\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\t\t\twebsocket.send(serialized, { binary: false, compress: false }, (error) => {\n\t\t\t\t\t\t\tif (error) reject(error);\n\t\t\t\t\t\t\telse resolve();\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tclose(reason: RemoteWorkspaceProtocolCloseReason): Promise<void> {\n\t\t\t\t\tif (websocket.readyState === WebSocket.CLOSED) return Promise.resolve();\n\t\t\t\t\twebsocket.close(websocketCloseCode(reason.code), reason.message);\n\t\t\t\t\treturn waitForWebSocketClose(websocket);\n\t\t\t\t},\n\t\t\t};\n\t\t\tprotocol = new RemoteWorkspaceServerProtocol(transport, handler, {\n\t\t\t\tworkspace,\n\t\t\t\tcatalog,\n\t\t\t\tcapabilities: runtime.capabilities,\n\t\t\t\tlimits: configuration.protocolLimits,\n\t\t\t\tbackendLabel: \"grip-workspace-daemon\",\n\t\t\t});\n\t\t\tconst connection: DaemonConnection = {\n\t\t\t\twebsocket,\n\t\t\t\tprotocol,\n\t\t\t\tclosed: Promise.resolve(),\n\t\t\t};\n\t\t\tconnection.closed = waitForWebSocketClose(websocket).then(async () => {\n\t\t\t\tclearTimeout(handshakeTimer);\n\t\t\t\tconnections.delete(connection);\n\t\t\t\tawait protocol.disconnect().catch(() => undefined);\n\t\t\t});\n\t\t\tconnections.add(connection);\n\t\t\twebsocket.on(\"message\", (data, isBinary) => {\n\t\t\t\tif (isBinary) {\n\t\t\t\t\twebsocket.close(1003, \"Text protocol required\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tvoid protocol\n\t\t\t\t\t.receive(websocketPayload(data))\n\t\t\t\t\t.then(() => {\n\t\t\t\t\t\tif (protocol.handshakeComplete) clearTimeout(handshakeTimer);\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => undefined);\n\t\t\t});\n\t\t\twebsocket.on(\"error\", () => undefined);\n\t\t});\n\t});\n\n\tconst api: DaemonServer = {\n\t\tlisten(): Promise<DaemonServerAddress> {\n\t\t\tif (serverState === \"closing\" || serverState === \"closed\")\n\t\t\t\treturn Promise.reject(new Error(\"Daemon server is closing\"));\n\t\t\tif (currentAddress) return Promise.resolve(currentAddress);\n\t\t\tif (listenPromise) return listenPromise;\n\t\t\tlistenPromise = new Promise((resolve, reject) => {\n\t\t\t\trejectListen = reject;\n\t\t\t\tconst onError = (error: Error) => {\n\t\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\t\tlistenPromise = undefined;\n\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\t\t\t\tconst onListening = () => {\n\t\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\t\tif (serverState !== \"created\") {\n\t\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\t\treject(new Error(\"Daemon server closed before listening completed\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst address = server.address();\n\t\t\t\t\tif (!address || typeof address === \"string\") {\n\t\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\t\treject(new Error(\"Daemon listener did not return a TCP address\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tserverState = \"listening\";\n\t\t\t\t\tconst port = (address as AddressInfo).port;\n\t\t\t\t\tcurrentAddress = Object.freeze({\n\t\t\t\t\t\thost: configuration.host,\n\t\t\t\t\t\tport,\n\t\t\t\t\t\tsecure: configuration.tls !== undefined,\n\t\t\t\t\t\turl: `${configuration.tls ? \"wss\" : \"ws\"}://${formatHostForUrl(configuration.host)}:${port}${DAEMON_WEBSOCKET_PATH}`,\n\t\t\t\t\t\tworkspaceRoot: configuration.workspaceRoot,\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t});\n\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\tresolve(currentAddress);\n\t\t\t\t};\n\t\t\t\tserver.once(\"error\", onError);\n\t\t\t\tserver.once(\"listening\", onListening);\n\t\t\t\tserver.listen(configuration.port, configuration.host);\n\t\t\t});\n\t\t\treturn listenPromise;\n\t\t},\n\t\taddress(): DaemonServerAddress | undefined {\n\t\t\treturn currentAddress ? { ...currentAddress } : undefined;\n\t\t},\n\t\tstate(): DaemonServerState {\n\t\t\treturn serverState;\n\t\t},\n\t\treload(): Promise<void> {\n\t\t\tif (serverState !== \"listening\") return Promise.reject(new Error(\"Daemon server is not listening\"));\n\t\t\treloadQueue = reloadQueue.then(async () => {\n\t\t\t\tif (serverState !== \"listening\") throw new Error(\"Daemon server is not listening\");\n\t\t\t\tconst candidate = createDaemonWorkspaceRuntime(configuration);\n\t\t\t\tconst candidateCatalog = { ...candidate.catalog, generation: catalog.generation + 1 };\n\t\t\t\tconst previous = runtime;\n\t\t\t\tconst previousCatalog = catalog;\n\t\t\t\truntime = candidate;\n\t\t\t\truntimeHandler = candidate.createHandler(workspace);\n\t\t\t\tcatalog = candidateCatalog;\n\t\t\t\ttry {\n\t\t\t\t\tawait Promise.all([...connections].map((connection) => connection.protocol.publishCatalog(catalog)));\n\t\t\t\t} catch (error) {\n\t\t\t\t\truntime = previous;\n\t\t\t\t\truntimeHandler = previous.createHandler(workspace);\n\t\t\t\t\tcatalog = previousCatalog;\n\t\t\t\t\tawait Promise.allSettled([...connections].map((connection) => connection.protocol.disconnect()));\n\t\t\t\t\tawait candidate.dispose();\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tawait previous.retire();\n\t\t\t});\n\t\t\treturn reloadQueue;\n\t\t},\n\t\tclose(): Promise<void> {\n\t\t\tif (closePromise) return closePromise;\n\t\t\tserverState = \"closing\";\n\t\t\trejectListen?.(new Error(\"Daemon server closed before listening completed\"));\n\t\t\trejectListen = undefined;\n\t\t\tconst deadline = performance.now() + configuration.shutdownTimeoutMs;\n\t\t\tconst remainingMs = () => Math.max(0, deadline - performance.now());\n\t\t\tclosePromise = (async () => {\n\t\t\t\tconst listenerClose = stopListener();\n\t\t\t\tconst snapshot = [...connections];\n\t\t\t\tawait waitWithin(\n\t\t\t\t\treloadQueue.catch(() => undefined),\n\t\t\t\t\tremainingMs(),\n\t\t\t\t\tforceController.signal,\n\t\t\t\t);\n\t\t\t\tconst hostDisposal = runtime.dispose();\n\t\t\t\tconst drained = await waitWithin(\n\t\t\t\t\tbeginDaemonConnectionDrain(snapshot, hostDisposal),\n\t\t\t\t\tremainingMs(),\n\t\t\t\t\tforceController.signal,\n\t\t\t\t);\n\t\t\t\tif (!drained) terminateDaemonConnections(snapshot);\n\t\t\t\tawait waitWithin(disconnectDaemonConnections(snapshot), remainingMs(), forceController.signal);\n\t\t\t\trequestDaemonConnectionClose(snapshot);\n\t\t\t\tconst socketsClosed = await waitWithin(\n\t\t\t\t\twaitForDaemonConnectionsClosed(snapshot),\n\t\t\t\t\tremainingMs(),\n\t\t\t\t\tforceController.signal,\n\t\t\t\t);\n\t\t\t\tif (!socketsClosed) terminateDaemonConnections(snapshot);\n\t\t\t\tdestroyDaemonSockets(sockets);\n\t\t\t\tawait listenerClose;\n\t\t\t\tconnections.clear();\n\t\t\t})().finally(() => {\n\t\t\t\tterminateDaemonConnections(connections);\n\t\t\t\tdestroyDaemonSockets(sockets);\n\t\t\t\tfor (const timer of pendingSocketTimers.values()) clearTimeout(timer);\n\t\t\t\tpendingSocketTimers.clear();\n\t\t\t\tpendingSocketDeadlines.clear();\n\t\t\t\tpendingSockets.clear();\n\t\t\t\tconnections.clear();\n\t\t\t\tcurrentAddress = undefined;\n\t\t\t\tserverState = \"closed\";\n\t\t\t});\n\t\t\treturn closePromise;\n\t\t},\n\t\tforceClose(): Promise<void> {\n\t\t\tif (serverState === \"closed\") return closePromise ?? Promise.resolve();\n\t\t\tserverState = \"closing\";\n\t\t\tforceController.abort();\n\t\t\tcurrentAddress = undefined;\n\t\t\tterminateDaemonConnections(connections);\n\t\t\tdestroyDaemonSockets(sockets);\n\t\t\treturn api.close();\n\t\t},\n\t};\n\treturn api;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/daemon/server.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport {\n\tcreateServer as createHttpServer,\n\ttype Server as HttpServer,\n\ttype IncomingMessage,\n\ttype ServerResponse,\n} from \"node:http\";\nimport { createServer as createHttpsServer } from \"node:https\";\nimport type { AddressInfo, Socket } from \"node:net\";\nimport WebSocket, { type RawData, WebSocketServer } from \"ws\";\nimport type { RemoteWorkspaceServerMessage } from \"../core/remote-workspace-protocol/contract.ts\";\nimport {\n\ttype RemoteWorkspaceProtocolCloseCode,\n\ttype RemoteWorkspaceProtocolCloseReason,\n\tRemoteWorkspaceRequestError,\n\ttype RemoteWorkspaceServerHandler,\n\tRemoteWorkspaceServerProtocol,\n} from \"../core/remote-workspace-protocol/session.ts\";\nimport {\n\tSHARED_WORKSPACE_WEBSOCKET_PROTOCOL,\n\ttype SharedWorkspaceServerFrame,\n} from \"../core/remote-workspace-protocol/shared-transport.ts\";\nimport { DaemonActivityBudget } from \"./activity-budget.ts\";\nimport { DAEMON_WEBSOCKET_PATH, type DaemonConfiguration } from \"./config.ts\";\nimport { DaemonOutboundBudget } from \"./outbound-budget.ts\";\nimport {\n\tcreateDaemonAuthorization,\n\trejectDaemonUpgrade,\n\tvalidateDaemonNetworkPolicy,\n\tvalidateDaemonUpgrade,\n} from \"./security.ts\";\nimport { SharedDaemonConnection } from \"./shared-connection.ts\";\nimport { SharedWorkspaceRoots } from \"./shared-roots.ts\";\nimport { createDaemonWorkspaceRuntime } from \"./workspace-runtime.ts\";\n\nexport interface DaemonServerAddress {\n\treadonly host: string;\n\treadonly port: number;\n\treadonly secure: boolean;\n\treadonly url: string;\n\treadonly workspaceRoot: string;\n\treadonly workspaceId: string;\n}\n\nexport type DaemonServerState = \"created\" | \"listening\" | \"closing\" | \"closed\";\n\nexport interface DaemonServer {\n\tlisten(): Promise<DaemonServerAddress>;\n\taddress(): DaemonServerAddress | undefined;\n\tstate(): DaemonServerState;\n\treload(): Promise<void>;\n\tclose(): Promise<void>;\n\tforceClose(): Promise<void>;\n}\n\ninterface DaemonConnection {\n\treadonly websocket: WebSocket;\n\treadonly protocol: RemoteWorkspaceServerProtocol | SharedDaemonConnection;\n\tclosed: Promise<void>;\n}\n\nfunction websocketPayload(data: RawData): Uint8Array {\n\tif (Buffer.isBuffer(data)) return Uint8Array.from(data);\n\tif (Array.isArray(data)) return Uint8Array.from(Buffer.concat(data));\n\treturn Uint8Array.from(new Uint8Array(data));\n}\n\nfunction websocketCloseCode(code: RemoteWorkspaceProtocolCloseCode): number {\n\tswitch (code) {\n\t\tcase \"normal\":\n\t\t\treturn 1000;\n\t\tcase \"protocol_error\":\n\t\t\treturn 1002;\n\t\tcase \"invalid_payload\":\n\t\t\treturn 1007;\n\t\tcase \"policy_violation\":\n\t\t\treturn 1008;\n\t\tcase \"message_too_large\":\n\t\t\treturn 1009;\n\t}\n}\n\nfunction waitForWebSocketClose(websocket: WebSocket): Promise<void> {\n\tif (websocket.readyState === WebSocket.CLOSED) return Promise.resolve();\n\treturn new Promise((resolve) => websocket.once(\"close\", () => resolve()));\n}\n\nfunction waitWithin(promise: Promise<unknown>, timeoutMs: number, forceSignal: AbortSignal): Promise<boolean> {\n\tif (forceSignal.aborted) return Promise.resolve(false);\n\treturn new Promise((resolve) => {\n\t\tlet settled = false;\n\t\tconst finish = (value: boolean) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearTimeout(timer);\n\t\t\tforceSignal.removeEventListener(\"abort\", onForce);\n\t\t\tresolve(value);\n\t\t};\n\t\tconst timer = setTimeout(() => finish(false), timeoutMs);\n\t\tconst onForce = () => finish(false);\n\t\tforceSignal.addEventListener(\"abort\", onForce, { once: true });\n\t\tpromise.then(\n\t\t\t() => finish(true),\n\t\t\t() => finish(false),\n\t\t);\n\t});\n}\nfunction terminateDaemonConnections(connections: Iterable<DaemonConnection>): void {\n\tfor (const connection of connections) connection.websocket.terminate();\n}\n\nfunction beginDaemonConnectionDrain(\n\tconnections: readonly DaemonConnection[],\n\thostDisposal: Promise<void>,\n): Promise<void> {\n\treturn Promise.allSettled([...connections.map((connection) => connection.protocol.beginDrain()), hostDisposal]).then(\n\t\t() => undefined,\n\t);\n}\n\nfunction disconnectDaemonConnections(connections: readonly DaemonConnection[]): Promise<void> {\n\treturn Promise.allSettled(connections.map((connection) => connection.protocol.disconnect())).then(() => undefined);\n}\n\nfunction requestDaemonConnectionClose(connections: readonly DaemonConnection[]): void {\n\tfor (const connection of connections) {\n\t\tif (connection.websocket.readyState === WebSocket.OPEN) {\n\t\t\tconnection.websocket.close(1001, \"Daemon shutting down\");\n\t\t}\n\t}\n}\n\nfunction waitForDaemonConnectionsClosed(connections: readonly DaemonConnection[]): Promise<void> {\n\treturn Promise.allSettled(connections.map((connection) => connection.closed)).then(() => undefined);\n}\n\nfunction destroyDaemonSockets(sockets: ReadonlySet<Socket>): void {\n\tfor (const socket of sockets) socket.destroy();\n}\n\nfunction formatHostForUrl(host: string): string {\n\treturn host.includes(\":\") && !host.startsWith(\"[\") ? `[${host}]` : host;\n}\n\nexport function createDaemonServer(configuration: DaemonConfiguration): DaemonServer {\n\tvalidateDaemonNetworkPolicy(configuration);\n\tconst authorization = createDaemonAuthorization(configuration.token);\n\tconst activityBudget = new DaemonActivityBudget(configuration);\n\tconst workspaceId = randomBytes(18).toString(\"base64url\");\n\tconst workspace = Object.freeze({\n\t\tid: workspaceId,\n\t\troot: configuration.workspaceRoot,\n\t\tpathFlavor: process.platform === \"win32\" ? (\"windows\" as const) : (\"posix\" as const),\n\t});\n\tlet runtime = createDaemonWorkspaceRuntime(configuration);\n\tlet runtimeHandler = runtime.createHandler(workspace);\n\tconst handler: RemoteWorkspaceServerHandler = {\n\t\thandleRequest: (request, context) => {\n\t\t\tif (typeof request.params === \"object\" && request.params !== null && \"expectedRevision\" in request.params) {\n\t\t\t\tthrow new RemoteWorkspaceRequestError({\n\t\t\t\t\tcode: \"invalid_request\",\n\t\t\t\t\tmessage: \"Revision preconditions require the v2 shared workspace protocol\",\n\t\t\t\t\texecutionState: \"not_started\",\n\t\t\t\t\tretryable: false,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn activityBudget.run(request.method, () => runtimeHandler.handleRequest(request, context));\n\t\t},\n\t\tvalidateToolArguments: (toolName, value) => runtimeHandler.validateToolArguments(toolName, value),\n\t};\n\tconst requestListener = (_request: IncomingMessage, response: ServerResponse) => {\n\t\tresponse.writeHead(404, { \"content-type\": \"text/plain\" });\n\t\tresponse.end(\"Not found\\n\");\n\t};\n\tconst server: HttpServer = configuration.tls\n\t\t? createHttpsServer(\n\t\t\t\t{\n\t\t\t\t\tcert: configuration.tls.cert,\n\t\t\t\t\tkey: configuration.tls.key,\n\t\t\t\t\tpassphrase: configuration.tls.passphrase,\n\t\t\t\t\tminVersion: \"TLSv1.2\",\n\t\t\t\t},\n\t\t\t\trequestListener,\n\t\t\t)\n\t\t: createHttpServer(requestListener);\n\tconst websocketServer = new WebSocketServer({\n\t\tnoServer: true,\n\t\tclientTracking: false,\n\t\tmaxPayload: configuration.protocolLimits.maxMessageBytes,\n\t\tperMessageDeflate: false,\n\t\tallowSynchronousEvents: false,\n\t\tskipUTF8Validation: false,\n\t});\n\tconst sockets = new Set<Socket>();\n\tconst pendingSockets = new Set<Socket>();\n\tconst pendingSocketTimers = new Map<Socket, NodeJS.Timeout>();\n\tconst pendingSocketDeadlines = new Map<Socket, number>();\n\tconst connections = new Set<DaemonConnection>();\n\tlet sharedRoots: SharedWorkspaceRoots | undefined;\n\tlet catalog = runtime.catalog;\n\tlet reloadQueue: Promise<void> = Promise.resolve();\n\tconst forceController = new AbortController();\n\tlet currentAddress: DaemonServerAddress | undefined;\n\tlet listenPromise: Promise<DaemonServerAddress> | undefined;\n\tlet rejectListen: ((error: Error) => void) | undefined;\n\tlet closePromise: Promise<void> | undefined;\n\tlet serverState: DaemonServerState = \"created\";\n\n\tconst stopListener = (): Promise<void> => {\n\t\tif (!server.listening && !listenPromise) return Promise.resolve();\n\t\treturn new Promise((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tconst cleanup = () => {\n\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\tserver.off(\"close\", onClose);\n\t\t\t};\n\t\t\tconst finish = () => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tcleanup();\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst closeListener = () => server.close(() => finish());\n\t\t\tconst onListening = () => closeListener();\n\t\t\tconst onError = () => finish();\n\t\t\tconst onClose = () => finish();\n\t\t\tserver.once(\"error\", onError);\n\t\t\tserver.once(\"close\", onClose);\n\t\t\tif (server.listening) closeListener();\n\t\t\telse server.once(\"listening\", onListening);\n\t\t});\n\t};\n\n\tserver.on(\"connection\", (socket: Socket) => {\n\t\tif (serverState !== \"listening\" || pendingSockets.size >= configuration.maxPendingConnections) {\n\t\t\tsocket.destroy();\n\t\t\treturn;\n\t\t}\n\t\tsockets.add(socket);\n\t\tpendingSockets.add(socket);\n\t\tconst pendingTimer = setTimeout(() => socket.destroy(), configuration.handshakeTimeoutMs);\n\t\tpendingTimer.unref?.();\n\t\tpendingSocketTimers.set(socket, pendingTimer);\n\t\tpendingSocketDeadlines.set(socket, performance.now() + configuration.handshakeTimeoutMs);\n\t\tsocket.once(\"close\", () => {\n\t\t\tsockets.delete(socket);\n\t\t\tpendingSockets.delete(socket);\n\t\t\tconst timer = pendingSocketTimers.get(socket);\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tpendingSocketTimers.delete(socket);\n\t\t\tpendingSocketDeadlines.delete(socket);\n\t\t});\n\t});\n\n\tserver.on(\"upgrade\", (request, socket, head) => {\n\t\tconst netSocket = socket as Socket;\n\t\tconst decision = validateDaemonUpgrade(request, configuration, authorization);\n\t\tif (!decision.accepted) {\n\t\t\trejectDaemonUpgrade(netSocket, decision);\n\t\t\treturn;\n\t\t}\n\t\tif (serverState !== \"listening\" || connections.size >= configuration.maxConnections) {\n\t\t\trejectDaemonUpgrade(netSocket, { accepted: false, status: 503, message: \"Service unavailable\" });\n\t\t\treturn;\n\t\t}\n\t\tconst handshakeDeadline = pendingSocketDeadlines.get(netSocket) ?? performance.now();\n\t\tpendingSockets.delete(netSocket);\n\t\tconst pendingTimer = pendingSocketTimers.get(netSocket);\n\t\tif (pendingTimer) clearTimeout(pendingTimer);\n\t\tpendingSocketTimers.delete(netSocket);\n\t\tpendingSocketDeadlines.delete(netSocket);\n\t\twebsocketServer.handleUpgrade(request, socket, head, (websocket) => {\n\t\t\tif (serverState !== \"listening\") {\n\t\t\t\twebsocket.terminate();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst remainingHandshakeMs = Math.max(0, handshakeDeadline - performance.now());\n\t\t\tconst handshakeTimer = setTimeout(() => {\n\t\t\t\twebsocket.terminate();\n\t\t\t}, remainingHandshakeMs);\n\t\t\thandshakeTimer.unref?.();\n\t\t\tlet protocol: RemoteWorkspaceServerProtocol;\n\t\t\tconst outboundBudget = new DaemonOutboundBudget(\n\t\t\t\tconfiguration.protocolLimits.maxPendingOutboundBytes,\n\t\t\t\tconfiguration.protocolLimits.maxPendingOutboundMessages,\n\t\t\t);\n\t\t\tconst transport = {\n\t\t\t\tsend(message: RemoteWorkspaceServerMessage | SharedWorkspaceServerFrame): Promise<void> {\n\t\t\t\t\tif (websocket.readyState !== WebSocket.OPEN) return Promise.reject(new Error(\"WebSocket is not open\"));\n\t\t\t\t\tconst serialized = JSON.stringify(message);\n\t\t\t\t\tconst bytes = Buffer.byteLength(serialized, \"utf8\");\n\t\t\t\t\tlet release: () => void;\n\t\t\t\t\ttry {\n\t\t\t\t\t\trelease = outboundBudget.reserve(bytes, websocket.bufferedAmount);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t// No channel can safely continue after a physical send is dropped.\n\t\t\t\t\t\twebsocket.terminate();\n\t\t\t\t\t\treturn Promise.reject(error);\n\t\t\t\t\t}\n\t\t\t\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\t\t\t\t// A stalled callback must not pin physical reservations indefinitely. A\n\t\t\t\t\t\t// timed-out send makes the entire uplink uncertain, not just its channel.\n\t\t\t\t\t\tconst deadline = setTimeout(() => {\n\t\t\t\t\t\t\twebsocket.terminate();\n\t\t\t\t\t\t\treject(new Error(\"WebSocket outbound send timed out\"));\n\t\t\t\t\t\t}, configuration.protocolLimits.maxTransportSendMs);\n\t\t\t\t\t\tdeadline.unref?.();\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\twebsocket.send(serialized, { binary: false, compress: false }, (error) => {\n\t\t\t\t\t\t\t\tclearTimeout(deadline);\n\t\t\t\t\t\t\t\tif (error) {\n\t\t\t\t\t\t\t\t\twebsocket.terminate();\n\t\t\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t\t\t} else resolve();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tclearTimeout(deadline);\n\t\t\t\t\t\t\twebsocket.terminate();\n\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t}).finally(release);\n\t\t\t\t},\n\t\t\t\tclose(reason: RemoteWorkspaceProtocolCloseReason): Promise<void> {\n\t\t\t\t\tif (websocket.readyState === WebSocket.CLOSED) return Promise.resolve();\n\t\t\t\t\twebsocket.close(websocketCloseCode(reason.code), reason.message);\n\t\t\t\t\treturn waitForWebSocketClose(websocket);\n\t\t\t\t},\n\t\t\t};\n\t\t\tif (websocket.protocol === SHARED_WORKSPACE_WEBSOCKET_PROTOCOL) {\n\t\t\t\tif (!sharedRoots) {\n\t\t\t\t\twebsocket.close(1008, \"Shared parent unavailable\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst shared = new SharedDaemonConnection(\n\t\t\t\t\tconfiguration,\n\t\t\t\t\tsharedRoots,\n\t\t\t\t\ttransport.send,\n\t\t\t\t\t(code, reason) => {\n\t\t\t\t\t\tif (websocket.readyState === WebSocket.OPEN) websocket.close(code, reason);\n\t\t\t\t\t},\n\t\t\t\t\tactivityBudget,\n\t\t\t\t);\n\t\t\t\tconst connection: DaemonConnection = { websocket, protocol: shared, closed: Promise.resolve() };\n\t\t\t\tconnection.closed = waitForWebSocketClose(websocket).then(async () => {\n\t\t\t\t\tclearTimeout(handshakeTimer);\n\t\t\t\t\tconnections.delete(connection);\n\t\t\t\t\tawait shared.disconnect().catch(() => undefined);\n\t\t\t\t});\n\t\t\t\tconnections.add(connection);\n\t\t\t\twebsocket.on(\"message\", (data, isBinary) => {\n\t\t\t\t\tif (isBinary) {\n\t\t\t\t\t\twebsocket.close(1003, \"Text protocol required\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tvoid shared.receive(websocketPayload(data)).then(() => {\n\t\t\t\t\t\tif (shared.handshakeComplete) clearTimeout(handshakeTimer);\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t\twebsocket.on(\"error\", () => undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tprotocol = new RemoteWorkspaceServerProtocol(transport, handler, {\n\t\t\t\tworkspace,\n\t\t\t\tcatalog,\n\t\t\t\tcapabilities: runtime.capabilities,\n\t\t\t\tlimits: configuration.protocolLimits,\n\t\t\t\tbackendLabel: \"grip-workspace-daemon\",\n\t\t\t});\n\t\t\tconst connection: DaemonConnection = {\n\t\t\t\twebsocket,\n\t\t\t\tprotocol,\n\t\t\t\tclosed: Promise.resolve(),\n\t\t\t};\n\t\t\tconnection.closed = waitForWebSocketClose(websocket).then(async () => {\n\t\t\t\tclearTimeout(handshakeTimer);\n\t\t\t\tconnections.delete(connection);\n\t\t\t\tawait protocol.disconnect().catch(() => undefined);\n\t\t\t});\n\t\t\tconnections.add(connection);\n\t\t\twebsocket.on(\"message\", (data, isBinary) => {\n\t\t\t\tif (isBinary) {\n\t\t\t\t\twebsocket.close(1003, \"Text protocol required\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tvoid protocol\n\t\t\t\t\t.receive(websocketPayload(data))\n\t\t\t\t\t.then(() => {\n\t\t\t\t\t\tif (protocol.handshakeComplete) clearTimeout(handshakeTimer);\n\t\t\t\t\t})\n\t\t\t\t\t.catch(() => undefined);\n\t\t\t});\n\t\t\twebsocket.on(\"error\", () => undefined);\n\t\t});\n\t});\n\n\tconst api: DaemonServer = {\n\t\tlisten(): Promise<DaemonServerAddress> {\n\t\t\tif (serverState === \"closing\" || serverState === \"closed\")\n\t\t\t\treturn Promise.reject(new Error(\"Daemon server is closing\"));\n\t\t\tif (currentAddress) return Promise.resolve(currentAddress);\n\t\t\tif (listenPromise) return listenPromise;\n\t\t\tif (configuration.sharedParentRoot && !sharedRoots) {\n\t\t\t\treturn SharedWorkspaceRoots.fromParent(configuration.sharedParentRoot).then((roots) => {\n\t\t\t\t\tsharedRoots = roots;\n\t\t\t\t\treturn api.listen();\n\t\t\t\t});\n\t\t\t}\n\t\t\tlistenPromise = new Promise((resolve, reject) => {\n\t\t\t\trejectListen = reject;\n\t\t\t\tconst onError = (error: Error) => {\n\t\t\t\t\tserver.off(\"listening\", onListening);\n\t\t\t\t\tlistenPromise = undefined;\n\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\treject(error);\n\t\t\t\t};\n\t\t\t\tconst onListening = () => {\n\t\t\t\t\tserver.off(\"error\", onError);\n\t\t\t\t\tif (serverState !== \"created\") {\n\t\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\t\treject(new Error(\"Daemon server closed before listening completed\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst address = server.address();\n\t\t\t\t\tif (!address || typeof address === \"string\") {\n\t\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\t\treject(new Error(\"Daemon listener did not return a TCP address\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tserverState = \"listening\";\n\t\t\t\t\tconst port = (address as AddressInfo).port;\n\t\t\t\t\tcurrentAddress = Object.freeze({\n\t\t\t\t\t\thost: configuration.host,\n\t\t\t\t\t\tport,\n\t\t\t\t\t\tsecure: configuration.tls !== undefined,\n\t\t\t\t\t\turl: `${configuration.tls ? \"wss\" : \"ws\"}://${formatHostForUrl(configuration.host)}:${port}${DAEMON_WEBSOCKET_PATH}`,\n\t\t\t\t\t\tworkspaceRoot: configuration.workspaceRoot,\n\t\t\t\t\t\tworkspaceId,\n\t\t\t\t\t});\n\t\t\t\t\trejectListen = undefined;\n\t\t\t\t\tresolve(currentAddress);\n\t\t\t\t};\n\t\t\t\tserver.once(\"error\", onError);\n\t\t\t\tserver.once(\"listening\", onListening);\n\t\t\t\tserver.listen(configuration.port, configuration.host);\n\t\t\t});\n\t\t\treturn listenPromise;\n\t\t},\n\t\taddress(): DaemonServerAddress | undefined {\n\t\t\treturn currentAddress ? { ...currentAddress } : undefined;\n\t\t},\n\t\tstate(): DaemonServerState {\n\t\t\treturn serverState;\n\t\t},\n\t\treload(): Promise<void> {\n\t\t\tif (serverState !== \"listening\") return Promise.reject(new Error(\"Daemon server is not listening\"));\n\t\t\treloadQueue = reloadQueue\n\t\t\t\t.catch(() => undefined)\n\t\t\t\t.then(async () => {\n\t\t\t\t\tif (serverState !== \"listening\") throw new Error(\"Daemon server is not listening\");\n\t\t\t\t\tconst candidate = createDaemonWorkspaceRuntime(configuration);\n\t\t\t\t\tconst candidateCatalog = { ...candidate.catalog, generation: catalog.generation + 1 };\n\t\t\t\t\tconst previous = runtime;\n\t\t\t\t\truntime = candidate;\n\t\t\t\t\truntimeHandler = candidate.createHandler(workspace);\n\t\t\t\t\tcatalog = candidateCatalog;\n\t\t\t\t\tconst snapshot = [...connections];\n\t\t\t\t\tconst outcomes = await Promise.allSettled(\n\t\t\t\t\t\tsnapshot.map(async (connection) => {\n\t\t\t\t\t\t\tif (connection.protocol instanceof SharedDaemonConnection) await connection.protocol.reload();\n\t\t\t\t\t\t\telse await connection.protocol.publishCatalog(catalog);\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tconst failed = snapshot.filter((_connection, index) => outcomes[index]?.status === \"rejected\");\n\t\t\t\t\t// A failed publication may have been sent already. Do not roll back successful peers\n\t\t\t\t\t// to an older generation: close only transports whose catalog is uncertain.\n\t\t\t\t\tfor (const connection of failed) connection.websocket.terminate();\n\t\t\t\t\tawait previous.retire();\n\t\t\t\t\tif (failed.length > 0)\n\t\t\t\t\t\tthrow new Error(`Daemon catalog reload failed for ${failed.length} connection(s)`);\n\t\t\t\t});\n\t\t\treturn reloadQueue;\n\t\t},\n\t\tclose(): Promise<void> {\n\t\t\tif (closePromise) return closePromise;\n\t\t\tserverState = \"closing\";\n\t\t\trejectListen?.(new Error(\"Daemon server closed before listening completed\"));\n\t\t\trejectListen = undefined;\n\t\t\tconst deadline = performance.now() + configuration.shutdownTimeoutMs;\n\t\t\tconst remainingMs = () => Math.max(0, deadline - performance.now());\n\t\t\tclosePromise = (async () => {\n\t\t\t\tconst listenerClose = stopListener();\n\t\t\t\tconst snapshot = [...connections];\n\t\t\t\tawait waitWithin(\n\t\t\t\t\treloadQueue.catch(() => undefined),\n\t\t\t\t\tremainingMs(),\n\t\t\t\t\tforceController.signal,\n\t\t\t\t);\n\t\t\t\tconst hostDisposal = runtime.dispose();\n\t\t\t\tconst drained = await waitWithin(\n\t\t\t\t\tbeginDaemonConnectionDrain(snapshot, hostDisposal),\n\t\t\t\t\tremainingMs(),\n\t\t\t\t\tforceController.signal,\n\t\t\t\t);\n\t\t\t\tif (!drained) terminateDaemonConnections(snapshot);\n\t\t\t\tawait waitWithin(disconnectDaemonConnections(snapshot), remainingMs(), forceController.signal);\n\t\t\t\trequestDaemonConnectionClose(snapshot);\n\t\t\t\tconst socketsClosed = await waitWithin(\n\t\t\t\t\twaitForDaemonConnectionsClosed(snapshot),\n\t\t\t\t\tremainingMs(),\n\t\t\t\t\tforceController.signal,\n\t\t\t\t);\n\t\t\t\tif (!socketsClosed) terminateDaemonConnections(snapshot);\n\t\t\t\tdestroyDaemonSockets(sockets);\n\t\t\t\tawait listenerClose;\n\t\t\t\tconnections.clear();\n\t\t\t})().finally(() => {\n\t\t\t\tterminateDaemonConnections(connections);\n\t\t\t\tdestroyDaemonSockets(sockets);\n\t\t\t\tfor (const timer of pendingSocketTimers.values()) clearTimeout(timer);\n\t\t\t\tpendingSocketTimers.clear();\n\t\t\t\tpendingSocketDeadlines.clear();\n\t\t\t\tpendingSockets.clear();\n\t\t\t\tconnections.clear();\n\t\t\t\tcurrentAddress = undefined;\n\t\t\t\tserverState = \"closed\";\n\t\t\t});\n\t\t\treturn closePromise;\n\t\t},\n\t\tforceClose(): Promise<void> {\n\t\t\tif (serverState === \"closed\") return closePromise ?? Promise.resolve();\n\t\t\tserverState = \"closing\";\n\t\t\tforceController.abort();\n\t\t\tcurrentAddress = undefined;\n\t\t\tterminateDaemonConnections(connections);\n\t\t\tdestroyDaemonSockets(sockets);\n\t\t\treturn api.close();\n\t\t},\n\t};\n\treturn api;\n}\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type SharedWorkspaceServerFrame } from "../core/remote-workspace-protocol/shared-transport.ts";
|
|
2
|
+
import { DaemonActivityBudget } from "./activity-budget.ts";
|
|
3
|
+
import type { DaemonConfiguration } from "./config.ts";
|
|
4
|
+
import type { SharedWorkspaceRoots } from "./shared-roots.ts";
|
|
5
|
+
/** One authenticated v2 WebSocket. No state (including request IDs) is shared between channels. */
|
|
6
|
+
export declare class SharedDaemonConnection {
|
|
7
|
+
private readonly channels;
|
|
8
|
+
private readonly channelDisposals;
|
|
9
|
+
private readonly pendingDisposals;
|
|
10
|
+
private readonly usedControlIds;
|
|
11
|
+
private readonly epoch;
|
|
12
|
+
private hello;
|
|
13
|
+
private closed;
|
|
14
|
+
private queue;
|
|
15
|
+
private pendingInboundMessages;
|
|
16
|
+
private pendingInboundBytes;
|
|
17
|
+
private readonly budget;
|
|
18
|
+
private readonly configuration;
|
|
19
|
+
private readonly roots;
|
|
20
|
+
private readonly send;
|
|
21
|
+
private readonly closeSocket;
|
|
22
|
+
constructor(configuration: DaemonConfiguration, roots: SharedWorkspaceRoots, send: (message: SharedWorkspaceServerFrame) => Promise<void>, closeSocket: (code: number, message: string) => void, budget?: DaemonActivityBudget);
|
|
23
|
+
receive(payload: Uint8Array): Promise<void>;
|
|
24
|
+
get handshakeComplete(): boolean;
|
|
25
|
+
beginDrain(): Promise<void>;
|
|
26
|
+
private createChannelRuntime;
|
|
27
|
+
reload(): Promise<void>;
|
|
28
|
+
private dispatch;
|
|
29
|
+
private open;
|
|
30
|
+
private disposeChannel;
|
|
31
|
+
disconnect(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=shared-connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared-connection.d.ts","sourceRoot":"","sources":["../../src/daemon/shared-connection.ts"],"names":[],"mappings":"AAUA,OAAO,EAGN,KAAK,0BAA0B,EAC/B,MAAM,uDAAuD,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAW9D,mGAAmG;AACnG,qBAAa,sBAAsB;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8B;IACvD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAyC;IAC1E,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA4B;IAC7D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyC;IAC/D,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,sBAAsB,CAAK;IACnC,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAE9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuB;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyD;IAC9E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0C;IAEtE,YACC,aAAa,EAAE,mBAAmB,EAClC,KAAK,EAAE,oBAAoB,EAC3B,IAAI,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,OAAO,CAAC,IAAI,CAAC,EAC5D,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,EACpD,MAAM,GAAE,oBAA8D,EAOtE;IAED,OAAO,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAkD1C;IAED,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAEK,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAEhC;IAED,OAAO,CAAC,oBAAoB;IAY5B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAwBtB;YAEa,QAAQ;YAkDR,IAAI;IA4ElB,OAAO,CAAC,cAAc;IAgBhB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAKhC;CACD","sourcesContent":["import { createHash, randomBytes } from \"node:crypto\";\nimport { VERSION } from \"../config.ts\";\nimport type {\n\tRemoteWorkspaceIdentity,\n\tRemoteWorkspaceServerMessage,\n} from \"../core/remote-workspace-protocol/contract.ts\";\nimport {\n\ttype RemoteWorkspaceServerHandler,\n\tRemoteWorkspaceServerProtocol,\n} from \"../core/remote-workspace-protocol/session.ts\";\nimport {\n\tparseSharedWorkspaceClientEnvelope,\n\ttype SharedWorkspaceClientEnvelope,\n\ttype SharedWorkspaceServerFrame,\n} from \"../core/remote-workspace-protocol/shared-transport.ts\";\nimport { DaemonActivityBudget } from \"./activity-budget.ts\";\nimport type { DaemonConfiguration } from \"./config.ts\";\nimport { withSharedRevisionGuard } from \"./shared-revision.ts\";\nimport type { SharedWorkspaceRoots } from \"./shared-roots.ts\";\nimport { createDaemonWorkspaceRuntime, type DaemonWorkspaceRuntime } from \"./workspace-runtime.ts\";\n\ninterface Channel {\n\treadonly protocol: RemoteWorkspaceServerProtocol;\n\truntime: DaemonWorkspaceRuntime;\n\thandler: RemoteWorkspaceServerHandler;\n\treadonly workspace: RemoteWorkspaceIdentity;\n\tgeneration: number;\n}\n\n/** One authenticated v2 WebSocket. No state (including request IDs) is shared between channels. */\nexport class SharedDaemonConnection {\n\tprivate readonly channels = new Map<string, Channel>();\n\tprivate readonly channelDisposals = new WeakMap<Channel, Promise<void>>();\n\tprivate readonly pendingDisposals = new Set<Promise<void>>();\n\tprivate readonly usedControlIds = new Set<string>();\n\tprivate readonly epoch = randomBytes(18).toString(\"base64url\");\n\tprivate hello = false;\n\tprivate closed = false;\n\tprivate queue: Promise<void> = Promise.resolve();\n\tprivate pendingInboundMessages = 0;\n\tprivate pendingInboundBytes = 0;\n\tprivate readonly budget: DaemonActivityBudget;\n\n\tprivate readonly configuration: DaemonConfiguration;\n\tprivate readonly roots: SharedWorkspaceRoots;\n\tprivate readonly send: (message: SharedWorkspaceServerFrame) => Promise<void>;\n\tprivate readonly closeSocket: (code: number, message: string) => void;\n\n\tconstructor(\n\t\tconfiguration: DaemonConfiguration,\n\t\troots: SharedWorkspaceRoots,\n\t\tsend: (message: SharedWorkspaceServerFrame) => Promise<void>,\n\t\tcloseSocket: (code: number, message: string) => void,\n\t\tbudget: DaemonActivityBudget = new DaemonActivityBudget(configuration),\n\t) {\n\t\tthis.configuration = configuration;\n\t\tthis.roots = roots;\n\t\tthis.send = send;\n\t\tthis.closeSocket = closeSocket;\n\t\tthis.budget = budget;\n\t}\n\n\treceive(payload: Uint8Array): Promise<void> {\n\t\tif (this.closed) return Promise.resolve();\n\t\tconst limits = this.configuration.protocolLimits;\n\t\tif (\n\t\t\tpayload.byteLength > limits.maxMessageBytes ||\n\t\t\tthis.pendingInboundMessages >= limits.maxPendingInboundMessages ||\n\t\t\tthis.pendingInboundBytes + payload.byteLength > limits.maxPendingInboundBytes\n\t\t) {\n\t\t\tthis.closed = true;\n\t\t\tthis.closeSocket(1009, \"Shared workspace inbound queue exceeds limits\");\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\t// Copy before queuing: WebSocket buffers need not remain stable after the callback returns.\n\t\tconst queuedPayload = Uint8Array.from(payload);\n\t\tthis.pendingInboundMessages++;\n\t\tthis.pendingInboundBytes += queuedPayload.byteLength;\n\t\tlet complete!: () => void;\n\t\tconst finished = new Promise<void>((resolve) => {\n\t\t\tcomplete = resolve;\n\t\t});\n\t\tlet channelPending = false;\n\t\tconst settle = () => {\n\t\t\tthis.pendingInboundMessages--;\n\t\t\tthis.pendingInboundBytes -= queuedPayload.byteLength;\n\t\t\tcomplete();\n\t\t};\n\t\tconst fail = () => {\n\t\t\tif (!this.closed) {\n\t\t\t\tthis.closed = true;\n\t\t\t\tthis.closeSocket(1002, \"Invalid shared workspace frame\");\n\t\t\t}\n\t\t};\n\t\tconst scheduled = this.queue.then(() => {\n\t\t\tif (this.closed) return;\n\t\t\tconst frame = parseSharedWorkspaceClientEnvelope(queuedPayload, limits);\n\t\t\tif (frame.type === \"channel\") {\n\t\t\t\t// Validate the channel before letting the control queue advance. Its own\n\t\t\t\t// protocol preserves per-channel receive order and bounds queued frames.\n\t\t\t\tif (!this.channels.has(frame.channelId)) throw new Error(\"Unknown shared workspace channel\");\n\t\t\t\tchannelPending = true;\n\t\t\t\tvoid this.dispatch(frame).catch(fail).finally(settle);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Hello, opens, closes and reloads remain serialized in control order.\n\t\t\treturn this.dispatch(frame);\n\t\t});\n\t\tthis.queue = scheduled.catch(fail).finally(() => {\n\t\t\tif (!channelPending) settle();\n\t\t});\n\t\treturn finished;\n\t}\n\n\tget handshakeComplete(): boolean {\n\t\treturn this.hello;\n\t}\n\n\tasync beginDrain(): Promise<void> {\n\t\tawait Promise.allSettled([...this.channels.values()].map((channel) => channel.protocol.beginDrain()));\n\t}\n\n\tprivate createChannelRuntime(root: string): DaemonWorkspaceRuntime {\n\t\treturn createDaemonWorkspaceRuntime({\n\t\t\t...this.configuration,\n\t\t\tworkspaceRoot: root,\n\t\t\t// Operator-wide resource files and the v1 temporary root are not channel-scoped.\n\t\t\t// Keep them available on v1, but never expose them through a child channel.\n\t\t\ttemporaryRoot: undefined,\n\t\t\tsandboxInstructionsPath: undefined,\n\t\t\tuserAgentsPath: undefined,\n\t\t});\n\t}\n\n\treload(): Promise<void> {\n\t\tconst operation = this.queue.then(async () => {\n\t\t\tif (this.closed) throw new Error(\"Shared workspace connection is closed\");\n\t\t\tfor (const channel of this.channels.values()) {\n\t\t\t\tconst previous = channel.runtime;\n\t\t\t\tconst previousHandler = channel.handler;\n\t\t\t\tconst candidate = this.createChannelRuntime(channel.workspace.root);\n\t\t\t\tconst candidateHandler = candidate.createHandler(channel.workspace, true);\n\t\t\t\tchannel.runtime = candidate;\n\t\t\t\tchannel.handler = candidateHandler;\n\t\t\t\ttry {\n\t\t\t\t\tawait channel.protocol.publishCatalog({ ...candidate.catalog, generation: channel.generation + 1 });\n\t\t\t\t\tchannel.generation++;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tchannel.runtime = previous;\n\t\t\t\t\tchannel.handler = previousHandler;\n\t\t\t\t\tawait candidate.dispose();\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tawait previous.retire();\n\t\t\t}\n\t\t});\n\t\tthis.queue = operation.catch(() => undefined);\n\t\treturn operation;\n\t}\n\n\tprivate async dispatch(frame: SharedWorkspaceClientEnvelope): Promise<void> {\n\t\tif (frame.type === \"hello\") {\n\t\t\tif (this.hello) throw new Error(\"Duplicate v2 hello\");\n\t\t\tthis.hello = true;\n\t\t\tthis.usedControlIds.add(frame.id);\n\t\t\tawait this.send({ type: \"hello_ack\", id: frame.id, version: 2, epoch: this.epoch, daemonVersion: VERSION });\n\t\t\treturn;\n\t\t}\n\t\tif (!this.hello) throw new Error(\"V2 hello required before opening channels\");\n\t\tif (frame.type === \"open\") {\n\t\t\tif (this.usedControlIds.has(frame.id)) throw new Error(\"Duplicate v2 control ID\");\n\t\t\tif (this.usedControlIds.size >= 65_536) throw new Error(\"V2 control ID budget exhausted\");\n\t\t\tthis.usedControlIds.add(frame.id);\n\t\t\tif (this.channels.size >= Math.min(this.configuration.maxConnections, 32)) {\n\t\t\t\tawait this.send({\n\t\t\t\t\ttype: \"control_error\",\n\t\t\t\t\tid: frame.id,\n\t\t\t\t\tcode: \"limit_exceeded\",\n\t\t\t\t\tmessage: \"Channel limit reached\",\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet root: string;\n\t\t\ttry {\n\t\t\t\troot = await this.roots.bind(frame.root);\n\t\t\t} catch {\n\t\t\t\tawait this.send({\n\t\t\t\t\ttype: \"control_error\",\n\t\t\t\t\tid: frame.id,\n\t\t\t\t\tcode: \"invalid_root\",\n\t\t\t\t\tmessage: \"Workspace root is not an existing directory under the shared parent\",\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait this.open(frame.id, root);\n\t\t\treturn;\n\t\t}\n\t\tconst channel = this.channels.get(frame.channelId);\n\t\tif (!channel) throw new Error(\"Unknown shared workspace channel\");\n\t\tif (frame.type === \"close\") {\n\t\t\t// Do not wait on a channel's in-flight requests while blocking the socket receive queue.\n\t\t\tthis.channels.delete(frame.channelId);\n\t\t\t// Begin cancellation before acknowledging detach; track it for physical shutdown.\n\t\t\tvoid this.disposeChannel(channel);\n\t\t\tawait this.send({ type: \"closed\", channelId: frame.channelId });\n\t\t\treturn;\n\t\t}\n\t\tawait channel.protocol.receive(Buffer.from(JSON.stringify(frame.payload)));\n\t}\n\n\tprivate async open(id: string, root: string): Promise<void> {\n\t\tconst channelId = randomBytes(24).toString(\"base64url\");\n\t\tconst workspace = Object.freeze({\n\t\t\tid: randomBytes(18).toString(\"base64url\"),\n\t\t\troot,\n\t\t\tpathFlavor: \"posix\" as const,\n\t\t});\n\t\tlet runtime: DaemonWorkspaceRuntime;\n\t\ttry {\n\t\t\truntime = this.createChannelRuntime(root);\n\t\t} catch {\n\t\t\t// A bad child project configuration is an open failure, not a corrupt\n\t\t\t// physical envelope. Keep already bound sibling channels usable.\n\t\t\tawait this.send({\n\t\t\t\ttype: \"control_error\",\n\t\t\t\tid,\n\t\t\t\tcode: \"invalid_request\",\n\t\t\t\tmessage: \"Unable to initialize shared workspace channel\",\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tconst handler = runtime.createHandler(workspace, true);\n\t\tlet channel!: Channel;\n\t\tconst protocol = new RemoteWorkspaceServerProtocol(\n\t\t\t{\n\t\t\t\tsend: (payload: RemoteWorkspaceServerMessage) =>\n\t\t\t\t\tthis.channels.has(channelId) && !this.closed\n\t\t\t\t\t\t? this.send({ type: \"channel\", channelId, payload })\n\t\t\t\t\t\t: Promise.reject(new Error(\"Shared workspace channel is closed\")),\n\t\t\t\tclose: async (reason) => {\n\t\t\t\t\tconst channel = this.channels.get(channelId);\n\t\t\t\t\tif (channel) {\n\t\t\t\t\t\tthis.channels.delete(channelId);\n\t\t\t\t\t\tvoid this.disposeChannel(channel);\n\t\t\t\t\t\tif (!this.closed) await this.send({ type: \"closed\", channelId, reason });\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tvalidateToolArguments: (toolName, value) => channel.handler.validateToolArguments(toolName, value),\n\t\t\t\thandleRequest: (request, context) =>\n\t\t\t\t\tthis.budget.run(request.method, async () => {\n\t\t\t\t\t\tconst result = await withSharedRevisionGuard(\n\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t\tworkspace.root,\n\t\t\t\t\t\t\tchannel.runtime.operations,\n\t\t\t\t\t\t\tcontext,\n\t\t\t\t\t\t\t() => channel.handler.handleRequest(request, context),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (request.method !== \"workspace.read\") return result;\n\t\t\t\t\t\tconst read = result as { contentBase64: string };\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t...read,\n\t\t\t\t\t\t\trevision: createHash(\"sha256\").update(Buffer.from(read.contentBase64, \"base64\")).digest(\"hex\"),\n\t\t\t\t\t\t};\n\t\t\t\t\t}),\n\t\t\t},\n\t\t\t{\n\t\t\t\tworkspace,\n\t\t\t\tcatalog: runtime.catalog,\n\t\t\t\tcapabilities: runtime.capabilities,\n\t\t\t\tlimits: this.configuration.protocolLimits,\n\t\t\t\tbackendLabel: \"grip-shared-workspace-daemon\",\n\t\t\t},\n\t\t);\n\t\tchannel = { runtime, handler, protocol, workspace, generation: runtime.catalog.generation };\n\t\tthis.channels.set(channelId, channel);\n\t\ttry {\n\t\t\tawait this.send({ type: \"opened\", id, channelId });\n\t\t} catch (error) {\n\t\t\tthis.channels.delete(channelId);\n\t\t\tvoid this.disposeChannel(channel);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate disposeChannel(channel: Channel): Promise<void> {\n\t\tconst existing = this.channelDisposals.get(channel);\n\t\tif (existing) return existing;\n\t\tconst disposal = (async () => {\n\t\t\tawait channel.protocol.disconnect().catch(() => undefined);\n\t\t\tawait channel.runtime.dispose().catch(() => undefined);\n\t\t})();\n\t\tthis.channelDisposals.set(channel, disposal);\n\t\tthis.pendingDisposals.add(disposal);\n\t\tvoid disposal.then(\n\t\t\t() => this.pendingDisposals.delete(disposal),\n\t\t\t() => this.pendingDisposals.delete(disposal),\n\t\t);\n\t\treturn disposal;\n\t}\n\n\tasync disconnect(): Promise<void> {\n\t\tthis.closed = true;\n\t\tconst channels = [...this.channels.values()];\n\t\tthis.channels.clear();\n\t\tawait Promise.allSettled([...channels.map((channel) => this.disposeChannel(channel)), ...this.pendingDisposals]);\n\t}\n}\n"]}
|