@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.
Files changed (113) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/extensions/gsd/auto/loop.js +4 -24
  3. package/dist/resources/extensions/gsd/auto/run-unit.js +1 -19
  4. package/dist/resources/extensions/gsd/auto-start.js +9 -29
  5. package/dist/resources/extensions/gsd/auto-worktree-sync.js +3 -7
  6. package/dist/resources/extensions/gsd/auto.js +1 -12
  7. package/dist/resources/extensions/gsd/doctor-engine-checks.js +2 -21
  8. package/dist/resources/extensions/gsd/flat-phase-migration.js +0 -12
  9. package/dist/resources/extensions/gsd/gsd-db.js +31 -75
  10. package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -20
  11. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  12. package/dist/web/standalone/.next/BUILD_ID +1 -1
  13. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  14. package/dist/web/standalone/.next/build-manifest.json +2 -2
  15. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  16. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  17. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  25. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/index.html +1 -1
  33. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  40. package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
  41. package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
  42. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  43. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  44. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  45. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  46. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  47. package/package.json +1 -1
  48. package/packages/cloud-mcp-gateway/package.json +2 -2
  49. package/packages/contracts/package.json +1 -1
  50. package/packages/daemon/package.json +4 -4
  51. package/packages/gsd-agent-core/package.json +5 -5
  52. package/packages/gsd-agent-modes/package.json +7 -7
  53. package/packages/gsd-cloud/README.md +3 -9
  54. package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
  55. package/packages/gsd-cloud/dist/cli.js +15 -121
  56. package/packages/gsd-cloud/dist/cli.js.map +1 -1
  57. package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
  58. package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
  59. package/packages/gsd-cloud/dist/cloud-config.js +1 -7
  60. package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
  61. package/packages/gsd-cloud/dist/cloud-runtime.d.ts +3 -1
  62. package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
  63. package/packages/gsd-cloud/dist/cloud-runtime.js +7 -9
  64. package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
  65. package/packages/gsd-cloud/package.json +2 -2
  66. package/packages/mcp-server/dist/cli-runner.d.ts +0 -3
  67. package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
  68. package/packages/mcp-server/dist/cli-runner.js +33 -119
  69. package/packages/mcp-server/dist/cli-runner.js.map +1 -1
  70. package/packages/mcp-server/package.json +4 -4
  71. package/packages/native/package.json +1 -1
  72. package/packages/pi-agent-core/package.json +1 -1
  73. package/packages/pi-ai/dist/models.generated.d.ts +0 -17
  74. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  75. package/packages/pi-ai/dist/models.generated.js +0 -17
  76. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  77. package/packages/pi-ai/package.json +1 -1
  78. package/packages/pi-coding-agent/package.json +7 -7
  79. package/packages/pi-tui/package.json +2 -2
  80. package/packages/rpc-client/package.json +2 -2
  81. package/pkg/package.json +1 -1
  82. package/src/resources/extensions/gsd/auto/loop.ts +4 -48
  83. package/src/resources/extensions/gsd/auto/run-unit.ts +1 -20
  84. package/src/resources/extensions/gsd/auto-start.ts +8 -31
  85. package/src/resources/extensions/gsd/auto-worktree-sync.ts +3 -10
  86. package/src/resources/extensions/gsd/auto.ts +1 -13
  87. package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -27
  88. package/src/resources/extensions/gsd/flat-phase-migration.ts +0 -14
  89. package/src/resources/extensions/gsd/gsd-db.ts +33 -85
  90. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +0 -156
  91. package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +1 -21
  92. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +1 -74
  93. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +0 -83
  94. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +9 -8
  95. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +0 -32
  96. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +0 -13
  97. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
  98. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +0 -60
  99. package/src/resources/extensions/gsd/worktree-state-projection.ts +24 -21
  100. package/packages/gsd-cloud/dist/background-cli.test.d.ts +0 -2
  101. package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +0 -1
  102. package/packages/gsd-cloud/dist/background-cli.test.js +0 -248
  103. package/packages/gsd-cloud/dist/background-cli.test.js.map +0 -1
  104. package/packages/gsd-cloud/dist/runtime-process.d.ts +0 -27
  105. package/packages/gsd-cloud/dist/runtime-process.d.ts.map +0 -1
  106. package/packages/gsd-cloud/dist/runtime-process.js +0 -272
  107. package/packages/gsd-cloud/dist/runtime-process.js.map +0 -1
  108. package/packages/gsd-cloud/dist/runtime-process.test.d.ts +0 -2
  109. package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +0 -1
  110. package/packages/gsd-cloud/dist/runtime-process.test.js +0 -97
  111. package/packages/gsd-cloud/dist/runtime-process.test.js.map +0 -1
  112. /package/dist/web/standalone/.next/static/{DXD9fZPnqtjZSlT5xdC3v → V9MAVyYVPboQUB9KTTAMi}/_buildManifest.js +0 -0
  113. /package/dist/web/standalone/.next/static/{DXD9fZPnqtjZSlT5xdC3v → V9MAVyYVPboQUB9KTTAMi}/_ssgManifest.js +0 -0
