@opengsd/gsd-pi 1.9.0-dev.d2070876 → 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 (117) 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 +12 -12
  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 +12 -12
  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/dist/core/skills.d.ts +0 -5
  79. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/skills.js +0 -54
  81. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  82. package/packages/pi-coding-agent/package.json +7 -7
  83. package/packages/pi-tui/package.json +2 -2
  84. package/packages/rpc-client/package.json +2 -2
  85. package/pkg/package.json +1 -1
  86. package/src/resources/extensions/gsd/auto/loop.ts +4 -48
  87. package/src/resources/extensions/gsd/auto/run-unit.ts +1 -20
  88. package/src/resources/extensions/gsd/auto-start.ts +8 -31
  89. package/src/resources/extensions/gsd/auto-worktree-sync.ts +3 -10
  90. package/src/resources/extensions/gsd/auto.ts +1 -13
  91. package/src/resources/extensions/gsd/doctor-engine-checks.ts +2 -27
  92. package/src/resources/extensions/gsd/flat-phase-migration.ts +0 -14
  93. package/src/resources/extensions/gsd/gsd-db.ts +33 -85
  94. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +0 -156
  95. package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +1 -21
  96. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +1 -74
  97. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +0 -83
  98. package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +9 -8
  99. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +0 -32
  100. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +0 -13
  101. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
  102. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +0 -60
  103. package/src/resources/extensions/gsd/worktree-state-projection.ts +24 -21
  104. package/packages/gsd-cloud/dist/background-cli.test.d.ts +0 -2
  105. package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +0 -1
  106. package/packages/gsd-cloud/dist/background-cli.test.js +0 -248
  107. package/packages/gsd-cloud/dist/background-cli.test.js.map +0 -1
  108. package/packages/gsd-cloud/dist/runtime-process.d.ts +0 -27
  109. package/packages/gsd-cloud/dist/runtime-process.d.ts.map +0 -1
  110. package/packages/gsd-cloud/dist/runtime-process.js +0 -272
  111. package/packages/gsd-cloud/dist/runtime-process.js.map +0 -1
  112. package/packages/gsd-cloud/dist/runtime-process.test.d.ts +0 -2
  113. package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +0 -1
  114. package/packages/gsd-cloud/dist/runtime-process.test.js +0 -97
  115. package/packages/gsd-cloud/dist/runtime-process.test.js.map +0 -1
  116. /package/dist/web/standalone/.next/static/{sggX-h41OjePo-FjdArP5 → V9MAVyYVPboQUB9KTTAMi}/_buildManifest.js +0 -0
  117. /package/dist/web/standalone/.next/static/{sggX-h41OjePo-FjdArP5 → V9MAVyYVPboQUB9KTTAMi}/_ssgManifest.js +0 -0
