@opengsd/gsd-pi 1.9.0-dev.97ef11e3 → 1.9.0-dev.e10b60d4
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/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +4 -24
- package/dist/resources/extensions/gsd/auto/run-unit.js +1 -19
- package/dist/resources/extensions/gsd/auto-start.js +9 -29
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +3 -7
- package/dist/resources/extensions/gsd/auto.js +1 -12
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -21
- package/dist/resources/extensions/gsd/flat-phase-migration.js +0 -12
- package/dist/resources/extensions/gsd/gsd-db.js +31 -75
- package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -20
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
- package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/README.md +3 -9
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cli.js +15 -121
- package/packages/gsd-cloud/dist/cli.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.js +1 -7
- package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +3 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.js +7 -9
- package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
- package/packages/gsd-cloud/package.json +2 -2
- package/packages/mcp-server/dist/cli-runner.d.ts +0 -3
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
- package/packages/mcp-server/dist/cli-runner.js +33 -119
- package/packages/mcp-server/dist/cli-runner.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +0 -17
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +0 -17
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop.ts +4 -48
- package/src/resources/extensions/gsd/auto/run-unit.ts +1 -20
- package/src/resources/extensions/gsd/auto-start.ts +8 -31
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +3 -10
- package/src/resources/extensions/gsd/auto.ts +1 -13
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -27
- package/src/resources/extensions/gsd/flat-phase-migration.ts +0 -14
- package/src/resources/extensions/gsd/gsd-db.ts +33 -85
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +0 -156
- package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +1 -21
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +1 -74
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +0 -83
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +9 -8
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +0 -32
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +0 -13
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +0 -60
- package/src/resources/extensions/gsd/worktree-state-projection.ts +24 -21
- package/packages/gsd-cloud/dist/background-cli.test.d.ts +0 -2
- package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +0 -1
- package/packages/gsd-cloud/dist/background-cli.test.js +0 -248
- package/packages/gsd-cloud/dist/background-cli.test.js.map +0 -1
- package/packages/gsd-cloud/dist/runtime-process.d.ts +0 -27
- package/packages/gsd-cloud/dist/runtime-process.d.ts.map +0 -1
- package/packages/gsd-cloud/dist/runtime-process.js +0 -272
- package/packages/gsd-cloud/dist/runtime-process.js.map +0 -1
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts +0 -2
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +0 -1
- package/packages/gsd-cloud/dist/runtime-process.test.js +0 -97
- package/packages/gsd-cloud/dist/runtime-process.test.js.map +0 -1
- /package/dist/web/standalone/.next/static/{DXD9fZPnqtjZSlT5xdC3v → V9MAVyYVPboQUB9KTTAMi}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{DXD9fZPnqtjZSlT5xdC3v → V9MAVyYVPboQUB9KTTAMi}/_ssgManifest.js +0 -0
|
@@ -45,7 +45,7 @@ export function parseCloudGatewayUrl(value) {
|
|
|
45
45
|
url.search = "";
|
|
46
46
|
return url;
|
|
47
47
|
}
|
|
48
|
-
export function saveCloudConfig(configPath, nextCloud
|
|
48
|
+
export function saveCloudConfig(configPath, nextCloud) {
|
|
49
49
|
let raw = {};
|
|
50
50
|
try {
|
|
51
51
|
raw = parseYaml(readFileSync(configPath, "utf-8")) ?? {};
|
|
@@ -59,12 +59,6 @@ export function saveCloudConfig(configPath, nextCloud, projectDirs) {
|
|
|
59
59
|
gateway_url: parseCloudGatewayUrl(nextCloud.gateway_url).toString(),
|
|
60
60
|
...(deviceToken ? { device_token_encrypted: protectCloudDeviceToken(deviceToken) } : {}),
|
|
61
61
|
};
|
|
62
|
-
if (projectDirs) {
|
|
63
|
-
const projects = raw.projects != null && typeof raw.projects === "object"
|
|
64
|
-
? raw.projects
|
|
65
|
-
: {};
|
|
66
|
-
raw.projects = { ...projects, scan_roots: projectDirs };
|
|
67
|
-
}
|
|
68
62
|
mkdirSync(dirname(configPath), { recursive: true });
|
|
69
63
|
writeConfigFile(configPath, stringifyYaml(raw));
|
|
70
64
|
return loadConfig(configPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-config.js","sourceRoot":"","sources":["../src/cloud-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { chmodSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { lookup } from \"node:dns\";\nimport type { LookupOptions } from \"node:dns\";\nimport { request as httpRequest } from \"node:http\";\nimport { request as httpsRequest } from \"node:https\";\nimport { isIP } from \"node:net\";\nimport type { LookupFunction } from \"node:net\";\nimport { dirname } from \"node:path\";\nimport { parse as parseYaml, stringify as stringifyYaml } from \"yaml\";\nimport { loadConfig } from \"./config.js\";\nimport { protectCloudDeviceToken } from \"./cloud-token.js\";\nimport type { DaemonConfig } from \"./types.js\";\n\nexport interface PairingExchangeResult {\n runtimeId: string;\n deviceToken: string;\n}\n\nexport async function exchangePairingCode(params: {\n gatewayUrl: string;\n code: string;\n runtimeName?: string;\n}): Promise<PairingExchangeResult> {\n const pairingUrl = new URL(\"/pairing/exchange\", parseCloudGatewayUrl(params.gatewayUrl));\n const body = await postJsonToValidatedGateway(pairingUrl, {\n code: params.code,\n runtimeName: params.runtimeName,\n });\n if (typeof body.runtimeId !== \"string\" || typeof body.deviceToken !== \"string\") {\n throw new Error(\"Pairing response did not include runtimeId and deviceToken\");\n }\n return { runtimeId: body.runtimeId, deviceToken: body.deviceToken };\n}\n\nexport function parseCloudGatewayUrl(value: string): URL {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new Error(\"Cloud gateway URL must be an absolute HTTP(S) URL\");\n }\n\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n throw new Error(\"Cloud gateway URL must use http or https\");\n }\n if (url.username || url.password) {\n throw new Error(\"Cloud gateway URL must not include credentials\");\n }\n if (url.hash) {\n throw new Error(\"Cloud gateway URL must not include a fragment\");\n }\n if (url.protocol === \"http:\" && !isLoopbackHost(url.hostname)) {\n throw new Error(\"Plain HTTP cloud gateway URLs are only allowed for localhost\");\n }\n if (url.protocol === \"https:\" && isPrivateIpHost(url.hostname)) {\n throw new Error(\"Cloud gateway URL must not target private or loopback IP addresses\");\n }\n\n url.pathname = url.pathname.replace(/\\/+$/, \"\");\n url.search = \"\";\n return url;\n}\n\nexport function saveCloudConfig(\n configPath: string,\n nextCloud: NonNullable<DaemonConfig[\"cloud\"]>,\n projectDirs?: string[],\n): DaemonConfig {\n let raw: Record<string, unknown> = {};\n try {\n raw = parseYaml(readFileSync(configPath, \"utf-8\")) as Record<string, unknown> ?? {};\n } catch {\n raw = {};\n }\n const { device_token: deviceToken, ...cloud } = nextCloud;\n raw.cloud = {\n ...cloud,\n gateway_url: parseCloudGatewayUrl(nextCloud.gateway_url).toString(),\n ...(deviceToken ? { device_token_encrypted: protectCloudDeviceToken(deviceToken) } : {}),\n };\n if (projectDirs) {\n const projects = raw.projects != null && typeof raw.projects === \"object\"\n ? raw.projects as Record<string, unknown>\n : {};\n raw.projects = { ...projects, scan_roots: projectDirs };\n }\n mkdirSync(dirname(configPath), { recursive: true });\n writeConfigFile(configPath, stringifyYaml(raw));\n return loadConfig(configPath);\n}\n\nexport function clearCloudConfig(configPath: string): DaemonConfig {\n let raw: Record<string, unknown> = {};\n try {\n raw = parseYaml(readFileSync(configPath, \"utf-8\")) as Record<string, unknown> ?? {};\n } catch {\n raw = {};\n }\n delete raw.cloud;\n mkdirSync(dirname(configPath), { recursive: true });\n writeConfigFile(configPath, stringifyYaml(raw));\n return loadConfig(configPath);\n}\n\nexport function redactedCloudStatus(config: DaemonConfig): Record<string, unknown> {\n const cloud = config.cloud;\n if (!cloud) return { configured: false };\n return {\n configured: true,\n enabled: cloud.enabled ?? true,\n gateway_url: cloud.gateway_url,\n runtime_id: cloud.runtime_id ?? null,\n runtime_name: cloud.runtime_name ?? null,\n [\"device_\" + \"token\"]: cloud.device_token ? \"[redacted]\" : null,\n };\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n const host = hostname.toLowerCase();\n return host === \"localhost\" || host === \"127.0.0.1\" || host === \"::1\" || host === \"[::1]\";\n}\n\nfunction isPrivateIpHost(hostname: string): boolean {\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n if (host === \"localhost\") return true;\n if (isIP(host) === 4) return isPrivateIpv4(host);\n if (isIP(host) === 6) return isPrivateIpv6(host);\n return false;\n}\n\nexport function validateGatewayNetworkTarget(url: URL): void {\n if (url.protocol === \"http:\" && isLoopbackHost(url.hostname)) return;\n if (isPrivateIpHost(url.hostname)) {\n throw new Error(\"Cloud gateway URL must not target private or loopback IP addresses\");\n }\n}\n\nexport function createGatewayLookup(url: URL): LookupFunction {\n const allowLoopback = url.protocol === \"http:\" && isLoopbackHost(url.hostname);\n const rejected = (address: string): boolean =>\n !address || (!allowLoopback && isPrivateIpHost(address));\n // Node's socket connect (autoSelectFamily / Happy Eyeballs, default-on since\n // Node 20) calls a custom lookup with `all: true` and expects an ARRAY\n // callback. Forcing `all: false` + a scalar callback made every real gateway\n // request throw \"Invalid IP address: undefined\". Honor both forms; the SSRF\n // guard is applied to every resolved address (reject the whole lookup if any\n // is private/loopback — an attacker-controlled resolver must not slip one in).\n return ((hostname: string, options: unknown, callback: (...args: unknown[]) => void) => {\n const wantsAll =\n typeof options === \"object\" && options !== null && (options as LookupOptions).all === true;\n const base: LookupOptions =\n typeof options === \"number\" ? { family: options } : { ...(options as LookupOptions) };\n if (wantsAll) {\n lookup(hostname, { ...base, all: true }, (err, addresses) => {\n if (err) return callback(err);\n const list = addresses as Array<{ address: string; family: number }>;\n if (!list.length || list.some((entry) => rejected(entry.address))) {\n return callback(new Error(\"Cloud gateway URL resolved to a private or loopback address\"));\n }\n callback(null, list);\n });\n return;\n }\n lookup(hostname, { ...base, all: false }, (err, address, family) => {\n if (err) return callback(err, address, family);\n if (rejected(address)) {\n return callback(new Error(\"Cloud gateway URL resolved to a private or loopback address\"), address, family);\n }\n callback(null, address, family);\n });\n }) as unknown as LookupFunction;\n}\n\nfunction isPrivateIpv4(host: string): boolean {\n const octets = host.split(\".\").map((part) => Number(part));\n if (octets.length !== 4 || octets.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return true;\n const [a, b] = octets;\n if (a === 10 || a === 127 || a === 0) return true;\n if (a === 169 && b === 254) return true;\n if (a === 172 && b >= 16 && b <= 31) return true;\n if (a === 192 && b === 168) return true;\n if (a === 100 && b >= 64 && b <= 127) return true;\n if (a === 192 && b === 0) return true;\n if (a === 198 && (b === 18 || b === 19)) return true;\n if (a >= 224) return true;\n return false;\n}\n\nfunction isPrivateIpv6(host: string): boolean {\n return host === \"::\"\n || host === \"::1\"\n || host.startsWith(\"fc\")\n || host.startsWith(\"fd\")\n || host.startsWith(\"fe80:\")\n || host.startsWith(\"2001:db8:\");\n}\n\nfunction writeConfigFile(configPath: string, contents: string): void {\n writeFileSync(configPath, contents, { encoding: \"utf-8\", mode: 0o600 });\n chmodSync(configPath, 0o600);\n}\n\nconst GATEWAY_REQUEST_TIMEOUT_MS = 30_000;\n\nexport function postJsonToValidatedGateway(\n url: URL,\n payload: Record<string, unknown>,\n timeoutMs: number = GATEWAY_REQUEST_TIMEOUT_MS,\n): Promise<Record<string, unknown>> {\n validateGatewayNetworkTarget(url);\n const body = JSON.stringify(payload);\n const requestImpl = url.protocol === \"https:\" ? httpsRequest : httpRequest;\n\n return new Promise((resolve, reject) => {\n const req = requestImpl({\n protocol: url.protocol,\n hostname: url.hostname,\n port: url.port,\n path: `${url.pathname}${url.search}`,\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": Buffer.byteLength(body),\n },\n lookup: createGatewayLookup(url),\n }, (res) => {\n const statusCode = res.statusCode ?? 0;\n const chunks: Buffer[] = [];\n res.on(\"data\", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));\n res.on(\"end\", () => {\n const responseText = Buffer.concat(chunks).toString(\"utf-8\");\n const parsed = parseJsonObject(responseText);\n if (statusCode < 200 || statusCode >= 300) {\n reject(new Error(typeof parsed.error === \"string\" ? parsed.error : `Pairing failed with HTTP ${statusCode}`));\n return;\n }\n resolve(parsed);\n });\n });\n\n req.setTimeout(timeoutMs, () => {\n req.destroy(new Error(`Gateway request timed out after ${timeoutMs}ms`));\n });\n req.on(\"error\", reject);\n req.end(body);\n });\n}\n\nfunction parseJsonObject(value: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(value) as unknown;\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? parsed as Record<string, unknown>\n : {};\n } catch {\n return {};\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cloud-config.js","sourceRoot":"","sources":["../src/cloud-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { chmodSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { lookup } from \"node:dns\";\nimport type { LookupOptions } from \"node:dns\";\nimport { request as httpRequest } from \"node:http\";\nimport { request as httpsRequest } from \"node:https\";\nimport { isIP } from \"node:net\";\nimport type { LookupFunction } from \"node:net\";\nimport { dirname } from \"node:path\";\nimport { parse as parseYaml, stringify as stringifyYaml } from \"yaml\";\nimport { loadConfig } from \"./config.js\";\nimport { protectCloudDeviceToken } from \"./cloud-token.js\";\nimport type { DaemonConfig } from \"./types.js\";\n\nexport interface PairingExchangeResult {\n runtimeId: string;\n deviceToken: string;\n}\n\nexport async function exchangePairingCode(params: {\n gatewayUrl: string;\n code: string;\n runtimeName?: string;\n}): Promise<PairingExchangeResult> {\n const pairingUrl = new URL(\"/pairing/exchange\", parseCloudGatewayUrl(params.gatewayUrl));\n const body = await postJsonToValidatedGateway(pairingUrl, {\n code: params.code,\n runtimeName: params.runtimeName,\n });\n if (typeof body.runtimeId !== \"string\" || typeof body.deviceToken !== \"string\") {\n throw new Error(\"Pairing response did not include runtimeId and deviceToken\");\n }\n return { runtimeId: body.runtimeId, deviceToken: body.deviceToken };\n}\n\nexport function parseCloudGatewayUrl(value: string): URL {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n throw new Error(\"Cloud gateway URL must be an absolute HTTP(S) URL\");\n }\n\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n throw new Error(\"Cloud gateway URL must use http or https\");\n }\n if (url.username || url.password) {\n throw new Error(\"Cloud gateway URL must not include credentials\");\n }\n if (url.hash) {\n throw new Error(\"Cloud gateway URL must not include a fragment\");\n }\n if (url.protocol === \"http:\" && !isLoopbackHost(url.hostname)) {\n throw new Error(\"Plain HTTP cloud gateway URLs are only allowed for localhost\");\n }\n if (url.protocol === \"https:\" && isPrivateIpHost(url.hostname)) {\n throw new Error(\"Cloud gateway URL must not target private or loopback IP addresses\");\n }\n\n url.pathname = url.pathname.replace(/\\/+$/, \"\");\n url.search = \"\";\n return url;\n}\n\nexport function saveCloudConfig(configPath: string, nextCloud: NonNullable<DaemonConfig[\"cloud\"]>): DaemonConfig {\n let raw: Record<string, unknown> = {};\n try {\n raw = parseYaml(readFileSync(configPath, \"utf-8\")) as Record<string, unknown> ?? {};\n } catch {\n raw = {};\n }\n const { device_token: deviceToken, ...cloud } = nextCloud;\n raw.cloud = {\n ...cloud,\n gateway_url: parseCloudGatewayUrl(nextCloud.gateway_url).toString(),\n ...(deviceToken ? { device_token_encrypted: protectCloudDeviceToken(deviceToken) } : {}),\n };\n mkdirSync(dirname(configPath), { recursive: true });\n writeConfigFile(configPath, stringifyYaml(raw));\n return loadConfig(configPath);\n}\n\nexport function clearCloudConfig(configPath: string): DaemonConfig {\n let raw: Record<string, unknown> = {};\n try {\n raw = parseYaml(readFileSync(configPath, \"utf-8\")) as Record<string, unknown> ?? {};\n } catch {\n raw = {};\n }\n delete raw.cloud;\n mkdirSync(dirname(configPath), { recursive: true });\n writeConfigFile(configPath, stringifyYaml(raw));\n return loadConfig(configPath);\n}\n\nexport function redactedCloudStatus(config: DaemonConfig): Record<string, unknown> {\n const cloud = config.cloud;\n if (!cloud) return { configured: false };\n return {\n configured: true,\n enabled: cloud.enabled ?? true,\n gateway_url: cloud.gateway_url,\n runtime_id: cloud.runtime_id ?? null,\n runtime_name: cloud.runtime_name ?? null,\n [\"device_\" + \"token\"]: cloud.device_token ? \"[redacted]\" : null,\n };\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n const host = hostname.toLowerCase();\n return host === \"localhost\" || host === \"127.0.0.1\" || host === \"::1\" || host === \"[::1]\";\n}\n\nfunction isPrivateIpHost(hostname: string): boolean {\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, \"\");\n if (host === \"localhost\") return true;\n if (isIP(host) === 4) return isPrivateIpv4(host);\n if (isIP(host) === 6) return isPrivateIpv6(host);\n return false;\n}\n\nexport function validateGatewayNetworkTarget(url: URL): void {\n if (url.protocol === \"http:\" && isLoopbackHost(url.hostname)) return;\n if (isPrivateIpHost(url.hostname)) {\n throw new Error(\"Cloud gateway URL must not target private or loopback IP addresses\");\n }\n}\n\nexport function createGatewayLookup(url: URL): LookupFunction {\n const allowLoopback = url.protocol === \"http:\" && isLoopbackHost(url.hostname);\n const rejected = (address: string): boolean =>\n !address || (!allowLoopback && isPrivateIpHost(address));\n // Node's socket connect (autoSelectFamily / Happy Eyeballs, default-on since\n // Node 20) calls a custom lookup with `all: true` and expects an ARRAY\n // callback. Forcing `all: false` + a scalar callback made every real gateway\n // request throw \"Invalid IP address: undefined\". Honor both forms; the SSRF\n // guard is applied to every resolved address (reject the whole lookup if any\n // is private/loopback — an attacker-controlled resolver must not slip one in).\n return ((hostname: string, options: unknown, callback: (...args: unknown[]) => void) => {\n const wantsAll =\n typeof options === \"object\" && options !== null && (options as LookupOptions).all === true;\n const base: LookupOptions =\n typeof options === \"number\" ? { family: options } : { ...(options as LookupOptions) };\n if (wantsAll) {\n lookup(hostname, { ...base, all: true }, (err, addresses) => {\n if (err) return callback(err);\n const list = addresses as Array<{ address: string; family: number }>;\n if (!list.length || list.some((entry) => rejected(entry.address))) {\n return callback(new Error(\"Cloud gateway URL resolved to a private or loopback address\"));\n }\n callback(null, list);\n });\n return;\n }\n lookup(hostname, { ...base, all: false }, (err, address, family) => {\n if (err) return callback(err, address, family);\n if (rejected(address)) {\n return callback(new Error(\"Cloud gateway URL resolved to a private or loopback address\"), address, family);\n }\n callback(null, address, family);\n });\n }) as unknown as LookupFunction;\n}\n\nfunction isPrivateIpv4(host: string): boolean {\n const octets = host.split(\".\").map((part) => Number(part));\n if (octets.length !== 4 || octets.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return true;\n const [a, b] = octets;\n if (a === 10 || a === 127 || a === 0) return true;\n if (a === 169 && b === 254) return true;\n if (a === 172 && b >= 16 && b <= 31) return true;\n if (a === 192 && b === 168) return true;\n if (a === 100 && b >= 64 && b <= 127) return true;\n if (a === 192 && b === 0) return true;\n if (a === 198 && (b === 18 || b === 19)) return true;\n if (a >= 224) return true;\n return false;\n}\n\nfunction isPrivateIpv6(host: string): boolean {\n return host === \"::\"\n || host === \"::1\"\n || host.startsWith(\"fc\")\n || host.startsWith(\"fd\")\n || host.startsWith(\"fe80:\")\n || host.startsWith(\"2001:db8:\");\n}\n\nfunction writeConfigFile(configPath: string, contents: string): void {\n writeFileSync(configPath, contents, { encoding: \"utf-8\", mode: 0o600 });\n chmodSync(configPath, 0o600);\n}\n\nconst GATEWAY_REQUEST_TIMEOUT_MS = 30_000;\n\nexport function postJsonToValidatedGateway(\n url: URL,\n payload: Record<string, unknown>,\n timeoutMs: number = GATEWAY_REQUEST_TIMEOUT_MS,\n): Promise<Record<string, unknown>> {\n validateGatewayNetworkTarget(url);\n const body = JSON.stringify(payload);\n const requestImpl = url.protocol === \"https:\" ? httpsRequest : httpRequest;\n\n return new Promise((resolve, reject) => {\n const req = requestImpl({\n protocol: url.protocol,\n hostname: url.hostname,\n port: url.port,\n path: `${url.pathname}${url.search}`,\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n \"content-length\": Buffer.byteLength(body),\n },\n lookup: createGatewayLookup(url),\n }, (res) => {\n const statusCode = res.statusCode ?? 0;\n const chunks: Buffer[] = [];\n res.on(\"data\", (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));\n res.on(\"end\", () => {\n const responseText = Buffer.concat(chunks).toString(\"utf-8\");\n const parsed = parseJsonObject(responseText);\n if (statusCode < 200 || statusCode >= 300) {\n reject(new Error(typeof parsed.error === \"string\" ? parsed.error : `Pairing failed with HTTP ${statusCode}`));\n return;\n }\n resolve(parsed);\n });\n });\n\n req.setTimeout(timeoutMs, () => {\n req.destroy(new Error(`Gateway request timed out after ${timeoutMs}ms`));\n });\n req.on(\"error\", reject);\n req.end(body);\n });\n}\n\nfunction parseJsonObject(value: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(value) as unknown;\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? parsed as Record<string, unknown>\n : {};\n } catch {\n return {};\n }\n}\n"]}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { Logger } from "./logger.js";
|
|
2
2
|
import type { DaemonConfig } from "./types.js";
|
|
3
3
|
import type { Executor } from "./executors/executor.js";
|
|
4
|
-
export declare const CLOUD_RUNTIME_INITIAL_CONNECT_WINDOW_MS: number;
|
|
5
4
|
export declare class CloudRuntime {
|
|
6
5
|
private readonly cloud;
|
|
7
6
|
private readonly executor;
|
|
8
7
|
private readonly logger;
|
|
9
8
|
private static readonly MAX_OUTBOX;
|
|
9
|
+
private static readonly RECONNECT_DELAY_MS;
|
|
10
|
+
private static readonly MAX_INITIAL_CONNECT_ATTEMPTS;
|
|
11
|
+
private static readonly INITIAL_CONNECT_HANDSHAKE_TIMEOUT_MS;
|
|
10
12
|
private socket;
|
|
11
13
|
private heartbeat;
|
|
12
14
|
private reconnect;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-runtime.d.ts","sourceRoot":"","sources":["../src/cloud-runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"cloud-runtime.d.ts","sourceRoot":"","sources":["../src/cloud-runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAWxD,qBAAa,YAAY;IAoBrB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM;IArBzB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAO;IACzC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAMnD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,4BAA4B,CAAK;IACzD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oCAAoC,CAAU;IACtE,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,SAAS,CAA4C;IAC7D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqC;IAC9D,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,oBAAoB,CAAyC;IACrE,OAAO,CAAC,sBAAsB,CAAK;gBAGhB,KAAK,EAAE,WAAW,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,EACzC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM;IAGjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQtB,IAAI,IAAI,IAAI;IAcZ,OAAO,CAAC,OAAO;IAkDf,OAAO,CAAC,gBAAgB;YAmBV,mBAAmB;IAKjC,OAAO,CAAC,iBAAiB;IA+BzB,OAAO,CAAC,iBAAiB;YAKX,iBAAiB;YAUjB,aAAa;YA6Bb,cAAc;IAqB5B,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,IAAI;CAcb"}
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
2
|
import { createGatewayLookup, parseCloudGatewayUrl, validateGatewayNetworkTarget } from "./cloud-config.js";
|
|
3
|
-
const INITIAL_CONNECT_ATTEMPTS = 5;
|
|
4
|
-
const INITIAL_CONNECT_HANDSHAKE_TIMEOUT_MS = 30_000;
|
|
5
|
-
const RECONNECT_DELAY_MS = 5_000;
|
|
6
|
-
export const CLOUD_RUNTIME_INITIAL_CONNECT_WINDOW_MS = INITIAL_CONNECT_ATTEMPTS * INITIAL_CONNECT_HANDSHAKE_TIMEOUT_MS
|
|
7
|
-
+ (INITIAL_CONNECT_ATTEMPTS - 1) * RECONNECT_DELAY_MS;
|
|
8
3
|
export class CloudRuntime {
|
|
9
4
|
cloud;
|
|
10
5
|
executor;
|
|
11
6
|
logger;
|
|
12
7
|
static MAX_OUTBOX = 200;
|
|
8
|
+
static RECONNECT_DELAY_MS = 5_000;
|
|
13
9
|
// How many times to retry the initial connect before rejecting start(). A
|
|
14
10
|
// transient handshake failure (gateway briefly unreachable, DNS hiccup) should
|
|
15
11
|
// retry like the daemon's reconnect loop rather than kill the runtime; a
|
|
16
12
|
// persistent failure (gateway down, session rejected) must eventually reject so
|
|
17
13
|
// the CLI reports an error instead of hanging or exiting silently.
|
|
14
|
+
static MAX_INITIAL_CONNECT_ATTEMPTS = 5;
|
|
15
|
+
static INITIAL_CONNECT_HANDSHAKE_TIMEOUT_MS = 30_000;
|
|
18
16
|
socket;
|
|
19
17
|
heartbeat;
|
|
20
18
|
reconnect;
|
|
@@ -74,7 +72,7 @@ export class CloudRuntime {
|
|
|
74
72
|
const socket = new WebSocket(url, {
|
|
75
73
|
headers: { Authorization: `Bearer ${this.cloud.device_token}` },
|
|
76
74
|
lookup: createGatewayLookup(gatewayUrl),
|
|
77
|
-
handshakeTimeout: INITIAL_CONNECT_HANDSHAKE_TIMEOUT_MS,
|
|
75
|
+
handshakeTimeout: CloudRuntime.INITIAL_CONNECT_HANDSHAKE_TIMEOUT_MS,
|
|
78
76
|
});
|
|
79
77
|
const previousSocket = this.socket;
|
|
80
78
|
this.socket = socket;
|
|
@@ -142,13 +140,13 @@ export class CloudRuntime {
|
|
|
142
140
|
// start() once the bounded attempts are exhausted, so a brief blip does
|
|
143
141
|
// not kill the runtime while a persistent outage still surfaces an error.
|
|
144
142
|
this.initialConnectAttempts += 1;
|
|
145
|
-
if (this.initialConnectAttempts >=
|
|
143
|
+
if (this.initialConnectAttempts >= CloudRuntime.MAX_INITIAL_CONNECT_ATTEMPTS) {
|
|
146
144
|
this.rejectFirstConnect(new Error(`cloud runtime connection failed after ${this.initialConnectAttempts} attempt(s)`));
|
|
147
145
|
return;
|
|
148
146
|
}
|
|
149
147
|
this.logger.warn("cloud runtime initial connect failed; retrying", {
|
|
150
148
|
attempt: this.initialConnectAttempts,
|
|
151
|
-
max:
|
|
149
|
+
max: CloudRuntime.MAX_INITIAL_CONNECT_ATTEMPTS,
|
|
152
150
|
});
|
|
153
151
|
}
|
|
154
152
|
else {
|
|
@@ -156,7 +154,7 @@ export class CloudRuntime {
|
|
|
156
154
|
}
|
|
157
155
|
if (this.reconnect)
|
|
158
156
|
clearTimeout(this.reconnect);
|
|
159
|
-
this.reconnect = setTimeout(() => this.connect(), RECONNECT_DELAY_MS);
|
|
157
|
+
this.reconnect = setTimeout(() => this.connect(), CloudRuntime.RECONNECT_DELAY_MS);
|
|
160
158
|
}
|
|
161
159
|
handleSocketError(socket, err) {
|
|
162
160
|
if (socket !== this.socket)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-runtime.js","sourceRoot":"","sources":["../src/cloud-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import WebSocket from \"ws\";\nimport type { Logger } from \"./logger.js\";\nimport type { DaemonConfig } from \"./types.js\";\nimport type { Executor } from \"./executors/executor.js\";\nimport { createGatewayLookup, parseCloudGatewayUrl, validateGatewayNetworkTarget } from \"./cloud-config.js\";\n\nconst INITIAL_CONNECT_ATTEMPTS = 5;\nconst INITIAL_CONNECT_HANDSHAKE_TIMEOUT_MS = 30_000;\nconst RECONNECT_DELAY_MS = 5_000;\n\nexport const CLOUD_RUNTIME_INITIAL_CONNECT_WINDOW_MS =\n INITIAL_CONNECT_ATTEMPTS * INITIAL_CONNECT_HANDSHAKE_TIMEOUT_MS\n + (INITIAL_CONNECT_ATTEMPTS - 1) * RECONNECT_DELAY_MS;\n\ninterface GatewayMessage {\n type: string;\n requestId?: string;\n toolName?: string;\n args?: Record<string, unknown>;\n projectAlias?: string;\n}\n\nexport class CloudRuntime {\n private static readonly MAX_OUTBOX = 200;\n // How many times to retry the initial connect before rejecting start(). A\n // transient handshake failure (gateway briefly unreachable, DNS hiccup) should\n // retry like the daemon's reconnect loop rather than kill the runtime; a\n // persistent failure (gateway down, session rejected) must eventually reject so\n // the CLI reports an error instead of hanging or exiting silently.\n private socket: WebSocket | undefined;\n private heartbeat: ReturnType<typeof setInterval> | undefined;\n private reconnect: ReturnType<typeof setTimeout> | undefined;\n private readonly inFlight = new Map<string, GatewayMessage>();\n private outbox: string[] = [];\n private stopped = false;\n private firstConnectDeferred: PromiseWithResolvers<void> | undefined;\n private initialConnectAttempts = 0;\n\n constructor(\n private readonly cloud: NonNullable<DaemonConfig[\"cloud\"]>,\n private readonly executor: Executor,\n private readonly logger: Logger,\n ) {}\n\n start(): Promise<void> {\n this.stopped = false;\n this.initialConnectAttempts = 0;\n this.firstConnectDeferred = Promise.withResolvers<void>();\n this.connect();\n return this.firstConnectDeferred.promise;\n }\n\n stop(): void {\n this.stopped = true;\n this.rejectFirstConnect(new Error(\"cloud runtime stopped\"));\n if (this.reconnect) clearTimeout(this.reconnect);\n this.reconnect = undefined;\n if (this.heartbeat) clearInterval(this.heartbeat);\n this.heartbeat = undefined;\n this.inFlight.clear();\n this.outbox = [];\n const socket = this.socket;\n this.socket = undefined;\n socket?.close();\n }\n\n private connect(): void {\n if (this.reconnect) clearTimeout(this.reconnect);\n this.reconnect = undefined;\n if (!this.cloud.device_token || !this.cloud.runtime_id) {\n this.logger.warn(\"cloud runtime skipped — missing device token or runtime id\");\n this.rejectFirstConnect(new Error(\"cloud runtime missing device token or runtime id\"));\n return;\n }\n const gatewayUrl = parseCloudGatewayUrl(this.cloud.gateway_url);\n try {\n validateGatewayNetworkTarget(gatewayUrl);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n this.logger.warn(\"cloud runtime skipped unsafe gateway URL\", { error: message });\n this.rejectFirstConnect(new Error(`cloud runtime unsafe gateway URL: ${message}`));\n return;\n }\n const url = new URL(\"/runtime/connect\", gatewayUrl);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n const socket = new WebSocket(url, {\n headers: { Authorization: `Bearer ${this.cloud.device_token}` },\n lookup: createGatewayLookup(gatewayUrl),\n handshakeTimeout: INITIAL_CONNECT_HANDSHAKE_TIMEOUT_MS,\n });\n const previousSocket = this.socket;\n this.socket = socket;\n if (previousSocket) {\n // Detach the old socket's handlers before closing so its listeners don't\n // linger on a socket we've already replaced (handlers also guard on\n // identity, but this releases them eagerly for GC).\n previousSocket.removeAllListeners();\n if (previousSocket.readyState !== WebSocket.CLOSING && previousSocket.readyState !== WebSocket.CLOSED) {\n previousSocket.close();\n }\n }\n\n socket.on(\"open\", () => {\n this.handleSocketOpen(socket);\n });\n socket.on(\"message\", (data) => {\n void this.handleSocketMessage(socket, data.toString(\"utf8\"));\n });\n socket.on(\"close\", () => {\n this.handleSocketClose(socket);\n });\n socket.on(\"error\", (err) => {\n this.handleSocketError(socket, err);\n });\n }\n\n private handleSocketOpen(socket: WebSocket): void {\n if (socket !== this.socket) return;\n this.resolveFirstConnect();\n this.logger.info(\"cloud runtime connected\", { gateway_url: this.cloud.gateway_url, runtime_id: this.cloud.runtime_id });\n // Re-advertise projects (async: the hello is sent on a later microtask), then\n // drain any messages buffered while disconnected. tool_results route by\n // requestId on the authenticated connection, so drain order vs the hello is\n // not significant.\n void this.advertiseProjects();\n const pending = this.outbox;\n this.outbox = [];\n for (const text of pending) {\n if (this.socket?.readyState === WebSocket.OPEN) this.socket.send(text);\n else this.outbox.push(text);\n }\n if (this.heartbeat) clearInterval(this.heartbeat);\n this.heartbeat = setInterval(() => this.send({ type: \"heartbeat\", at: Date.now() }), 30_000);\n }\n\n private async handleSocketMessage(socket: WebSocket, text: string): Promise<void> {\n if (socket !== this.socket) return;\n await this.handleMessage(text);\n }\n\n private handleSocketClose(socket: WebSocket): void {\n if (socket !== this.socket) return;\n if (this.heartbeat) clearInterval(this.heartbeat);\n this.heartbeat = undefined;\n this.socket = undefined;\n if (this.stopped) return;\n if (this.firstConnectDeferred) {\n // Still trying to establish the first connection: retry transient\n // handshake failures (like the daemon's reconnect loop) and only reject\n // start() once the bounded attempts are exhausted, so a brief blip does\n // not kill the runtime while a persistent outage still surfaces an error.\n this.initialConnectAttempts += 1;\n if (this.initialConnectAttempts >= INITIAL_CONNECT_ATTEMPTS) {\n this.rejectFirstConnect(\n new Error(\n `cloud runtime connection failed after ${this.initialConnectAttempts} attempt(s)`,\n ),\n );\n return;\n }\n this.logger.warn(\"cloud runtime initial connect failed; retrying\", {\n attempt: this.initialConnectAttempts,\n max: INITIAL_CONNECT_ATTEMPTS,\n });\n } else {\n this.logger.warn(\"cloud runtime disconnected; reconnecting\");\n }\n if (this.reconnect) clearTimeout(this.reconnect);\n this.reconnect = setTimeout(() => this.connect(), RECONNECT_DELAY_MS);\n }\n\n private handleSocketError(socket: WebSocket, err: Error): void {\n if (socket !== this.socket) return;\n this.logger.warn(\"cloud runtime socket error\", { error: err.message });\n }\n\n private async advertiseProjects(): Promise<void> {\n const projects = await this.executor.advertisedProjects();\n this.send({\n type: \"hello\",\n runtimeId: this.cloud.runtime_id,\n runtimeName: this.cloud.runtime_name,\n projects,\n });\n }\n\n private async handleMessage(text: string): Promise<void> {\n let message: GatewayMessage;\n try {\n message = JSON.parse(text) as GatewayMessage;\n } catch {\n return;\n }\n if (message.type === \"cancel\" && message.requestId) {\n void this.cancelInFlight(message.requestId);\n return;\n }\n if (message.type !== \"tool_call\" || !message.requestId || !message.toolName) return;\n this.inFlight.set(message.requestId, message);\n try {\n const result = await this.executor.execute(message.toolName, message.args ?? {}, message.projectAlias);\n if (!this.inFlight.has(message.requestId)) return;\n this.send({ type: \"tool_result\", requestId: message.requestId, result });\n } catch (err) {\n if (!this.inFlight.has(message.requestId)) return;\n this.send({\n type: \"tool_result\",\n requestId: message.requestId,\n error: err instanceof Error ? err.message : String(err),\n });\n } finally {\n this.inFlight.delete(message.requestId);\n }\n }\n\n private async cancelInFlight(requestId: string): Promise<void> {\n const pending = this.inFlight.get(requestId);\n if (!pending) return;\n this.inFlight.delete(requestId);\n try {\n if (typeof pending.args?.sessionId === \"string\") {\n await this.executor.execute(\"gsd_cancel\", { sessionId: pending.args.sessionId }, pending.projectAlias);\n return;\n }\n const projectDir = typeof pending.args?.projectDir === \"string\" ? pending.args.projectDir : pending.projectAlias;\n if (projectDir) {\n await this.executor.execute(\"gsd_cancel\", { projectDir });\n }\n } catch (err) {\n this.logger.warn(\"cloud runtime cancel failed\", {\n requestId,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n private resolveFirstConnect(): void {\n const deferred = this.firstConnectDeferred;\n if (!deferred) return;\n this.firstConnectDeferred = undefined;\n deferred.resolve();\n }\n\n private rejectFirstConnect(err: Error): void {\n const deferred = this.firstConnectDeferred;\n if (!deferred) return;\n this.firstConnectDeferred = undefined;\n deferred.reject(err);\n }\n\n private send(message: unknown): void {\n const text = JSON.stringify(message);\n if (this.socket?.readyState === WebSocket.OPEN) {\n this.socket.send(text);\n return;\n }\n // Buffer while disconnected; flushed on reconnect in handleSocketOpen. Bounded\n // so a long outage cannot grow memory without limit — a stale heartbeat is\n // worth less than a fresh tool_result, so drop oldest first.\n this.outbox.push(text);\n if (this.outbox.length > CloudRuntime.MAX_OUTBOX) {\n this.outbox.shift();\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cloud-runtime.js","sourceRoot":"","sources":["../src/cloud-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAI3B,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AAU5G,MAAM,OAAO,YAAY;IAoBJ;IACA;IACA;IArBX,MAAM,CAAU,UAAU,GAAG,GAAG,CAAC;IACjC,MAAM,CAAU,kBAAkB,GAAG,KAAK,CAAC;IACnD,0EAA0E;IAC1E,+EAA+E;IAC/E,yEAAyE;IACzE,gFAAgF;IAChF,mEAAmE;IAC3D,MAAM,CAAU,4BAA4B,GAAG,CAAC,CAAC;IACjD,MAAM,CAAU,oCAAoC,GAAG,MAAM,CAAC;IAC9D,MAAM,CAAwB;IAC9B,SAAS,CAA6C;IACtD,SAAS,CAA4C;IAC5C,QAAQ,GAAG,IAAI,GAAG,EAA0B,CAAC;IACtD,MAAM,GAAa,EAAE,CAAC;IACtB,OAAO,GAAG,KAAK,CAAC;IAChB,oBAAoB,CAAyC;IAC7D,sBAAsB,GAAG,CAAC,CAAC;IAEnC,YACmB,KAAyC,EACzC,QAAkB,EAClB,MAAc;QAFd,UAAK,GAAL,KAAK,CAAoC;QACzC,aAAQ,GAAR,QAAQ,CAAU;QAClB,WAAM,GAAN,MAAM,CAAQ;IAC9B,CAAC;IAEJ,KAAK;QACH,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,sBAAsB,GAAG,CAAC,CAAC;QAChC,IAAI,CAAC,oBAAoB,GAAG,OAAO,CAAC,aAAa,EAAQ,CAAC;QAC1D,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,OAAO,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC;IAC3C,CAAC;IAED,IAAI;QACF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,kBAAkB,CAAC,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;QAC5D,IAAI,IAAI,CAAC,SAAS;YAAE,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,IAAI,CAAC,SAAS;YAAE,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QACjB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,MAAM,EAAE,KAAK,EAAE,CAAC;IAClB,CAAC;IAEO,OAAO;QACb,IAAI,IAAI,CAAC,SAAS;YAAE,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;YACvD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,4DAA4D,CAAC,CAAC;YAC/E,IAAI,CAAC,kBAAkB,CAAC,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC,CAAC;YACvF,OAAO;QACT,CAAC;QACD,MAAM,UAAU,GAAG,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAChE,IAAI,CAAC;YACH,4BAA4B,CAAC,UAAU,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,0CAA0C,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;YACjF,IAAI,CAAC,kBAAkB,CAAC,IAAI,KAAK,CAAC,qCAAqC,OAAO,EAAE,CAAC,CAAC,CAAC;YACnF,OAAO;QACT,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAC;QACpD,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;QAC1D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE;YAChC,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE;YAC/D,MAAM,EAAE,mBAAmB,CAAC,UAAU,CAAC;YACvC,gBAAgB,EAAE,YAAY,CAAC,oCAAoC;SACpE,CAAC,CAAC;QACH,MAAM,cAAc,GAAG,IAAI,CAAC,MAAM,CAAC;QACnC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,cAAc,EAAE,CAAC;YACnB,yEAAyE;YACzE,oEAAoE;YACpE,oDAAoD;YACpD,cAAc,CAAC,kBAAkB,EAAE,CAAC;YACpC,IAAI,cAAc,CAAC,UAAU,KAAK,SAAS,CAAC,OAAO,IAAI,cAAc,CAAC,UAAU,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;gBACtG,cAAc,CAAC,KAAK,EAAE,CAAC;YACzB,CAAC;QACH,CAAC;QAED,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACrB,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YAC5B,KAAK,IAAI,CAAC,mBAAmB,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACtB,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACzB,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,gBAAgB,CAAC,MAAiB;QACxC,IAAI,MAAM,KAAK,IAAI,CAAC,MAAM;YAAE,OAAO;QACnC,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;QACxH,8EAA8E;QAC9E,wEAAwE;QACxE,4EAA4E;QAC5E,mBAAmB;QACnB,KAAK,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;QACjB,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;YAC3B,IAAI,IAAI,CAAC,MAAM,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI;gBAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;gBAClE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QACD,IAAI,IAAI,CAAC,SAAS;YAAE,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;IAC/F,CAAC;IAEO,KAAK,CAAC,mBAAmB,CAAC,MAAiB,EAAE,IAAY;QAC/D,IAAI,MAAM,KAAK,IAAI,CAAC,MAAM;YAAE,OAAO;QACnC,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;IAEO,iBAAiB,CAAC,MAAiB;QACzC,IAAI,MAAM,KAAK,IAAI,CAAC,MAAM;YAAE,OAAO;QACnC,IAAI,IAAI,CAAC,SAAS;YAAE,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACxB,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC9B,kEAAkE;YAClE,wEAAwE;YACxE,wEAAwE;YACxE,0EAA0E;YAC1E,IAAI,CAAC,sBAAsB,IAAI,CAAC,CAAC;YACjC,IAAI,IAAI,CAAC,sBAAsB,IAAI,YAAY,CAAC,4BAA4B,EAAE,CAAC;gBAC7E,IAAI,CAAC,kBAAkB,CACrB,IAAI,KAAK,CACP,yCAAyC,IAAI,CAAC,sBAAsB,aAAa,CAClF,CACF,CAAC;gBACF,OAAO;YACT,CAAC;YACD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,gDAAgD,EAAE;gBACjE,OAAO,EAAE,IAAI,CAAC,sBAAsB;gBACpC,GAAG,EAAE,YAAY,CAAC,4BAA4B;aAC/C,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,IAAI,CAAC,SAAS;YAAE,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjD,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,kBAAkB,CAAC,CAAC;IACrF,CAAC;IAEO,iBAAiB,CAAC,MAAiB,EAAE,GAAU;QACrD,IAAI,MAAM,KAAK,IAAI,CAAC,MAAM;YAAE,OAAO;QACnC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,4BAA4B,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACzE,CAAC;IAEO,KAAK,CAAC,iBAAiB;QAC7B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,kBAAkB,EAAE,CAAC;QAC1D,IAAI,CAAC,IAAI,CAAC;YACR,IAAI,EAAE,OAAO;YACb,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU;YAChC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY;YACpC,QAAQ;SACT,CAAC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,IAAY;QACtC,IAAI,OAAuB,CAAC;QAC5B,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAmB,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YACnD,KAAK,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAC5C,OAAO;QACT,CAAC;QACD,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,QAAQ;YAAE,OAAO;QACpF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC9C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,IAAI,EAAE,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;YACvG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC;gBAAE,OAAO;YAClD,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC;gBAAE,OAAO;YAClD,IAAI,CAAC,IAAI,CAAC;gBACR,IAAI,EAAE,aAAa;gBACnB,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,SAAiB;QAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAChC,IAAI,CAAC;YACH,IAAI,OAAO,OAAO,CAAC,IAAI,EAAE,SAAS,KAAK,QAAQ,EAAE,CAAC;gBAChD,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;gBACvG,OAAO;YACT,CAAC;YACD,MAAM,UAAU,GAAG,OAAO,OAAO,CAAC,IAAI,EAAE,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;YACjH,IAAI,UAAU,EAAE,CAAC;gBACf,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,EAAE;gBAC9C,SAAS;gBACT,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAEO,mBAAmB;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,oBAAoB,CAAC;QAC3C,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,IAAI,CAAC,oBAAoB,GAAG,SAAS,CAAC;QACtC,QAAQ,CAAC,OAAO,EAAE,CAAC;IACrB,CAAC;IAEO,kBAAkB,CAAC,GAAU;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,oBAAoB,CAAC;QAC3C,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,IAAI,CAAC,oBAAoB,GAAG,SAAS,CAAC;QACtC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;IAEO,IAAI,CAAC,OAAgB;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,IAAI,CAAC,MAAM,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YAC/C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvB,OAAO;QACT,CAAC;QACD,+EAA+E;QAC/E,2EAA2E;QAC3E,6DAA6D;QAC7D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,YAAY,CAAC,UAAU,EAAE,CAAC;YACjD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;IACH,CAAC","sourcesContent":["import WebSocket from \"ws\";\nimport type { Logger } from \"./logger.js\";\nimport type { DaemonConfig } from \"./types.js\";\nimport type { Executor } from \"./executors/executor.js\";\nimport { createGatewayLookup, parseCloudGatewayUrl, validateGatewayNetworkTarget } from \"./cloud-config.js\";\n\ninterface GatewayMessage {\n type: string;\n requestId?: string;\n toolName?: string;\n args?: Record<string, unknown>;\n projectAlias?: string;\n}\n\nexport class CloudRuntime {\n private static readonly MAX_OUTBOX = 200;\n private static readonly RECONNECT_DELAY_MS = 5_000;\n // How many times to retry the initial connect before rejecting start(). A\n // transient handshake failure (gateway briefly unreachable, DNS hiccup) should\n // retry like the daemon's reconnect loop rather than kill the runtime; a\n // persistent failure (gateway down, session rejected) must eventually reject so\n // the CLI reports an error instead of hanging or exiting silently.\n private static readonly MAX_INITIAL_CONNECT_ATTEMPTS = 5;\n private static readonly INITIAL_CONNECT_HANDSHAKE_TIMEOUT_MS = 30_000;\n private socket: WebSocket | undefined;\n private heartbeat: ReturnType<typeof setInterval> | undefined;\n private reconnect: ReturnType<typeof setTimeout> | undefined;\n private readonly inFlight = new Map<string, GatewayMessage>();\n private outbox: string[] = [];\n private stopped = false;\n private firstConnectDeferred: PromiseWithResolvers<void> | undefined;\n private initialConnectAttempts = 0;\n\n constructor(\n private readonly cloud: NonNullable<DaemonConfig[\"cloud\"]>,\n private readonly executor: Executor,\n private readonly logger: Logger,\n ) {}\n\n start(): Promise<void> {\n this.stopped = false;\n this.initialConnectAttempts = 0;\n this.firstConnectDeferred = Promise.withResolvers<void>();\n this.connect();\n return this.firstConnectDeferred.promise;\n }\n\n stop(): void {\n this.stopped = true;\n this.rejectFirstConnect(new Error(\"cloud runtime stopped\"));\n if (this.reconnect) clearTimeout(this.reconnect);\n this.reconnect = undefined;\n if (this.heartbeat) clearInterval(this.heartbeat);\n this.heartbeat = undefined;\n this.inFlight.clear();\n this.outbox = [];\n const socket = this.socket;\n this.socket = undefined;\n socket?.close();\n }\n\n private connect(): void {\n if (this.reconnect) clearTimeout(this.reconnect);\n this.reconnect = undefined;\n if (!this.cloud.device_token || !this.cloud.runtime_id) {\n this.logger.warn(\"cloud runtime skipped — missing device token or runtime id\");\n this.rejectFirstConnect(new Error(\"cloud runtime missing device token or runtime id\"));\n return;\n }\n const gatewayUrl = parseCloudGatewayUrl(this.cloud.gateway_url);\n try {\n validateGatewayNetworkTarget(gatewayUrl);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n this.logger.warn(\"cloud runtime skipped unsafe gateway URL\", { error: message });\n this.rejectFirstConnect(new Error(`cloud runtime unsafe gateway URL: ${message}`));\n return;\n }\n const url = new URL(\"/runtime/connect\", gatewayUrl);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n const socket = new WebSocket(url, {\n headers: { Authorization: `Bearer ${this.cloud.device_token}` },\n lookup: createGatewayLookup(gatewayUrl),\n handshakeTimeout: CloudRuntime.INITIAL_CONNECT_HANDSHAKE_TIMEOUT_MS,\n });\n const previousSocket = this.socket;\n this.socket = socket;\n if (previousSocket) {\n // Detach the old socket's handlers before closing so its listeners don't\n // linger on a socket we've already replaced (handlers also guard on\n // identity, but this releases them eagerly for GC).\n previousSocket.removeAllListeners();\n if (previousSocket.readyState !== WebSocket.CLOSING && previousSocket.readyState !== WebSocket.CLOSED) {\n previousSocket.close();\n }\n }\n\n socket.on(\"open\", () => {\n this.handleSocketOpen(socket);\n });\n socket.on(\"message\", (data) => {\n void this.handleSocketMessage(socket, data.toString(\"utf8\"));\n });\n socket.on(\"close\", () => {\n this.handleSocketClose(socket);\n });\n socket.on(\"error\", (err) => {\n this.handleSocketError(socket, err);\n });\n }\n\n private handleSocketOpen(socket: WebSocket): void {\n if (socket !== this.socket) return;\n this.resolveFirstConnect();\n this.logger.info(\"cloud runtime connected\", { gateway_url: this.cloud.gateway_url, runtime_id: this.cloud.runtime_id });\n // Re-advertise projects (async: the hello is sent on a later microtask), then\n // drain any messages buffered while disconnected. tool_results route by\n // requestId on the authenticated connection, so drain order vs the hello is\n // not significant.\n void this.advertiseProjects();\n const pending = this.outbox;\n this.outbox = [];\n for (const text of pending) {\n if (this.socket?.readyState === WebSocket.OPEN) this.socket.send(text);\n else this.outbox.push(text);\n }\n if (this.heartbeat) clearInterval(this.heartbeat);\n this.heartbeat = setInterval(() => this.send({ type: \"heartbeat\", at: Date.now() }), 30_000);\n }\n\n private async handleSocketMessage(socket: WebSocket, text: string): Promise<void> {\n if (socket !== this.socket) return;\n await this.handleMessage(text);\n }\n\n private handleSocketClose(socket: WebSocket): void {\n if (socket !== this.socket) return;\n if (this.heartbeat) clearInterval(this.heartbeat);\n this.heartbeat = undefined;\n this.socket = undefined;\n if (this.stopped) return;\n if (this.firstConnectDeferred) {\n // Still trying to establish the first connection: retry transient\n // handshake failures (like the daemon's reconnect loop) and only reject\n // start() once the bounded attempts are exhausted, so a brief blip does\n // not kill the runtime while a persistent outage still surfaces an error.\n this.initialConnectAttempts += 1;\n if (this.initialConnectAttempts >= CloudRuntime.MAX_INITIAL_CONNECT_ATTEMPTS) {\n this.rejectFirstConnect(\n new Error(\n `cloud runtime connection failed after ${this.initialConnectAttempts} attempt(s)`,\n ),\n );\n return;\n }\n this.logger.warn(\"cloud runtime initial connect failed; retrying\", {\n attempt: this.initialConnectAttempts,\n max: CloudRuntime.MAX_INITIAL_CONNECT_ATTEMPTS,\n });\n } else {\n this.logger.warn(\"cloud runtime disconnected; reconnecting\");\n }\n if (this.reconnect) clearTimeout(this.reconnect);\n this.reconnect = setTimeout(() => this.connect(), CloudRuntime.RECONNECT_DELAY_MS);\n }\n\n private handleSocketError(socket: WebSocket, err: Error): void {\n if (socket !== this.socket) return;\n this.logger.warn(\"cloud runtime socket error\", { error: err.message });\n }\n\n private async advertiseProjects(): Promise<void> {\n const projects = await this.executor.advertisedProjects();\n this.send({\n type: \"hello\",\n runtimeId: this.cloud.runtime_id,\n runtimeName: this.cloud.runtime_name,\n projects,\n });\n }\n\n private async handleMessage(text: string): Promise<void> {\n let message: GatewayMessage;\n try {\n message = JSON.parse(text) as GatewayMessage;\n } catch {\n return;\n }\n if (message.type === \"cancel\" && message.requestId) {\n void this.cancelInFlight(message.requestId);\n return;\n }\n if (message.type !== \"tool_call\" || !message.requestId || !message.toolName) return;\n this.inFlight.set(message.requestId, message);\n try {\n const result = await this.executor.execute(message.toolName, message.args ?? {}, message.projectAlias);\n if (!this.inFlight.has(message.requestId)) return;\n this.send({ type: \"tool_result\", requestId: message.requestId, result });\n } catch (err) {\n if (!this.inFlight.has(message.requestId)) return;\n this.send({\n type: \"tool_result\",\n requestId: message.requestId,\n error: err instanceof Error ? err.message : String(err),\n });\n } finally {\n this.inFlight.delete(message.requestId);\n }\n }\n\n private async cancelInFlight(requestId: string): Promise<void> {\n const pending = this.inFlight.get(requestId);\n if (!pending) return;\n this.inFlight.delete(requestId);\n try {\n if (typeof pending.args?.sessionId === \"string\") {\n await this.executor.execute(\"gsd_cancel\", { sessionId: pending.args.sessionId }, pending.projectAlias);\n return;\n }\n const projectDir = typeof pending.args?.projectDir === \"string\" ? pending.args.projectDir : pending.projectAlias;\n if (projectDir) {\n await this.executor.execute(\"gsd_cancel\", { projectDir });\n }\n } catch (err) {\n this.logger.warn(\"cloud runtime cancel failed\", {\n requestId,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n }\n\n private resolveFirstConnect(): void {\n const deferred = this.firstConnectDeferred;\n if (!deferred) return;\n this.firstConnectDeferred = undefined;\n deferred.resolve();\n }\n\n private rejectFirstConnect(err: Error): void {\n const deferred = this.firstConnectDeferred;\n if (!deferred) return;\n this.firstConnectDeferred = undefined;\n deferred.reject(err);\n }\n\n private send(message: unknown): void {\n const text = JSON.stringify(message);\n if (this.socket?.readyState === WebSocket.OPEN) {\n this.socket.send(text);\n return;\n }\n // Buffer while disconnected; flushed on reconnect in handleSocketOpen. Bounded\n // so a long outage cannot grow memory without limit — a stale heartbeat is\n // worth less than a fresh tool_result, so drop oldest first.\n this.outbox.push(text);\n if (this.outbox.length > CloudRuntime.MAX_OUTBOX) {\n this.outbox.shift();\n }\n }\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengsd/gsd-cloud",
|
|
3
|
-
"version": "1.9.0-dev.
|
|
3
|
+
"version": "1.9.0-dev.e10b60d4",
|
|
4
4
|
"description": "Standalone CLI agent that connects a local GSD runtime to GSD Cloud (cloud.opengsd.net)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "node ../../scripts/clean-package-dist.cjs && tsc -p tsconfig.json --incremental false",
|
|
20
|
-
"test": "pnpm run build && node --test dist/inject-gateway.test.js dist/cloud-gateway-lookup.test.js dist/cloud-config.test.js dist/device-flow.test.js dist/cloud-runtime.test.js dist/
|
|
20
|
+
"test": "pnpm run build && node --test dist/inject-gateway.test.js dist/cloud-gateway-lookup.test.js dist/cloud-config.test.js dist/device-flow.test.js dist/cloud-runtime.test.js dist/executors/mcp-stdio-client.test.js dist/executors/gsd-pi-executor.test.js"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"ws": "^8.21.0",
|
|
@@ -34,9 +34,6 @@ export interface RunMcpServerCliOptions {
|
|
|
34
34
|
clearInterval?: typeof clearInterval;
|
|
35
35
|
isOrphaned?: () => boolean;
|
|
36
36
|
cleanupStepTimeoutMs?: number;
|
|
37
|
-
stdinIdleTimeoutMs?: number;
|
|
38
|
-
stdinIdleCheckIntervalMs?: number;
|
|
39
|
-
orphanParentLossCheckIntervalMs?: number;
|
|
40
37
|
}
|
|
41
38
|
export declare function runMcpServerCli(options?: RunMcpServerCliOptions): Promise<void>;
|
|
42
39
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-runner.d.ts","sourceRoot":"","sources":["../src/cli-runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"cli-runner.d.ts","sourceRoot":"","sources":["../src/cli-runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAiCtD,UAAU,kBAAkB;IAC1B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,UAAU,aAAa;IACrB,OAAO,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,UAAU,yBAAyB;IACjC,KAAI,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;CACnD;AAED,MAAM,WAAW,sBAAsB;IACrC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;IAC/B,2BAA2B,CAAC,EAAE,MAAM,IAAI,CAAC;IACzC,mBAAmB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAAC;IAC7D,4BAA4B,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,qBAAqB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,oBAAoB,CAAC,EAAE,MAAM,kBAAkB,CAAC;IAChD,eAAe,CAAC,EAAE,CAAC,cAAc,EAAE,kBAAkB,KAAK,OAAO,CAAC;QAAE,MAAM,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC;IAC7F,0BAA0B,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,oBAAoB,EAAE,yBAAyB,CAAA;KAAE,CAAC,CAAC;IAChG,uBAAuB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IAC3D,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IAClE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,WAAW,CAAC;IACjC,aAAa,CAAC,EAAE,OAAO,aAAa,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAkBD,wBAAsB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CA4IzF"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Worker } from 'node:worker_threads';
|
|
2
1
|
import { SessionManager } from './session-manager.js';
|
|
3
2
|
import { createMcpServer } from './server.js';
|
|
4
3
|
import { loadStoredCredentialEnvKeys } from './tool-credentials.js';
|
|
@@ -11,56 +10,17 @@ const STDIN_IDLE_TIMEOUT_MS = 5 * 60 * 1000;
|
|
|
11
10
|
const STDIN_IDLE_CHECK_INTERVAL_MS = 60 * 1000;
|
|
12
11
|
const CLEANUP_STEP_TIMEOUT_MS = 2 * 1000;
|
|
13
12
|
/**
|
|
14
|
-
* Cadence for the
|
|
13
|
+
* Cadence for the ref-held parent-liveness monitor.
|
|
15
14
|
*
|
|
16
|
-
* Separate from the
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
15
|
+
* Separate from the `.unref()`'d idle watchdog: a process already pegged at
|
|
16
|
+
* ~100% CPU starves its event loop, so the watchdog timer may never fire and a
|
|
17
|
+
* spinning orphan lingers until the next spawn sweeps it. This monitor is
|
|
18
|
+
* ref-held (NOT `.unref()`'d) so the libuv loop keeps scheduling it under load,
|
|
19
|
+
* and it exits the process itself the moment the parent is gone — independent
|
|
20
|
+
* of the external PID-registry sweep that only runs on the next launch. See
|
|
21
|
+
* #783.
|
|
20
22
|
*/
|
|
21
23
|
const ORPHAN_PARENT_LOSS_CHECK_INTERVAL_MS = 10 * 1000;
|
|
22
|
-
const ORPHAN_MONITOR_WORKER_SOURCE = `
|
|
23
|
-
import { writeSync } from 'node:fs';
|
|
24
|
-
import { parentPort, workerData } from 'node:worker_threads';
|
|
25
|
-
|
|
26
|
-
const lastActivityMs = new BigInt64Array(workerData.lastActivityMsBuffer);
|
|
27
|
-
let stopped = false;
|
|
28
|
-
|
|
29
|
-
function parentGone() {
|
|
30
|
-
if (process.ppid !== workerData.initialParentPid) return true;
|
|
31
|
-
try {
|
|
32
|
-
process.kill(workerData.initialParentPid, 0);
|
|
33
|
-
return false;
|
|
34
|
-
} catch (err) {
|
|
35
|
-
return err && err.code === 'ESRCH';
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function check() {
|
|
40
|
-
if (stopped) return;
|
|
41
|
-
const last = Number(Atomics.load(lastActivityMs, 0));
|
|
42
|
-
if (Date.now() - last <= workerData.idleTimeoutMs) return;
|
|
43
|
-
if (!parentGone()) return;
|
|
44
|
-
try {
|
|
45
|
-
writeSync(2, '[gsd-mcp-server] Parent process is gone and stdin is idle; hard-killing orphaned server\\n');
|
|
46
|
-
} catch {}
|
|
47
|
-
try {
|
|
48
|
-
process.kill(workerData.targetPid, 'SIGKILL');
|
|
49
|
-
} catch {
|
|
50
|
-
process.exit(0);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const timer = setInterval(check, workerData.checkIntervalMs);
|
|
55
|
-
parentPort?.on('message', (message) => {
|
|
56
|
-
if (message && message.type === 'stop') {
|
|
57
|
-
stopped = true;
|
|
58
|
-
clearInterval(timer);
|
|
59
|
-
process.exit(0);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
check();
|
|
63
|
-
`;
|
|
64
24
|
function createDefaultIsOrphaned(initialParentPid) {
|
|
65
25
|
return () => {
|
|
66
26
|
if (process.ppid !== initialParentPid)
|
|
@@ -74,43 +34,6 @@ function createDefaultIsOrphaned(initialParentPid) {
|
|
|
74
34
|
}
|
|
75
35
|
};
|
|
76
36
|
}
|
|
77
|
-
function startWorkerOrphanMonitor(options) {
|
|
78
|
-
const worker = new Worker(ORPHAN_MONITOR_WORKER_SOURCE, {
|
|
79
|
-
eval: true,
|
|
80
|
-
workerData: {
|
|
81
|
-
initialParentPid: options.initialParentPid,
|
|
82
|
-
targetPid: options.targetPid,
|
|
83
|
-
lastActivityMsBuffer: options.lastActivityMsBuffer,
|
|
84
|
-
idleTimeoutMs: options.idleTimeoutMs,
|
|
85
|
-
checkIntervalMs: options.checkIntervalMs,
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
worker.unref();
|
|
89
|
-
worker.on('error', (err) => {
|
|
90
|
-
options.stderr.write(`[gsd-mcp-server] Orphan monitor failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
91
|
-
});
|
|
92
|
-
return {
|
|
93
|
-
stop() {
|
|
94
|
-
worker.postMessage({ type: 'stop' });
|
|
95
|
-
void worker.terminate();
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
function startMainThreadOrphanMonitor(options) {
|
|
100
|
-
const interval = options.startInterval(() => {
|
|
101
|
-
if (!options.isOrphaned())
|
|
102
|
-
return;
|
|
103
|
-
if (options.idleMs() <= options.idleTimeoutMs)
|
|
104
|
-
return;
|
|
105
|
-
options.stderr.write(`[gsd-mcp-server] Parent process is gone; shutting down to avoid orphan spin\n`);
|
|
106
|
-
options.cleanup();
|
|
107
|
-
}, options.checkIntervalMs);
|
|
108
|
-
return {
|
|
109
|
-
stop() {
|
|
110
|
-
options.stopInterval(interval);
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
37
|
async function importDefaultStdioServerTransport() {
|
|
115
38
|
return import(`${MCP_PKG}/server/stdio.js`);
|
|
116
39
|
}
|
|
@@ -125,12 +48,8 @@ export async function runMcpServerCli(options = {}) {
|
|
|
125
48
|
const now = options.now ?? (() => Date.now());
|
|
126
49
|
const startInterval = options.setInterval ?? setInterval;
|
|
127
50
|
const stopInterval = options.clearInterval ?? clearInterval;
|
|
128
|
-
const
|
|
129
|
-
const isOrphaned = options.isOrphaned ?? createDefaultIsOrphaned(initialParentPid);
|
|
51
|
+
const isOrphaned = options.isOrphaned ?? createDefaultIsOrphaned(process.ppid);
|
|
130
52
|
const cleanupStepTimeoutMs = options.cleanupStepTimeoutMs ?? CLEANUP_STEP_TIMEOUT_MS;
|
|
131
|
-
const stdinIdleTimeoutMs = options.stdinIdleTimeoutMs ?? STDIN_IDLE_TIMEOUT_MS;
|
|
132
|
-
const stdinIdleCheckIntervalMs = options.stdinIdleCheckIntervalMs ?? STDIN_IDLE_CHECK_INTERVAL_MS;
|
|
133
|
-
const orphanParentLossCheckIntervalMs = options.orphanParentLossCheckIntervalMs ?? ORPHAN_PARENT_LOSS_CHECK_INTERVAL_MS;
|
|
134
53
|
const loadEnv = options.loadStoredCredentialEnvKeys ?? loadStoredCredentialEnvKeys;
|
|
135
54
|
const registerInstance = options.registerMcpInstance ?? registerMcpInstance;
|
|
136
55
|
const sweepOrphans = options.sweepProjectOrphanMcpServers ?? sweepProjectOrphanMcpServers;
|
|
@@ -146,7 +65,6 @@ export async function runMcpServerCli(options = {}) {
|
|
|
146
65
|
let cleaningUp = false;
|
|
147
66
|
let idleWatchdog;
|
|
148
67
|
let orphanMonitor;
|
|
149
|
-
const lastActivityMs = new BigInt64Array(new SharedArrayBuffer(8));
|
|
150
68
|
let trackedStdin;
|
|
151
69
|
let sessionManager;
|
|
152
70
|
let server;
|
|
@@ -174,7 +92,8 @@ export async function runMcpServerCli(options = {}) {
|
|
|
174
92
|
async function stopRuntime() {
|
|
175
93
|
if (idleWatchdog)
|
|
176
94
|
stopInterval(idleWatchdog);
|
|
177
|
-
orphanMonitor
|
|
95
|
+
if (orphanMonitor)
|
|
96
|
+
stopInterval(orphanMonitor);
|
|
178
97
|
trackedStdin?.close();
|
|
179
98
|
if (registered)
|
|
180
99
|
unregisterInstance(projectDir);
|
|
@@ -205,38 +124,33 @@ export async function runMcpServerCli(options = {}) {
|
|
|
205
124
|
sessionManager = createSessionManager();
|
|
206
125
|
({ server } = await createServer(sessionManager));
|
|
207
126
|
const { StdioServerTransport } = await importTransport();
|
|
208
|
-
trackedStdin = createActivityTrackingInput(stdin,
|
|
209
|
-
const current = now();
|
|
210
|
-
Atomics.store(lastActivityMs, 0, BigInt(Math.trunc(current)));
|
|
211
|
-
return current;
|
|
212
|
-
});
|
|
127
|
+
trackedStdin = createActivityTrackingInput(stdin, now);
|
|
213
128
|
const transport = new StdioServerTransport(trackedStdin.input, stdout);
|
|
214
129
|
idleWatchdog = startInterval(() => {
|
|
215
|
-
if (trackedStdin && now() - trackedStdin.lastActivityAt() >
|
|
216
|
-
stderr.write(`[gsd-mcp-server] Idle stdin watchdog: no activity for ${
|
|
130
|
+
if (trackedStdin && now() - trackedStdin.lastActivityAt() > STDIN_IDLE_TIMEOUT_MS && isOrphaned()) {
|
|
131
|
+
stderr.write(`[gsd-mcp-server] Idle stdin watchdog: no activity for ${STDIN_IDLE_TIMEOUT_MS / 1000}s and parent process is gone, shutting down\n`);
|
|
217
132
|
void cleanup();
|
|
218
133
|
}
|
|
219
|
-
},
|
|
134
|
+
}, STDIN_IDLE_CHECK_INTERVAL_MS);
|
|
220
135
|
idleWatchdog.unref();
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
});
|
|
136
|
+
// Ref-held parent-liveness monitor (#783). The idle watchdog above is
|
|
137
|
+
// `.unref()`'d, so a process pegged at ~100% CPU (e.g. a repeating throw
|
|
138
|
+
// against a dead stdio pipe) starves its event loop and the watchdog never
|
|
139
|
+
// fires — the orphan spins until the next launch sweeps it. This monitor is
|
|
140
|
+
// NOT unref'd: the loop keeps scheduling it under load, so parent loss is
|
|
141
|
+
// detected in seconds regardless of CPU state. It still requires the same
|
|
142
|
+
// idle gate as the watchdog so an active session whose parent briefly
|
|
143
|
+
// appears gone is not killed (#783 "stays alive when parent is gone but
|
|
144
|
+
// stdin is still active").
|
|
145
|
+
orphanMonitor = startInterval(() => {
|
|
146
|
+
if (!isOrphaned())
|
|
147
|
+
return;
|
|
148
|
+
const idleMs = trackedStdin ? now() - trackedStdin.lastActivityAt() : 0;
|
|
149
|
+
if (idleMs <= STDIN_IDLE_TIMEOUT_MS)
|
|
150
|
+
return;
|
|
151
|
+
stderr.write(`[gsd-mcp-server] Parent process is gone; shutting down to avoid orphan spin\n`);
|
|
152
|
+
void cleanup();
|
|
153
|
+
}, ORPHAN_PARENT_LOSS_CHECK_INTERVAL_MS);
|
|
240
154
|
// Fail closed (ADR-036): warm the executor / write-gate bridges BEFORE
|
|
241
155
|
// connecting the transport. If a bridge is broken we must not advertise the
|
|
242
156
|
// workflow tool surface — a rejection here propagates to the catch below so
|