@@ -1,248 +0,0 @@
1
- // Project/App: Open GSD
2
- // File Purpose: Acceptance coverage for the detached gsd-cloud runtime lifecycle.
3
- import { spawn } from "node:child_process";
4
- import { mkdtempSync, mkdirSync, realpathSync, rmSync, symlinkSync } from "node:fs";
5
- import { createServer } from "node:http";
6
- import { tmpdir } from "node:os";
7
- import { dirname, join, resolve } from "node:path";
8
- import { fileURLToPath } from "node:url";
9
- import { test } from "node:test";
10
- import assert from "node:assert/strict";
11
- import { WebSocketServer } from "ws";
12
- import { saveCloudConfig } from "./cloud-config.js";
13
- const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
14
- const cliPath = join(packageRoot, "bin", "gsd-cloud.js");
15
- function runCli(args, cwd, timeoutMs = 12_000, extraEnv = {}) {
16
- return new Promise((resolveResult, reject) => {
17
- const child = spawn(process.execPath, [cliPath, ...args], {
18
- cwd,
19
- env: { ...process.env, GSD_CLOUD_PROJECTS: undefined, ...extraEnv },
20
- stdio: ["ignore", "pipe", "pipe"],
21
- });
22
- let stdout = "";
23
- let stderr = "";
24
- child.stdout.setEncoding("utf8");
25
- child.stderr.setEncoding("utf8");
26
- child.stdout.on("data", (chunk) => { stdout += chunk; });
27
- child.stderr.on("data", (chunk) => { stderr += chunk; });
28
- const timeout = setTimeout(() => {
29
- child.kill("SIGTERM");
30
- reject(new Error(`gsd-cloud did not return within ${timeoutMs}ms`));
31
- }, timeoutMs);
32
- child.once("error", (error) => {
33
- clearTimeout(timeout);
34
- reject(error);
35
- });
36
- child.once("exit", (code) => {
37
- clearTimeout(timeout);
38
- resolveResult({ code, stdout, stderr });
39
- });
40
- });
41
- }
42
- // Spawn a long-lived CLI process (e.g. `--foreground`) without waiting for exit,
43
- // so the test can inspect its state and then stop it.
44
- function spawnForegroundCli(args, cwd) {
45
- const child = spawn(process.execPath, [cliPath, ...args], {
46
- cwd,
47
- env: { ...process.env, GSD_CLOUD_PROJECTS: undefined },
48
- stdio: ["ignore", "pipe", "pipe"],
49
- });
50
- const exited = new Promise((resolveExit) => {
51
- child.once("exit", (code) => resolveExit(code));
52
- });
53
- return { child, exited };
54
- }
55
- async function createTestGateway(deviceFlow = false) {
56
- let baseUrl = "";
57
- const server = createServer((req, res) => {
58
- if (!deviceFlow) {
59
- res.writeHead(404).end();
60
- return;
61
- }
62
- res.setHeader("content-type", "application/json");
63
- if (req.url === "/api/device/code") {
64
- res.end(JSON.stringify({
65
- userCode: "TEST-CODE",
66
- deviceCode: "test-device",
67
- verificationUriComplete: `${baseUrl}/approve-device?code=TEST-CODE`,
68
- expiresIn: 30,
69
- }));
70
- return;
71
- }
72
- if (req.url === "/api/device/token") {
73
- res.end(JSON.stringify({
74
- status: "approved",
75
- token: "test",
76
- runtimeId: "fixture-runtime",
77
- gateway_url: baseUrl,
78
- }));
79
- return;
80
- }
81
- res.writeHead(404).end();
82
- });
83
- const wss = new WebSocketServer({ server });
84
- await new Promise((resolveListen, reject) => {
85
- server.once("error", reject);
86
- server.listen(0, "127.0.0.1", resolveListen);
87
- });
88
- const address = server.address();
89
- assert.ok(address && typeof address === "object");
90
- baseUrl = `http://127.0.0.1:${address.port}`;
91
- let helloTimeout;
92
- const hello = new Promise((resolveHello, reject) => {
93
- helloTimeout = setTimeout(() => reject(new Error("background runtime did not advertise its project")), 8_000);
94
- wss.once("connection", (socket) => {
95
- socket.once("message", (data) => {
96
- clearTimeout(helloTimeout);
97
- resolveHello(JSON.parse(data.toString("utf8")));
98
- });
99
- });
100
- });
101
- return {
102
- baseUrl,
103
- hello,
104
- close: async () => {
105
- clearTimeout(helloTimeout);
106
- for (const client of wss.clients)
107
- client.terminate();
108
- await new Promise((resolveClose) => wss.close(() => server.close(() => resolveClose())));
109
- },
110
- };
111
- }
112
- test("connect returns while a background runtime advertises the selected project", async () => {
113
- const root = mkdtempSync(join(tmpdir(), "gsd-cloud-background-"));
114
- const projectDir = join(root, "project");
115
- const expectedProjectDir = join(realpathSync(root), "project");
116
- const configPath = join(root, "daemon.yaml");
117
- mkdirSync(join(projectDir, ".gsd"), { recursive: true });
118
- const gateway = await createTestGateway();
119
- saveCloudConfig(configPath, {
120
- gateway_url: gateway.baseUrl,
121
- device_token: "test",
122
- runtime_id: "fixture-runtime",
123
- enabled: true,
124
- });
125
- try {
126
- const connect = await runCli(["connect", "--config", configPath], projectDir);
127
- assert.equal(connect.code, 0, connect.stderr);
128
- assert.match(connect.stdout, /connected in the background/i);
129
- const message = await gateway.hello;
130
- assert.equal(message.type, "hello");
131
- const projects = message.projects;
132
- assert.equal(projects.length, 1);
133
- assert.equal(projects[0]?.path, expectedProjectDir);
134
- const status = await runCli(["status", "--config", configPath], projectDir);
135
- assert.equal(status.code, 0, status.stderr);
136
- const statusBody = JSON.parse(status.stdout);
137
- assert.equal(statusBody.background?.running, true);
138
- assert.equal(typeof statusBody.background?.pid, "number");
139
- assert.deepEqual(statusBody.projects, [expectedProjectDir]);
140
- }
141
- finally {
142
- await runCli(["disconnect", "--config", configPath], projectDir).catch(() => undefined);
143
- await gateway.close();
144
- rmSync(root, { recursive: true, force: true });
145
- }
146
- });
147
- test("login returns after approval and keeps the selected project connected", async () => {
148
- const root = mkdtempSync(join(tmpdir(), "gsd-cloud-login-background-"));
149
- const projectDir = join(root, "project");
150
- const expectedProjectDir = join(realpathSync(root), "project");
151
- const configPath = join(root, "daemon.yaml");
152
- mkdirSync(join(projectDir, ".gsd"), { recursive: true });
153
- const gateway = await createTestGateway(true);
154
- try {
155
- const login = await runCli([
156
- "login",
157
- "--gateway", gateway.baseUrl,
158
- "--config", configPath,
159
- ], projectDir, 12_000);
160
- assert.equal(login.code, 0, login.stderr);
161
- assert.match(login.stdout, /Authorization approved!/);
162
- assert.match(login.stdout, /connected in the background/i);
163
- const message = await gateway.hello;
164
- const projects = message.projects;
165
- assert.deepEqual(projects.map((project) => project.path), [expectedProjectDir]);
166
- const disconnect = await runCli(["disconnect", "--config", configPath], projectDir);
167
- assert.equal(disconnect.code, 0, disconnect.stderr);
168
- const status = await runCli(["status", "--config", configPath], projectDir);
169
- const statusBody = JSON.parse(status.stdout);
170
- assert.equal(statusBody.configured, false);
171
- assert.equal(statusBody.background?.running, false);
172
- }
173
- finally {
174
- await runCli(["disconnect", "--config", configPath], projectDir).catch(() => undefined);
175
- await gateway.close();
176
- rmSync(root, { recursive: true, force: true });
177
- }
178
- });
179
- test("advertised project paths are canonicalized through symlinks", async () => {
180
- const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-cloud-symlink-")));
181
- const realProject = join(root, "real-project");
182
- mkdirSync(join(realProject, ".gsd"), { recursive: true });
183
- const linkProject = join(root, "link-project");
184
- symlinkSync(realProject, linkProject);
185
- const configPath = join(root, "daemon.yaml");
186
- const gateway = await createTestGateway();
187
- saveCloudConfig(configPath, {
188
- gateway_url: gateway.baseUrl,
189
- device_token: "test",
190
- runtime_id: "fixture-runtime",
191
- enabled: true,
192
- });
193
- try {
194
- // Point the runtime at the symlink; the advertised path must be the real dir.
195
- const connect = await runCli(["connect", "--config", configPath], realProject, 5_000, { GSD_CLOUD_PROJECTS: linkProject });
196
- assert.equal(connect.code, 0, connect.stderr);
197
- const message = await gateway.hello;
198
- const projects = message.projects;
199
- assert.equal(projects.length, 1);
200
- assert.equal(projects[0]?.path, realProject);
201
- const status = await runCli(["status", "--config", configPath], realProject);
202
- const statusBody = JSON.parse(status.stdout);
203
- assert.deepEqual(statusBody.projects, [realProject]);
204
- }
205
- finally {
206
- await runCli(["disconnect", "--config", configPath], realProject).catch(() => undefined);
207
- await gateway.close();
208
- rmSync(root, { recursive: true, force: true });
209
- }
210
- });
211
- test("a foreground runtime registers its PID so disconnect can stop it", async () => {
212
- const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-cloud-foreground-")));
213
- const projectDir = join(root, "project");
214
- const configPath = join(root, "daemon.yaml");
215
- mkdirSync(join(projectDir, ".gsd"), { recursive: true });
216
- const gateway = await createTestGateway();
217
- saveCloudConfig(configPath, {
218
- gateway_url: gateway.baseUrl,
219
- device_token: "test",
220
- runtime_id: "fixture-runtime",
221
- enabled: true,
222
- });
223
- const foreground = spawnForegroundCli(["connect", "--foreground", "--config", configPath], projectDir);
224
- try {
225
- // Once the foreground runtime advertises its project it is fully connected.
226
- await gateway.hello;
227
- // The foreground session must record its own PID in the shared runtime state
228
- // so status can see it and a later disconnect can find and stop it.
229
- const status = await runCli(["status", "--config", configPath], projectDir);
230
- const statusBody = JSON.parse(status.stdout);
231
- assert.equal(statusBody.background?.running, true);
232
- assert.equal(statusBody.background?.pid, foreground.child.pid);
233
- const disconnect = await runCli(["disconnect", "--config", configPath], projectDir);
234
- assert.equal(disconnect.code, 0, disconnect.stderr);
235
- // disconnect must have terminated the foreground process.
236
- const exitCode = await foreground.exited;
237
- assert.equal(exitCode, 0);
238
- const afterStatus = await runCli(["status", "--config", configPath], projectDir);
239
- const afterBody = JSON.parse(afterStatus.stdout);
240
- assert.equal(afterBody.background?.running, false);
241
- }
242
- finally {
243
- foreground.child.kill("SIGKILL");
244
- await gateway.close();
245
- rmSync(root, { recursive: true, force: true });
246
- }
247
- });
248
- //# sourceMappingURL=background-cli.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"background-cli.test.js","sourceRoot":"","sources":["../src/background-cli.test.ts"],"names":[],"mappings":"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","sourcesContent":["// Project/App: Open GSD\n// File Purpose: Acceptance coverage for the detached gsd-cloud runtime lifecycle.\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { mkdtempSync, mkdirSync, realpathSync, rmSync, symlinkSync } from \"node:fs\";\nimport { createServer } from \"node:http\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { test } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { WebSocketServer } from \"ws\";\nimport { saveCloudConfig } from \"./cloud-config.js\";\n\nconst packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), \"..\");\nconst cliPath = join(packageRoot, \"bin\", \"gsd-cloud.js\");\n\ninterface CliResult {\n code: number | null;\n stdout: string;\n stderr: string;\n}\n\ninterface TestGateway {\n baseUrl: string;\n hello: Promise<Record<string, unknown>>;\n close: () => Promise<void>;\n}\n\nfunction runCli(\n args: string[],\n cwd: string,\n timeoutMs = 12_000,\n extraEnv: Record<string, string | undefined> = {},\n): Promise<CliResult> {\n return new Promise((resolveResult, reject) => {\n const child = spawn(process.execPath, [cliPath, ...args], {\n cwd,\n env: { ...process.env, GSD_CLOUD_PROJECTS: undefined, ...extraEnv },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n let stdout = \"\";\n let stderr = \"\";\n child.stdout.setEncoding(\"utf8\");\n child.stderr.setEncoding(\"utf8\");\n child.stdout.on(\"data\", (chunk: string) => { stdout += chunk; });\n child.stderr.on(\"data\", (chunk: string) => { stderr += chunk; });\n\n const timeout = setTimeout(() => {\n child.kill(\"SIGTERM\");\n reject(new Error(`gsd-cloud did not return within ${timeoutMs}ms`));\n }, timeoutMs);\n child.once(\"error\", (error) => {\n clearTimeout(timeout);\n reject(error);\n });\n child.once(\"exit\", (code) => {\n clearTimeout(timeout);\n resolveResult({ code, stdout, stderr });\n });\n });\n}\n\ninterface ForegroundCli {\n child: ChildProcess;\n exited: Promise<number | null>;\n}\n\n// Spawn a long-lived CLI process (e.g. `--foreground`) without waiting for exit,\n// so the test can inspect its state and then stop it.\nfunction spawnForegroundCli(args: string[], cwd: string): ForegroundCli {\n const child = spawn(process.execPath, [cliPath, ...args], {\n cwd,\n env: { ...process.env, GSD_CLOUD_PROJECTS: undefined },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n const exited = new Promise<number | null>((resolveExit) => {\n child.once(\"exit\", (code) => resolveExit(code));\n });\n return { child, exited };\n}\n\nasync function createTestGateway(deviceFlow = false): Promise<TestGateway> {\n let baseUrl = \"\";\n const server = createServer((req, res) => {\n if (!deviceFlow) {\n res.writeHead(404).end();\n return;\n }\n res.setHeader(\"content-type\", \"application/json\");\n if (req.url === \"/api/device/code\") {\n res.end(JSON.stringify({\n userCode: \"TEST-CODE\",\n deviceCode: \"test-device\",\n verificationUriComplete: `${baseUrl}/approve-device?code=TEST-CODE`,\n expiresIn: 30,\n }));\n return;\n }\n if (req.url === \"/api/device/token\") {\n res.end(JSON.stringify({\n status: \"approved\",\n token: \"test\",\n runtimeId: \"fixture-runtime\",\n gateway_url: baseUrl,\n }));\n return;\n }\n res.writeHead(404).end();\n });\n const wss = new WebSocketServer({ server });\n await new Promise<void>((resolveListen, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", resolveListen);\n });\n const address = server.address();\n assert.ok(address && typeof address === \"object\");\n baseUrl = `http://127.0.0.1:${address.port}`;\n\n let helloTimeout: ReturnType<typeof setTimeout>;\n const hello = new Promise<Record<string, unknown>>((resolveHello, reject) => {\n helloTimeout = setTimeout(() => reject(new Error(\"background runtime did not advertise its project\")), 8_000);\n wss.once(\"connection\", (socket) => {\n socket.once(\"message\", (data) => {\n clearTimeout(helloTimeout);\n resolveHello(JSON.parse(data.toString(\"utf8\")) as Record<string, unknown>);\n });\n });\n });\n\n return {\n baseUrl,\n hello,\n close: async () => {\n clearTimeout(helloTimeout);\n for (const client of wss.clients) client.terminate();\n await new Promise<void>((resolveClose) => wss.close(() => server.close(() => resolveClose())));\n },\n };\n}\n\ntest(\"connect returns while a background runtime advertises the selected project\", async () => {\n const root = mkdtempSync(join(tmpdir(), \"gsd-cloud-background-\"));\n const projectDir = join(root, \"project\");\n const expectedProjectDir = join(realpathSync(root), \"project\");\n const configPath = join(root, \"daemon.yaml\");\n mkdirSync(join(projectDir, \".gsd\"), { recursive: true });\n\n const gateway = await createTestGateway();\n\n saveCloudConfig(configPath, {\n gateway_url: gateway.baseUrl,\n device_token: \"test\",\n runtime_id: \"fixture-runtime\",\n enabled: true,\n });\n\n try {\n const connect = await runCli([\"connect\", \"--config\", configPath], projectDir);\n assert.equal(connect.code, 0, connect.stderr);\n assert.match(connect.stdout, /connected in the background/i);\n\n const message = await gateway.hello;\n assert.equal(message.type, \"hello\");\n const projects = message.projects as Array<{ path: string }>;\n assert.equal(projects.length, 1);\n assert.equal(projects[0]?.path, expectedProjectDir);\n\n const status = await runCli([\"status\", \"--config\", configPath], projectDir);\n assert.equal(status.code, 0, status.stderr);\n const statusBody = JSON.parse(status.stdout) as {\n background?: { running?: boolean; pid?: number | null };\n projects?: string[];\n };\n assert.equal(statusBody.background?.running, true);\n assert.equal(typeof statusBody.background?.pid, \"number\");\n assert.deepEqual(statusBody.projects, [expectedProjectDir]);\n } finally {\n await runCli([\"disconnect\", \"--config\", configPath], projectDir).catch(() => undefined);\n await gateway.close();\n rmSync(root, { recursive: true, force: true });\n }\n});\n\ntest(\"login returns after approval and keeps the selected project connected\", async () => {\n const root = mkdtempSync(join(tmpdir(), \"gsd-cloud-login-background-\"));\n const projectDir = join(root, \"project\");\n const expectedProjectDir = join(realpathSync(root), \"project\");\n const configPath = join(root, \"daemon.yaml\");\n mkdirSync(join(projectDir, \".gsd\"), { recursive: true });\n const gateway = await createTestGateway(true);\n\n try {\n const login = await runCli([\n \"login\",\n \"--gateway\", gateway.baseUrl,\n \"--config\", configPath,\n ], projectDir, 12_000);\n assert.equal(login.code, 0, login.stderr);\n assert.match(login.stdout, /Authorization approved!/);\n assert.match(login.stdout, /connected in the background/i);\n\n const message = await gateway.hello;\n const projects = message.projects as Array<{ path: string }>;\n assert.deepEqual(projects.map((project) => project.path), [expectedProjectDir]);\n\n const disconnect = await runCli([\"disconnect\", \"--config\", configPath], projectDir);\n assert.equal(disconnect.code, 0, disconnect.stderr);\n const status = await runCli([\"status\", \"--config\", configPath], projectDir);\n const statusBody = JSON.parse(status.stdout) as {\n configured?: boolean;\n background?: { running?: boolean };\n };\n assert.equal(statusBody.configured, false);\n assert.equal(statusBody.background?.running, false);\n } finally {\n await runCli([\"disconnect\", \"--config\", configPath], projectDir).catch(() => undefined);\n await gateway.close();\n rmSync(root, { recursive: true, force: true });\n }\n});\n\ntest(\"advertised project paths are canonicalized through symlinks\", async () => {\n const root = realpathSync(mkdtempSync(join(tmpdir(), \"gsd-cloud-symlink-\")));\n const realProject = join(root, \"real-project\");\n mkdirSync(join(realProject, \".gsd\"), { recursive: true });\n const linkProject = join(root, \"link-project\");\n symlinkSync(realProject, linkProject);\n const configPath = join(root, \"daemon.yaml\");\n const gateway = await createTestGateway();\n\n saveCloudConfig(configPath, {\n gateway_url: gateway.baseUrl,\n device_token: \"test\",\n runtime_id: \"fixture-runtime\",\n enabled: true,\n });\n\n try {\n // Point the runtime at the symlink; the advertised path must be the real dir.\n const connect = await runCli(\n [\"connect\", \"--config\", configPath],\n realProject,\n 5_000,\n { GSD_CLOUD_PROJECTS: linkProject },\n );\n assert.equal(connect.code, 0, connect.stderr);\n\n const message = await gateway.hello;\n const projects = message.projects as Array<{ path: string }>;\n assert.equal(projects.length, 1);\n assert.equal(projects[0]?.path, realProject);\n\n const status = await runCli([\"status\", \"--config\", configPath], realProject);\n const statusBody = JSON.parse(status.stdout) as { projects?: string[] };\n assert.deepEqual(statusBody.projects, [realProject]);\n } finally {\n await runCli([\"disconnect\", \"--config\", configPath], realProject).catch(() => undefined);\n await gateway.close();\n rmSync(root, { recursive: true, force: true });\n }\n});\n\ntest(\"a foreground runtime registers its PID so disconnect can stop it\", async () => {\n const root = realpathSync(mkdtempSync(join(tmpdir(), \"gsd-cloud-foreground-\")));\n const projectDir = join(root, \"project\");\n const configPath = join(root, \"daemon.yaml\");\n mkdirSync(join(projectDir, \".gsd\"), { recursive: true });\n const gateway = await createTestGateway();\n\n saveCloudConfig(configPath, {\n gateway_url: gateway.baseUrl,\n device_token: \"test\",\n runtime_id: \"fixture-runtime\",\n enabled: true,\n });\n\n const foreground = spawnForegroundCli([\"connect\", \"--foreground\", \"--config\", configPath], projectDir);\n try {\n // Once the foreground runtime advertises its project it is fully connected.\n await gateway.hello;\n\n // The foreground session must record its own PID in the shared runtime state\n // so status can see it and a later disconnect can find and stop it.\n const status = await runCli([\"status\", \"--config\", configPath], projectDir);\n const statusBody = JSON.parse(status.stdout) as {\n background?: { running?: boolean; pid?: number | null };\n };\n assert.equal(statusBody.background?.running, true);\n assert.equal(statusBody.background?.pid, foreground.child.pid);\n\n const disconnect = await runCli([\"disconnect\", \"--config\", configPath], projectDir);\n assert.equal(disconnect.code, 0, disconnect.stderr);\n\n // disconnect must have terminated the foreground process.\n const exitCode = await foreground.exited;\n assert.equal(exitCode, 0);\n\n const afterStatus = await runCli([\"status\", \"--config\", configPath], projectDir);\n const afterBody = JSON.parse(afterStatus.stdout) as { background?: { running?: boolean } };\n assert.equal(afterBody.background?.running, false);\n } finally {\n foreground.child.kill(\"SIGKILL\");\n await gateway.close();\n rmSync(root, { recursive: true, force: true });\n }\n});\n"]}
@@ -1,27 +0,0 @@
1
- export interface RuntimeProcessStatus {
2
- running: boolean;
3
- pid: number | null;
4
- projects: string[];
5
- log_file: string;
6
- }
7
- interface StartRuntimeOptions {
8
- binaryPath: string;
9
- configPath: string;
10
- projectDirs: string[];
11
- readyTimeoutMs?: number;
12
- verbose?: boolean;
13
- }
14
- export declare const BACKGROUND_RUNTIME_READY_TIMEOUT_MS: number;
15
- export declare function startBackgroundRuntime(opts: StartRuntimeOptions): Promise<RuntimeProcessStatus>;
16
- /**
17
- * Record a running runtime so a later launch can find and stop it. Used both by
18
- * the detached launcher (child PID) and by a `--foreground` session (its own
19
- * PID), so the two modes share one source of truth and never coexist on the
20
- * same device token.
21
- */
22
- export declare function writeRuntimeState(configPath: string, pid: number, projects: string[]): void;
23
- export declare function clearRuntimeState(configPath: string): void;
24
- export declare function stopBackgroundRuntime(configPath: string): Promise<boolean>;
25
- export declare function backgroundRuntimeStatus(configPath: string): RuntimeProcessStatus;
26
- export {};
27
- //# sourceMappingURL=runtime-process.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runtime-process.d.ts","sourceRoot":"","sources":["../src/runtime-process.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,mBAAmB;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAOD,eAAO,MAAM,mCAAmC,QACoB,CAAC;AAErE,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAoDrG;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAI3F;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAE1D;AAED,wBAAsB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMhF;AAED,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,oBAAoB,CAoBhF"}
@@ -1,272 +0,0 @@
1
- // Project/App: Open GSD
2
- // File Purpose: Detached cloud runtime process lifecycle and status persistence.
3
- import { spawn } from "node:child_process";
4
- import { chmodSync, closeSync, mkdirSync, openSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
5
- import { dirname, join } from "node:path";
6
- import { CLOUD_RUNTIME_INITIAL_CONNECT_WINDOW_MS } from "./cloud-runtime.js";
7
- const PROCESS_STARTUP_GRACE_MS = 5_000;
8
- const FORCED_STOP_TIMEOUT_MS = 5_000;
9
- const STOP_GRACE_PERIOD_MS = 5_000;
10
- const STOP_POLL_INTERVAL_MS = 50;
11
- export const BACKGROUND_RUNTIME_READY_TIMEOUT_MS = CLOUD_RUNTIME_INITIAL_CONNECT_WINDOW_MS + PROCESS_STARTUP_GRACE_MS;
12
- export async function startBackgroundRuntime(opts) {
13
- await acquireRuntimeStartLock(opts.configPath);
14
- try {
15
- await stopBackgroundRuntime(opts.configPath);
16
- const logFile = runtimeLogPath(opts.configPath);
17
- mkdirSync(dirname(runtimeStatePath(opts.configPath)), { recursive: true });
18
- const logFd = openSync(logFile, "a", 0o600);
19
- chmodSync(logFile, 0o600);
20
- const runArgs = [opts.binaryPath, "_run", "--config", opts.configPath];
21
- if (opts.verbose)
22
- runArgs.push("--verbose");
23
- let child;
24
- try {
25
- child = spawn(process.execPath, runArgs, {
26
- cwd: opts.projectDirs[0] ?? process.cwd(),
27
- detached: true,
28
- env: process.env,
29
- stdio: ["ignore", logFd, logFd, "ipc"],
30
- });
31
- }
32
- finally {
33
- closeSync(logFd);
34
- }
35
- if (child.pid == null) {
36
- child.kill();
37
- throw new Error(`could not start the background runtime; see ${logFile}`);
38
- }
39
- const pid = child.pid;
40
- writeRuntimeState(opts.configPath, pid, opts.projectDirs);
41
- try {
42
- await waitUntilReady(child, opts.readyTimeoutMs ?? BACKGROUND_RUNTIME_READY_TIMEOUT_MS);
43
- }
44
- catch (error) {
45
- if (child.connected)
46
- child.disconnect();
47
- await terminateProcess(pid);
48
- removeRuntimeState(opts.configPath);
49
- throw error;
50
- }
51
- if (child.connected)
52
- child.disconnect();
53
- child.unref();
54
- return {
55
- running: true,
56
- pid,
57
- projects: opts.projectDirs,
58
- log_file: logFile,
59
- };
60
- }
61
- finally {
62
- releaseRuntimeStartLock(opts.configPath);
63
- }
64
- }
65
- /**
66
- * Record a running runtime so a later launch can find and stop it. Used both by
67
- * the detached launcher (child PID) and by a `--foreground` session (its own
68
- * PID), so the two modes share one source of truth and never coexist on the
69
- * same device token.
70
- */
71
- export function writeRuntimeState(configPath, pid, projects) {
72
- const statePath = runtimeStatePath(configPath);
73
- mkdirSync(dirname(statePath), { recursive: true });
74
- writePrivateJson(statePath, { pid, projects });
75
- }
76
- export function clearRuntimeState(configPath) {
77
- removeRuntimeState(configPath);
78
- }
79
- export async function stopBackgroundRuntime(configPath) {
80
- const state = readRuntimeState(configPath);
81
- if (!state)
82
- return false;
83
- await terminateProcess(state.pid);
84
- removeRuntimeState(configPath);
85
- return true;
86
- }
87
- export function backgroundRuntimeStatus(configPath) {
88
- const state = readRuntimeState(configPath);
89
- if (!state) {
90
- return { running: false, pid: null, projects: [], log_file: runtimeLogPath(configPath) };
91
- }
92
- if (!processIsRunning(state.pid)) {
93
- removeRuntimeState(configPath);
94
- return {
95
- running: false,
96
- pid: null,
97
- projects: state.projects,
98
- log_file: runtimeLogPath(configPath),
99
- };
100
- }
101
- return {
102
- running: true,
103
- pid: state.pid,
104
- projects: state.projects,
105
- log_file: runtimeLogPath(configPath),
106
- };
107
- }
108
- function waitUntilReady(child, timeoutMs) {
109
- return new Promise((resolve, reject) => {
110
- let timeout;
111
- const cleanup = () => {
112
- clearTimeout(timeout);
113
- child.removeListener("error", onError);
114
- child.removeListener("exit", onExit);
115
- child.removeListener("message", onMessage);
116
- };
117
- const onError = (error) => {
118
- cleanup();
119
- reject(error);
120
- };
121
- const onExit = (code) => {
122
- cleanup();
123
- reject(new Error(`background runtime exited before connecting (exit ${code ?? "unknown"})`));
124
- };
125
- const onMessage = (message) => {
126
- if (!message || typeof message !== "object" || message.type !== "ready")
127
- return;
128
- cleanup();
129
- resolve();
130
- };
131
- timeout = setTimeout(() => {
132
- cleanup();
133
- reject(new Error(`background runtime did not connect within ${timeoutMs}ms`));
134
- }, timeoutMs);
135
- child.once("error", onError);
136
- child.once("exit", onExit);
137
- child.on("message", onMessage);
138
- });
139
- }
140
- function runtimeStatePath(configPath) {
141
- return join(dirname(configPath), "cloud-runtime.json");
142
- }
143
- function runtimeLogPath(configPath) {
144
- return join(dirname(configPath), "cloud-runtime.log");
145
- }
146
- function runtimeStartLockPath(configPath) {
147
- return join(dirname(configPath), "cloud-runtime.start.lock");
148
- }
149
- async function acquireRuntimeStartLock(configPath) {
150
- const lockPath = runtimeStartLockPath(configPath);
151
- mkdirSync(dirname(lockPath), { recursive: true });
152
- // Match worst-case `startBackgroundRuntime` hold time: stop prior runtime, wait for
153
- // ready, and tear down the child if ready fails, plus one poll interval.
154
- const deadline = Date.now()
155
- + BACKGROUND_RUNTIME_READY_TIMEOUT_MS
156
- + 2 * (STOP_GRACE_PERIOD_MS + FORCED_STOP_TIMEOUT_MS)
157
- + STOP_POLL_INTERVAL_MS;
158
- while (Date.now() < deadline) {
159
- try {
160
- const fd = openSync(lockPath, "wx");
161
- writeFileSync(fd, `${process.pid}\n`, "utf8");
162
- closeSync(fd);
163
- return;
164
- }
165
- catch (error) {
166
- if (error.code !== "EEXIST")
167
- throw error;
168
- try {
169
- const ownerPid = readRuntimeStartLockOwner(lockPath);
170
- const incompleteLockIsStale = ownerPid === null
171
- && Date.now() - statSync(lockPath).mtimeMs > 1_000;
172
- if ((ownerPid !== null && !processIsRunning(ownerPid)) || incompleteLockIsStale) {
173
- unlinkSync(lockPath);
174
- continue;
175
- }
176
- }
177
- catch {
178
- // Another process may have released the lock; retry.
179
- }
180
- await new Promise((resolve) => setTimeout(resolve, STOP_POLL_INTERVAL_MS));
181
- }
182
- }
183
- throw new Error("timed out waiting for the background runtime start lock");
184
- }
185
- function releaseRuntimeStartLock(configPath) {
186
- const lockPath = runtimeStartLockPath(configPath);
187
- if (readRuntimeStartLockOwner(lockPath) !== process.pid)
188
- return;
189
- try {
190
- unlinkSync(lockPath);
191
- }
192
- catch (error) {
193
- if (error.code !== "ENOENT")
194
- throw error;
195
- }
196
- }
197
- function readRuntimeStartLockOwner(lockPath) {
198
- try {
199
- const pid = Number.parseInt(readFileSync(lockPath, "utf8").trim(), 10);
200
- return Number.isInteger(pid) && pid > 0 ? pid : null;
201
- }
202
- catch (error) {
203
- if (error.code === "ENOENT")
204
- return null;
205
- throw error;
206
- }
207
- }
208
- function readRuntimeState(configPath) {
209
- try {
210
- const value = JSON.parse(readFileSync(runtimeStatePath(configPath), "utf8"));
211
- const pid = value.pid;
212
- if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || !Array.isArray(value.projects))
213
- return null;
214
- return {
215
- pid,
216
- projects: value.projects.filter((project) => typeof project === "string"),
217
- };
218
- }
219
- catch {
220
- return null;
221
- }
222
- }
223
- function processIsRunning(pid) {
224
- try {
225
- process.kill(pid, 0);
226
- return true;
227
- }
228
- catch (error) {
229
- return error.code === "EPERM";
230
- }
231
- }
232
- async function waitForProcessExit(pid, timeoutMs) {
233
- const deadline = Date.now() + timeoutMs;
234
- while (processIsRunning(pid)) {
235
- if (Date.now() >= deadline)
236
- return false;
237
- await new Promise((resolve) => setTimeout(resolve, STOP_POLL_INTERVAL_MS));
238
- }
239
- return true;
240
- }
241
- async function terminateProcess(pid) {
242
- signalProcess(pid, "SIGTERM");
243
- if (await waitForProcessExit(pid, STOP_GRACE_PERIOD_MS))
244
- return;
245
- signalProcess(pid, "SIGKILL");
246
- if (!await waitForProcessExit(pid, FORCED_STOP_TIMEOUT_MS)) {
247
- throw new Error(`background runtime PID ${pid} did not stop`);
248
- }
249
- }
250
- function signalProcess(pid, signal) {
251
- try {
252
- process.kill(pid, signal);
253
- }
254
- catch (error) {
255
- if (error.code !== "ESRCH")
256
- throw error;
257
- }
258
- }
259
- function removeRuntimeState(configPath) {
260
- try {
261
- unlinkSync(runtimeStatePath(configPath));
262
- }
263
- catch (error) {
264
- if (error.code !== "ENOENT")
265
- throw error;
266
- }
267
- }
268
- function writePrivateJson(path, value) {
269
- writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
270
- chmodSync(path, 0o600);
271
- }
272
- //# sourceMappingURL=runtime-process.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runtime-process.js","sourceRoot":"","sources":["../src/runtime-process.ts"],"names":[],"mappings":"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","sourcesContent":["// Project/App: Open GSD\n// File Purpose: Detached cloud runtime process lifecycle and status persistence.\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport {\n chmodSync,\n closeSync,\n mkdirSync,\n openSync,\n readFileSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { CLOUD_RUNTIME_INITIAL_CONNECT_WINDOW_MS } from \"./cloud-runtime.js\";\n\ninterface RuntimeProcessState {\n pid: number;\n projects: string[];\n}\n\nexport interface RuntimeProcessStatus {\n running: boolean;\n pid: number | null;\n projects: string[];\n log_file: string;\n}\n\ninterface StartRuntimeOptions {\n binaryPath: string;\n configPath: string;\n projectDirs: string[];\n readyTimeoutMs?: number;\n verbose?: boolean;\n}\n\nconst PROCESS_STARTUP_GRACE_MS = 5_000;\nconst FORCED_STOP_TIMEOUT_MS = 5_000;\nconst STOP_GRACE_PERIOD_MS = 5_000;\nconst STOP_POLL_INTERVAL_MS = 50;\n\nexport const BACKGROUND_RUNTIME_READY_TIMEOUT_MS =\n CLOUD_RUNTIME_INITIAL_CONNECT_WINDOW_MS + PROCESS_STARTUP_GRACE_MS;\n\nexport async function startBackgroundRuntime(opts: StartRuntimeOptions): Promise<RuntimeProcessStatus> {\n await acquireRuntimeStartLock(opts.configPath);\n try {\n await stopBackgroundRuntime(opts.configPath);\n\n const logFile = runtimeLogPath(opts.configPath);\n mkdirSync(dirname(runtimeStatePath(opts.configPath)), { recursive: true });\n const logFd = openSync(logFile, \"a\", 0o600);\n chmodSync(logFile, 0o600);\n const runArgs = [opts.binaryPath, \"_run\", \"--config\", opts.configPath];\n if (opts.verbose) runArgs.push(\"--verbose\");\n let child: ChildProcess;\n try {\n child = spawn(process.execPath, runArgs, {\n cwd: opts.projectDirs[0] ?? process.cwd(),\n detached: true,\n env: process.env,\n stdio: [\"ignore\", logFd, logFd, \"ipc\"],\n });\n } finally {\n closeSync(logFd);\n }\n\n if (child.pid == null) {\n child.kill();\n throw new Error(`could not start the background runtime; see ${logFile}`);\n }\n const pid = child.pid;\n\n writeRuntimeState(opts.configPath, pid, opts.projectDirs);\n\n try {\n await waitUntilReady(child, opts.readyTimeoutMs ?? BACKGROUND_RUNTIME_READY_TIMEOUT_MS);\n } catch (error) {\n if (child.connected) child.disconnect();\n await terminateProcess(pid);\n removeRuntimeState(opts.configPath);\n throw error;\n }\n\n if (child.connected) child.disconnect();\n child.unref();\n\n return {\n running: true,\n pid,\n projects: opts.projectDirs,\n log_file: logFile,\n };\n } finally {\n releaseRuntimeStartLock(opts.configPath);\n }\n}\n\n/**\n * Record a running runtime so a later launch can find and stop it. Used both by\n * the detached launcher (child PID) and by a `--foreground` session (its own\n * PID), so the two modes share one source of truth and never coexist on the\n * same device token.\n */\nexport function writeRuntimeState(configPath: string, pid: number, projects: string[]): void {\n const statePath = runtimeStatePath(configPath);\n mkdirSync(dirname(statePath), { recursive: true });\n writePrivateJson(statePath, { pid, projects } satisfies RuntimeProcessState);\n}\n\nexport function clearRuntimeState(configPath: string): void {\n removeRuntimeState(configPath);\n}\n\nexport async function stopBackgroundRuntime(configPath: string): Promise<boolean> {\n const state = readRuntimeState(configPath);\n if (!state) return false;\n await terminateProcess(state.pid);\n removeRuntimeState(configPath);\n return true;\n}\n\nexport function backgroundRuntimeStatus(configPath: string): RuntimeProcessStatus {\n const state = readRuntimeState(configPath);\n if (!state) {\n return { running: false, pid: null, projects: [], log_file: runtimeLogPath(configPath) };\n }\n if (!processIsRunning(state.pid)) {\n removeRuntimeState(configPath);\n return {\n running: false,\n pid: null,\n projects: state.projects,\n log_file: runtimeLogPath(configPath),\n };\n }\n return {\n running: true,\n pid: state.pid,\n projects: state.projects,\n log_file: runtimeLogPath(configPath),\n };\n}\n\nfunction waitUntilReady(child: ChildProcess, timeoutMs: number): Promise<void> {\n return new Promise((resolve, reject) => {\n let timeout: ReturnType<typeof setTimeout>;\n const cleanup = (): void => {\n clearTimeout(timeout);\n child.removeListener(\"error\", onError);\n child.removeListener(\"exit\", onExit);\n child.removeListener(\"message\", onMessage);\n };\n const onError = (error: Error): void => {\n cleanup();\n reject(error);\n };\n const onExit = (code: number | null): void => {\n cleanup();\n reject(new Error(`background runtime exited before connecting (exit ${code ?? \"unknown\"})`));\n };\n const onMessage = (message: unknown): void => {\n if (!message || typeof message !== \"object\" || (message as { type?: unknown }).type !== \"ready\") return;\n cleanup();\n resolve();\n };\n timeout = setTimeout(() => {\n cleanup();\n reject(new Error(`background runtime did not connect within ${timeoutMs}ms`));\n }, timeoutMs);\n\n child.once(\"error\", onError);\n child.once(\"exit\", onExit);\n child.on(\"message\", onMessage);\n });\n}\n\nfunction runtimeStatePath(configPath: string): string {\n return join(dirname(configPath), \"cloud-runtime.json\");\n}\n\nfunction runtimeLogPath(configPath: string): string {\n return join(dirname(configPath), \"cloud-runtime.log\");\n}\n\nfunction runtimeStartLockPath(configPath: string): string {\n return join(dirname(configPath), \"cloud-runtime.start.lock\");\n}\n\nasync function acquireRuntimeStartLock(configPath: string): Promise<void> {\n const lockPath = runtimeStartLockPath(configPath);\n mkdirSync(dirname(lockPath), { recursive: true });\n // Match worst-case `startBackgroundRuntime` hold time: stop prior runtime, wait for\n // ready, and tear down the child if ready fails, plus one poll interval.\n const deadline = Date.now()\n + BACKGROUND_RUNTIME_READY_TIMEOUT_MS\n + 2 * (STOP_GRACE_PERIOD_MS + FORCED_STOP_TIMEOUT_MS)\n + STOP_POLL_INTERVAL_MS;\n while (Date.now() < deadline) {\n try {\n const fd = openSync(lockPath, \"wx\");\n writeFileSync(fd, `${process.pid}\\n`, \"utf8\");\n closeSync(fd);\n return;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n try {\n const ownerPid = readRuntimeStartLockOwner(lockPath);\n const incompleteLockIsStale = ownerPid === null\n && Date.now() - statSync(lockPath).mtimeMs > 1_000;\n if ((ownerPid !== null && !processIsRunning(ownerPid)) || incompleteLockIsStale) {\n unlinkSync(lockPath);\n continue;\n }\n } catch {\n // Another process may have released the lock; retry.\n }\n await new Promise((resolve) => setTimeout(resolve, STOP_POLL_INTERVAL_MS));\n }\n }\n throw new Error(\"timed out waiting for the background runtime start lock\");\n}\n\nfunction releaseRuntimeStartLock(configPath: string): void {\n const lockPath = runtimeStartLockPath(configPath);\n if (readRuntimeStartLockOwner(lockPath) !== process.pid) return;\n try {\n unlinkSync(lockPath);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n }\n}\n\nfunction readRuntimeStartLockOwner(lockPath: string): number | null {\n try {\n const pid = Number.parseInt(readFileSync(lockPath, \"utf8\").trim(), 10);\n return Number.isInteger(pid) && pid > 0 ? pid : null;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw error;\n }\n}\n\nfunction readRuntimeState(configPath: string): RuntimeProcessState | null {\n try {\n const value = JSON.parse(readFileSync(runtimeStatePath(configPath), \"utf8\")) as Partial<RuntimeProcessState>;\n const pid = value.pid;\n if (typeof pid !== \"number\" || !Number.isInteger(pid) || pid <= 0 || !Array.isArray(value.projects)) return null;\n return {\n pid,\n projects: value.projects.filter((project): project is string => typeof project === \"string\"),\n };\n } catch {\n return null;\n }\n}\n\nfunction processIsRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === \"EPERM\";\n }\n}\n\nasync function waitForProcessExit(pid: number, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (processIsRunning(pid)) {\n if (Date.now() >= deadline) return false;\n await new Promise((resolve) => setTimeout(resolve, STOP_POLL_INTERVAL_MS));\n }\n return true;\n}\n\nasync function terminateProcess(pid: number): Promise<void> {\n signalProcess(pid, \"SIGTERM\");\n if (await waitForProcessExit(pid, STOP_GRACE_PERIOD_MS)) return;\n signalProcess(pid, \"SIGKILL\");\n if (!await waitForProcessExit(pid, FORCED_STOP_TIMEOUT_MS)) {\n throw new Error(`background runtime PID ${pid} did not stop`);\n }\n}\n\nfunction signalProcess(pid: number, signal: NodeJS.Signals): void {\n try {\n process.kill(pid, signal);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ESRCH\") throw error;\n }\n}\n\nfunction removeRuntimeState(configPath: string): void {\n try {\n unlinkSync(runtimeStatePath(configPath));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n }\n}\n\nfunction writePrivateJson(path: string, value: unknown): void {\n writeFileSync(path, `${JSON.stringify(value, null, 2)}\\n`, { encoding: \"utf8\", mode: 0o600 });\n chmodSync(path, 0o600);\n}\n"]}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=runtime-process.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runtime-process.test.d.ts","sourceRoot":"","sources":["../src/runtime-process.test.ts"],"names":[],"mappings":""}