@@ -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":""}
@@ -1,97 +0,0 @@
1
- // Project/App: Open GSD
2
- // File Purpose: Regression coverage for detached cloud runtime process timing and shutdown.
3
- import { spawn } from "node:child_process";
4
- import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
- import { tmpdir } from "node:os";
6
- import { join } from "node:path";
7
- import { test } from "node:test";
8
- import assert from "node:assert/strict";
9
- import { CLOUD_RUNTIME_INITIAL_CONNECT_WINDOW_MS } from "./cloud-runtime.js";
10
- import { BACKGROUND_RUNTIME_READY_TIMEOUT_MS, backgroundRuntimeStatus, startBackgroundRuntime, stopBackgroundRuntime, } from "./runtime-process.js";
11
- test("background startup allows the cloud runtime's full initial reconnect window", () => {
12
- assert.ok(BACKGROUND_RUNTIME_READY_TIMEOUT_MS > CLOUD_RUNTIME_INITIAL_CONNECT_WINDOW_MS);
13
- });
14
- test("stop waits for the detached runtime to exit before removing its state", { timeout: 5_000 }, async () => {
15
- const root = mkdtempSync(join(tmpdir(), "gsd-cloud-stop-"));
16
- const configPath = join(root, "daemon.yaml");
17
- const statePath = join(root, "cloud-runtime.json");
18
- const child = spawn(process.execPath, [
19
- "-e",
20
- "process.on('SIGTERM',()=>setTimeout(()=>process.exit(0),200));process.send?.('ready');setInterval(()=>{},1000)",
21
- ], { stdio: ["ignore", "ignore", "ignore", "ipc"] });
22
- try {
23
- await new Promise((resolve, reject) => {
24
- child.once("error", reject);
25
- child.once("message", () => resolve());
26
- });
27
- assert.ok(child.pid);
28
- writeFileSync(statePath, `${JSON.stringify({ pid: child.pid, projects: [root] })}\n`);
29
- const startedAt = Date.now();
30
- assert.equal(await stopBackgroundRuntime(configPath), true);
31
- assert.ok(Date.now() - startedAt >= 150);
32
- assert.equal(existsSync(statePath), false);
33
- }
34
- finally {
35
- child.kill("SIGKILL");
36
- rmSync(root, { recursive: true, force: true });
37
- }
38
- });
39
- test("concurrent starts serialize and leave only the newest runtime running", { timeout: 15_000 }, async () => {
40
- const root = mkdtempSync(join(tmpdir(), "gsd-cloud-concurrent-start-"));
41
- const configPath = join(root, "daemon.yaml");
42
- const binaryPath = writeReadyRuntime(root);
43
- try {
44
- const [first, second] = await Promise.all([
45
- startBackgroundRuntime({ binaryPath, configPath, projectDirs: [root] }),
46
- startBackgroundRuntime({ binaryPath, configPath, projectDirs: [root] }),
47
- ]);
48
- assert.notEqual(first.pid, second.pid);
49
- assert.ok(first.pid);
50
- assert.equal(processIsRunning(first.pid), false);
51
- assert.equal(backgroundRuntimeStatus(configPath).pid, second.pid);
52
- }
53
- finally {
54
- await stopBackgroundRuntime(configPath).catch(() => undefined);
55
- rmSync(root, { recursive: true, force: true });
56
- }
57
- });
58
- test("verbose background starts forward the flag to the runtime child", { timeout: 10_000 }, async () => {
59
- const root = mkdtempSync(join(tmpdir(), "gsd-cloud-verbose-start-"));
60
- const configPath = join(root, "daemon.yaml");
61
- const binaryPath = writeReadyRuntime(root);
62
- try {
63
- await startBackgroundRuntime({
64
- binaryPath,
65
- configPath,
66
- projectDirs: [root],
67
- verbose: true,
68
- });
69
- const args = JSON.parse(readFileSync(join(root, "runtime-args.json"), "utf8"));
70
- assert.ok(args.includes("--verbose"));
71
- }
72
- finally {
73
- await stopBackgroundRuntime(configPath).catch(() => undefined);
74
- rmSync(root, { recursive: true, force: true });
75
- }
76
- });
77
- function writeReadyRuntime(root) {
78
- const binaryPath = join(root, "runtime.mjs");
79
- writeFileSync(binaryPath, [
80
- 'import { writeFileSync } from "node:fs";',
81
- `writeFileSync(${JSON.stringify(join(root, "runtime-args.json"))}, JSON.stringify(process.argv.slice(2)));`,
82
- 'process.on("SIGTERM", () => process.exit(0));',
83
- 'process.send?.({ type: "ready" });',
84
- 'setInterval(() => undefined, 1_000);',
85
- ].join("\n"));
86
- return binaryPath;
87
- }
88
- function processIsRunning(pid) {
89
- try {
90
- process.kill(pid, 0);
91
- return true;
92
- }
93
- catch {
94
- return false;
95
- }
96
- }
97
- //# sourceMappingURL=runtime-process.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runtime-process.test.js","sourceRoot":"","sources":["../src/runtime-process.test.ts"],"names":[],"mappings":"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","sourcesContent":["// Project/App: Open GSD\n// File Purpose: Regression coverage for detached cloud runtime process timing and shutdown.\nimport { spawn } from \"node:child_process\";\nimport { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { test } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { CLOUD_RUNTIME_INITIAL_CONNECT_WINDOW_MS } from \"./cloud-runtime.js\";\nimport {\n BACKGROUND_RUNTIME_READY_TIMEOUT_MS,\n backgroundRuntimeStatus,\n startBackgroundRuntime,\n stopBackgroundRuntime,\n} from \"./runtime-process.js\";\n\ntest(\"background startup allows the cloud runtime's full initial reconnect window\", () => {\n assert.ok(BACKGROUND_RUNTIME_READY_TIMEOUT_MS > CLOUD_RUNTIME_INITIAL_CONNECT_WINDOW_MS);\n});\n\ntest(\"stop waits for the detached runtime to exit before removing its state\", { timeout: 5_000 }, async () => {\n const root = mkdtempSync(join(tmpdir(), \"gsd-cloud-stop-\"));\n const configPath = join(root, \"daemon.yaml\");\n const statePath = join(root, \"cloud-runtime.json\");\n const child = spawn(process.execPath, [\n \"-e\",\n \"process.on('SIGTERM',()=>setTimeout(()=>process.exit(0),200));process.send?.('ready');setInterval(()=>{},1000)\",\n ], { stdio: [\"ignore\", \"ignore\", \"ignore\", \"ipc\"] });\n\n try {\n await new Promise<void>((resolve, reject) => {\n child.once(\"error\", reject);\n child.once(\"message\", () => resolve());\n });\n assert.ok(child.pid);\n writeFileSync(statePath, `${JSON.stringify({ pid: child.pid, projects: [root] })}\\n`);\n\n const startedAt = Date.now();\n assert.equal(await stopBackgroundRuntime(configPath), true);\n\n assert.ok(Date.now() - startedAt >= 150);\n assert.equal(existsSync(statePath), false);\n } finally {\n child.kill(\"SIGKILL\");\n rmSync(root, { recursive: true, force: true });\n }\n});\n\ntest(\"concurrent starts serialize and leave only the newest runtime running\", { timeout: 15_000 }, async () => {\n const root = mkdtempSync(join(tmpdir(), \"gsd-cloud-concurrent-start-\"));\n const configPath = join(root, \"daemon.yaml\");\n const binaryPath = writeReadyRuntime(root);\n\n try {\n const [first, second] = await Promise.all([\n startBackgroundRuntime({ binaryPath, configPath, projectDirs: [root] }),\n startBackgroundRuntime({ binaryPath, configPath, projectDirs: [root] }),\n ]);\n\n assert.notEqual(first.pid, second.pid);\n assert.ok(first.pid);\n assert.equal(processIsRunning(first.pid), false);\n assert.equal(backgroundRuntimeStatus(configPath).pid, second.pid);\n } finally {\n await stopBackgroundRuntime(configPath).catch(() => undefined);\n rmSync(root, { recursive: true, force: true });\n }\n});\n\ntest(\"verbose background starts forward the flag to the runtime child\", { timeout: 10_000 }, async () => {\n const root = mkdtempSync(join(tmpdir(), \"gsd-cloud-verbose-start-\"));\n const configPath = join(root, \"daemon.yaml\");\n const binaryPath = writeReadyRuntime(root);\n\n try {\n await startBackgroundRuntime({\n binaryPath,\n configPath,\n projectDirs: [root],\n verbose: true,\n });\n\n const args = JSON.parse(readFileSync(join(root, \"runtime-args.json\"), \"utf8\")) as string[];\n assert.ok(args.includes(\"--verbose\"));\n } finally {\n await stopBackgroundRuntime(configPath).catch(() => undefined);\n rmSync(root, { recursive: true, force: true });\n }\n});\n\nfunction writeReadyRuntime(root: string): string {\n const binaryPath = join(root, \"runtime.mjs\");\n writeFileSync(binaryPath, [\n 'import { writeFileSync } from \"node:fs\";',\n `writeFileSync(${JSON.stringify(join(root, \"runtime-args.json\"))}, JSON.stringify(process.argv.slice(2)));`,\n 'process.on(\"SIGTERM\", () => process.exit(0));',\n 'process.send?.({ type: \"ready\" });',\n 'setInterval(() => undefined, 1_000);',\n ].join(\"\\n\"));\n return binaryPath;\n}\n\nfunction processIsRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n"]}