@opengsd/gsd-pi 1.9.0-dev.e10b60d4 → 1.10.0-dev.a5e42e99
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/README.md +10 -9
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +24 -4
- package/dist/resources/extensions/gsd/auto/run-unit.js +19 -1
- package/dist/resources/extensions/gsd/auto-model-selection.js +1 -0
- package/dist/resources/extensions/gsd/auto-start.js +30 -10
- package/dist/resources/extensions/gsd/auto-verification.js +25 -12
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +7 -3
- package/dist/resources/extensions/gsd/auto.js +12 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +4 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +12 -3
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +37 -6
- package/dist/resources/extensions/gsd/flat-phase-migration.js +12 -0
- package/dist/resources/extensions/gsd/gsd-db.js +75 -31
- package/dist/resources/extensions/gsd/model-cost-table.js +6 -3
- package/dist/resources/extensions/gsd/model-router.js +10 -5
- package/dist/resources/extensions/gsd/planning-subagent-registry.js +12 -4
- package/dist/resources/extensions/gsd/preferences-models.js +50 -10
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +41 -6
- package/dist/resources/extensions/gsd/preferences.js +7 -0
- package/dist/resources/extensions/gsd/templates/PREFERENCES.md +3 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -1
- package/dist/resources/extensions/gsd/verification-evidence.js +5 -3
- package/dist/resources/extensions/gsd/worktree-state-projection.js +20 -18
- package/dist/resources/extensions/ollama/ollama-chat-provider.js +2 -2
- package/dist/resources/extensions/subagent/index.js +1 -1
- package/dist/startup-model-validation.d.ts +1 -1
- 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 +6 -6
- 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 +6 -6
- 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 +2 -2
- 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/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +1 -1
- package/package.json +7 -6
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/rpc.d.ts +1 -1
- package/packages/contracts/dist/rpc.d.ts.map +1 -1
- package/packages/contracts/dist/rpc.js +1 -1
- package/packages/contracts/dist/rpc.js.map +1 -1
- package/packages/contracts/dist/rpc.test.js +1 -1
- package/packages/contracts/dist/rpc.test.js.map +1 -1
- 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/dist/cli/args.js +2 -2
- package/packages/gsd-agent-modes/dist/cli/args.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.js +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/README.md +21 -5
- package/packages/gsd-cloud/dist/background-cli.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/background-cli.test.js +325 -0
- package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -0
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cli.js +146 -16
- 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 +7 -1
- package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +7 -4
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.js +115 -31
- package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.test.js +224 -59
- package/packages/gsd-cloud/dist/cloud-runtime.test.js.map +1 -1
- package/packages/gsd-cloud/dist/runtime-artifacts.d.ts +3 -0
- package/packages/gsd-cloud/dist/runtime-artifacts.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-artifacts.js +36 -0
- package/packages/gsd-cloud/dist/runtime-artifacts.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.d.ts +32 -0
- package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.js +640 -0
- package/packages/gsd-cloud/dist/runtime-process.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js +543 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.d.ts +117 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.js +291 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.test.js +434 -0
- package/packages/gsd-cloud/dist/runtime-telemetry.test.js.map +1 -0
- package/packages/gsd-cloud/package.json +2 -2
- package/packages/mcp-server/dist/cli-runner.d.ts +3 -0
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
- package/packages/mcp-server/dist/cli-runner.js +119 -33
- 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/dist/types.d.ts +2 -2
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +130 -2
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +101 -9
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.js +18 -6
- package/packages/pi-ai/dist/models.js.map +1 -1
- package/packages/pi-ai/dist/providers/amazon-bedrock.js +3 -2
- package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/packages/pi-ai/dist/providers/google.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts +1 -1
- package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js +1 -1
- package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +9 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -3
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +54 -0
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.d.ts +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.js +1 -0
- package/packages/pi-coding-agent/dist/theme/theme.js.map +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/dist/theme/theme.d.ts +1 -1
- package/pkg/dist/theme/theme.d.ts.map +1 -1
- package/pkg/dist/theme/theme.js +1 -0
- package/pkg/dist/theme/theme.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop.ts +48 -4
- package/src/resources/extensions/gsd/auto/run-unit.ts +20 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +1 -0
- package/src/resources/extensions/gsd/auto-start.ts +32 -9
- package/src/resources/extensions/gsd/auto-verification.ts +46 -12
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +10 -3
- package/src/resources/extensions/gsd/auto.ts +13 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +4 -3
- package/src/resources/extensions/gsd/docs/preferences-reference.md +12 -3
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +42 -6
- package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -0
- package/src/resources/extensions/gsd/gsd-db.ts +85 -33
- package/src/resources/extensions/gsd/model-cost-table.ts +14 -3
- package/src/resources/extensions/gsd/model-router.ts +10 -5
- package/src/resources/extensions/gsd/planning-subagent-registry.ts +18 -4
- package/src/resources/extensions/gsd/preferences-models.ts +54 -10
- package/src/resources/extensions/gsd/preferences-types.ts +10 -2
- package/src/resources/extensions/gsd/preferences-validation.ts +49 -6
- package/src/resources/extensions/gsd/preferences.ts +13 -0
- package/src/resources/extensions/gsd/templates/PREFERENCES.md +3 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +74 -1
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +147 -0
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +8 -9
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +22 -7
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +38 -1
- package/src/resources/extensions/gsd/tests/preferences.test.ts +71 -4
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/workflow-authority-faults.test.ts +238 -0
- package/src/resources/extensions/gsd/tests/workflow-authority-fixture.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/workflow-authority-fixture.ts +141 -0
- package/src/resources/extensions/gsd/tests/workflow-authority-projection-conflict.test.ts +177 -0
- package/src/resources/extensions/gsd/tests/workflow-fault-harness.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/workflow-fault-harness.ts +72 -0
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +40 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +5 -1
- package/src/resources/extensions/gsd/verification-evidence.ts +5 -2
- package/src/resources/extensions/gsd/worktree-state-projection.ts +21 -24
- package/src/resources/extensions/ollama/ollama-chat-provider.ts +2 -2
- package/src/resources/extensions/ollama/tests/ollama-chat-provider-think.test.ts +4 -0
- package/src/resources/extensions/subagent/index.ts +1 -1
- /package/dist/web/standalone/.next/static/{V9MAVyYVPboQUB9KTTAMi → xwUaOqui04jIIFVsIBLiL}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{V9MAVyYVPboQUB9KTTAMi → xwUaOqui04jIIFVsIBLiL}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
// Project/App: Open GSD
|
|
2
|
+
// File Purpose: Detached cloud runtime process lifecycle and status persistence.
|
|
3
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { chmodSync, closeSync, linkSync, mkdirSync, openSync, readFileSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
6
|
+
import { dirname } from "node:path";
|
|
7
|
+
import { CLOUD_RUNTIME_INITIAL_CONNECT_WINDOW_MS } from "./cloud-runtime.js";
|
|
8
|
+
import { canonicalConfigPath, runtimeArtifactPath } from "./runtime-artifacts.js";
|
|
9
|
+
const PROCESS_STARTUP_GRACE_MS = 5_000;
|
|
10
|
+
const FORCED_STOP_TIMEOUT_MS = 5_000;
|
|
11
|
+
const STOP_GRACE_PERIOD_MS = 5_000;
|
|
12
|
+
const STOP_POLL_INTERVAL_MS = 50;
|
|
13
|
+
const MALFORMED_START_LOCK_GRACE_MS = 5_000;
|
|
14
|
+
export const BACKGROUND_RUNTIME_READY_TIMEOUT_MS = CLOUD_RUNTIME_INITIAL_CONNECT_WINDOW_MS + PROCESS_STARTUP_GRACE_MS;
|
|
15
|
+
const START_LOCK_TIMEOUT_MS = BACKGROUND_RUNTIME_READY_TIMEOUT_MS
|
|
16
|
+
+ 2 * (STOP_GRACE_PERIOD_MS + FORCED_STOP_TIMEOUT_MS)
|
|
17
|
+
+ STOP_POLL_INTERVAL_MS;
|
|
18
|
+
export async function startBackgroundRuntime(opts) {
|
|
19
|
+
const releaseStartLock = await acquireRuntimeStartLock(opts.configPath);
|
|
20
|
+
try {
|
|
21
|
+
await stopBackgroundRuntime(opts.configPath);
|
|
22
|
+
const logFile = runtimeLogPath(opts.configPath);
|
|
23
|
+
mkdirSync(dirname(runtimeStatePath(opts.configPath)), { recursive: true });
|
|
24
|
+
const logFd = openSync(logFile, "a", 0o600);
|
|
25
|
+
chmodSync(logFile, 0o600);
|
|
26
|
+
const runArgs = [opts.binaryPath, "_run", "--config", opts.configPath];
|
|
27
|
+
if (opts.verbose)
|
|
28
|
+
runArgs.push("--verbose");
|
|
29
|
+
let child;
|
|
30
|
+
try {
|
|
31
|
+
child = spawn(process.execPath, runArgs, {
|
|
32
|
+
cwd: opts.projectDirs[0] ?? process.cwd(),
|
|
33
|
+
detached: true,
|
|
34
|
+
env: process.env,
|
|
35
|
+
stdio: ["ignore", logFd, logFd, "ipc"],
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
closeSync(logFd);
|
|
40
|
+
}
|
|
41
|
+
if (child.pid == null) {
|
|
42
|
+
child.kill();
|
|
43
|
+
throw new Error(`could not start the background runtime; see ${logFile}`);
|
|
44
|
+
}
|
|
45
|
+
const pid = child.pid;
|
|
46
|
+
let processStartIdentity = null;
|
|
47
|
+
try {
|
|
48
|
+
processStartIdentity = (opts.processIdentityReader ?? readProcessStartIdentity)(pid);
|
|
49
|
+
if (!processStartIdentity) {
|
|
50
|
+
throw new Error(`could not determine process identity for PID ${pid}`);
|
|
51
|
+
}
|
|
52
|
+
writeRuntimeStateWithIdentity(opts.configPath, pid, opts.projectDirs, processStartIdentity);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (child.connected)
|
|
56
|
+
child.disconnect();
|
|
57
|
+
if (processStartIdentity) {
|
|
58
|
+
await terminateProcess(pid, processStartIdentity);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
await terminateKnownChild(child);
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
await waitUntilReady(child, opts.readyTimeoutMs ?? BACKGROUND_RUNTIME_READY_TIMEOUT_MS);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (child.connected)
|
|
70
|
+
child.disconnect();
|
|
71
|
+
await terminateProcess(pid, processStartIdentity);
|
|
72
|
+
removeRuntimeState(opts.configPath);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
if (child.connected)
|
|
76
|
+
child.disconnect();
|
|
77
|
+
child.unref();
|
|
78
|
+
return {
|
|
79
|
+
running: true,
|
|
80
|
+
pid,
|
|
81
|
+
projects: opts.projectDirs,
|
|
82
|
+
log_file: logFile,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
releaseStartLock();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Record a running runtime so a later launch can find and stop it. Used both by
|
|
91
|
+
* the detached launcher (child PID) and by a `--foreground` session (its own
|
|
92
|
+
* PID), so the two modes share one source of truth and never coexist on the
|
|
93
|
+
* same device token.
|
|
94
|
+
*/
|
|
95
|
+
export function writeRuntimeState(configPath, pid, projects) {
|
|
96
|
+
const processStartIdentity = readProcessStartIdentity(pid);
|
|
97
|
+
if (!processStartIdentity) {
|
|
98
|
+
throw new Error(`could not determine process identity for PID ${pid}`);
|
|
99
|
+
}
|
|
100
|
+
writeRuntimeStateWithIdentity(configPath, pid, projects, processStartIdentity);
|
|
101
|
+
}
|
|
102
|
+
function writeRuntimeStateWithIdentity(configPath, pid, projects, processStartIdentity) {
|
|
103
|
+
const statePath = runtimeStatePath(configPath);
|
|
104
|
+
mkdirSync(dirname(statePath), { recursive: true });
|
|
105
|
+
writePrivateJson(statePath, {
|
|
106
|
+
pid,
|
|
107
|
+
projects,
|
|
108
|
+
process_start_identity: processStartIdentity,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
export function clearRuntimeState(configPath) {
|
|
112
|
+
removeRuntimeState(configPath);
|
|
113
|
+
}
|
|
114
|
+
export async function stopBackgroundRuntime(configPath) {
|
|
115
|
+
const located = readRuntimeState(configPath);
|
|
116
|
+
if (!located)
|
|
117
|
+
return false;
|
|
118
|
+
const processStartIdentity = located.state.process_start_identity;
|
|
119
|
+
if (!processStartIdentity || !runtimeProcessMatches(located.state)) {
|
|
120
|
+
removeRuntimeStateFile(located.path);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
await terminateProcess(located.state.pid, processStartIdentity);
|
|
124
|
+
removeRuntimeStateFile(located.path);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
export function backgroundRuntimeStatus(configPath) {
|
|
128
|
+
const located = readRuntimeState(configPath);
|
|
129
|
+
if (!located) {
|
|
130
|
+
return { running: false, pid: null, projects: [], log_file: runtimeLogPath(configPath) };
|
|
131
|
+
}
|
|
132
|
+
if (!runtimeProcessMatches(located.state)) {
|
|
133
|
+
removeRuntimeStateFile(located.path);
|
|
134
|
+
return {
|
|
135
|
+
running: false,
|
|
136
|
+
pid: null,
|
|
137
|
+
projects: located.state.projects,
|
|
138
|
+
log_file: runtimeLogPath(configPath),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
running: true,
|
|
143
|
+
pid: located.state.pid,
|
|
144
|
+
projects: located.state.projects,
|
|
145
|
+
log_file: runtimeLogPath(configPath),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function waitUntilReady(child, timeoutMs) {
|
|
149
|
+
return new Promise((resolve, reject) => {
|
|
150
|
+
let timeout;
|
|
151
|
+
const cleanup = () => {
|
|
152
|
+
clearTimeout(timeout);
|
|
153
|
+
child.removeListener("error", onError);
|
|
154
|
+
child.removeListener("exit", onExit);
|
|
155
|
+
child.removeListener("message", onMessage);
|
|
156
|
+
};
|
|
157
|
+
const onError = (error) => {
|
|
158
|
+
cleanup();
|
|
159
|
+
reject(error);
|
|
160
|
+
};
|
|
161
|
+
const onExit = (code) => {
|
|
162
|
+
cleanup();
|
|
163
|
+
reject(new Error(`background runtime exited before connecting (exit ${code ?? "unknown"})`));
|
|
164
|
+
};
|
|
165
|
+
const onMessage = (message) => {
|
|
166
|
+
if (!message || typeof message !== "object" || message.type !== "ready")
|
|
167
|
+
return;
|
|
168
|
+
cleanup();
|
|
169
|
+
resolve();
|
|
170
|
+
};
|
|
171
|
+
timeout = setTimeout(() => {
|
|
172
|
+
cleanup();
|
|
173
|
+
reject(new Error(`background runtime did not connect within ${timeoutMs}ms`));
|
|
174
|
+
}, timeoutMs);
|
|
175
|
+
child.once("error", onError);
|
|
176
|
+
child.once("exit", onExit);
|
|
177
|
+
child.on("message", onMessage);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
export function runtimeStatePath(configPath) {
|
|
181
|
+
return runtimeArtifactPath(configPath, "state");
|
|
182
|
+
}
|
|
183
|
+
export function runtimeLogPath(configPath) {
|
|
184
|
+
return runtimeArtifactPath(configPath, "log");
|
|
185
|
+
}
|
|
186
|
+
function runtimeStartLockPath(configPath) {
|
|
187
|
+
return runtimeArtifactPath(configPath, "start.lock");
|
|
188
|
+
}
|
|
189
|
+
export async function acquireRuntimeStartLock(configPath, onRecoveryClaimed) {
|
|
190
|
+
const lockPath = runtimeStartLockPath(configPath);
|
|
191
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
192
|
+
const processStartIdentity = readProcessStartIdentity(process.pid);
|
|
193
|
+
if (!processStartIdentity) {
|
|
194
|
+
throw new Error(`could not determine process identity for PID ${process.pid}`);
|
|
195
|
+
}
|
|
196
|
+
const owner = {
|
|
197
|
+
pid: process.pid,
|
|
198
|
+
process_start_identity: processStartIdentity,
|
|
199
|
+
};
|
|
200
|
+
// Match worst-case `startBackgroundRuntime` hold time: stop prior runtime, wait for
|
|
201
|
+
// ready, and tear down the child if ready fails, plus one poll interval.
|
|
202
|
+
const deadline = Date.now() + START_LOCK_TIMEOUT_MS;
|
|
203
|
+
while (Date.now() < deadline) {
|
|
204
|
+
try {
|
|
205
|
+
createRuntimeStartLock(lockPath, owner);
|
|
206
|
+
let released = false;
|
|
207
|
+
return () => {
|
|
208
|
+
if (released)
|
|
209
|
+
return;
|
|
210
|
+
released = true;
|
|
211
|
+
releaseRuntimeStartLock(lockPath, owner);
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (error.code !== "EEXIST")
|
|
216
|
+
throw error;
|
|
217
|
+
try {
|
|
218
|
+
if (recoverRuntimeStartLock(lockPath, configPath, onRecoveryClaimed)) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
// Another process may have released the lock; retry.
|
|
224
|
+
}
|
|
225
|
+
await new Promise((resolve) => setTimeout(resolve, STOP_POLL_INTERVAL_MS));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
throw new Error("timed out waiting for the background runtime start lock");
|
|
229
|
+
}
|
|
230
|
+
function createRuntimeStartLock(lockPath, owner) {
|
|
231
|
+
const temporaryPath = `${lockPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
232
|
+
try {
|
|
233
|
+
writePrivateJson(temporaryPath, owner);
|
|
234
|
+
linkSync(temporaryPath, lockPath);
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
removeRuntimeStateFile(temporaryPath);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function releaseRuntimeStartLock(lockPath, owner) {
|
|
241
|
+
if (!runtimeStartLockOwnersEqual(readRuntimeStartLockOwner(lockPath), owner))
|
|
242
|
+
return;
|
|
243
|
+
removeRuntimeStateFile(lockPath);
|
|
244
|
+
}
|
|
245
|
+
function readRuntimeStartLockOwner(lockPath) {
|
|
246
|
+
try {
|
|
247
|
+
const text = readFileSync(lockPath, "utf8").trim();
|
|
248
|
+
if (/^\d+$/.test(text)) {
|
|
249
|
+
const pid = Number.parseInt(text, 10);
|
|
250
|
+
return pid > 0 ? { pid } : null;
|
|
251
|
+
}
|
|
252
|
+
const value = JSON.parse(text);
|
|
253
|
+
if (!Number.isInteger(value.pid) || (value.pid ?? 0) <= 0)
|
|
254
|
+
return null;
|
|
255
|
+
if (typeof value.process_start_identity !== "string")
|
|
256
|
+
return null;
|
|
257
|
+
return { pid: value.pid, process_start_identity: value.process_start_identity };
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
if (error.code === "ENOENT")
|
|
261
|
+
return null;
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function runtimeStartLockOwnerIsRunning(owner) {
|
|
266
|
+
if (!owner.process_start_identity)
|
|
267
|
+
return processIsRunning(owner.pid);
|
|
268
|
+
return processMatchesIdentity(owner.pid, owner.process_start_identity);
|
|
269
|
+
}
|
|
270
|
+
function runtimeStartLockCanBeRecovered(lockPath, configPath) {
|
|
271
|
+
const owner = readRuntimeStartLockOwner(lockPath);
|
|
272
|
+
if (owner?.process_start_identity)
|
|
273
|
+
return !runtimeStartLockOwnerIsRunning(owner);
|
|
274
|
+
const age = Date.now() - statSync(lockPath).mtimeMs;
|
|
275
|
+
if (owner) {
|
|
276
|
+
if (!runtimeStartLockOwnerIsRunning(owner))
|
|
277
|
+
return true;
|
|
278
|
+
return age > START_LOCK_TIMEOUT_MS
|
|
279
|
+
&& inspectProcessCommandConfig(owner.pid, configPath) === false;
|
|
280
|
+
}
|
|
281
|
+
return age > MALFORMED_START_LOCK_GRACE_MS;
|
|
282
|
+
}
|
|
283
|
+
function recoverRuntimeStartLock(lockPath, configPath, onRecoveryClaimed) {
|
|
284
|
+
const recoveryPath = `${lockPath}.recovery.${process.pid}.${randomUUID()}`;
|
|
285
|
+
try {
|
|
286
|
+
linkSync(lockPath, recoveryPath);
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
if (error.code === "ENOENT")
|
|
290
|
+
return false;
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
if (!runtimeStartLockCanBeRecovered(recoveryPath, configPath))
|
|
295
|
+
return false;
|
|
296
|
+
onRecoveryClaimed?.();
|
|
297
|
+
if (!pathsReferenceSameFile(lockPath, recoveryPath))
|
|
298
|
+
return false;
|
|
299
|
+
unlinkSync(lockPath);
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
removeRuntimeStateFile(recoveryPath);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function pathsReferenceSameFile(firstPath, secondPath) {
|
|
307
|
+
try {
|
|
308
|
+
const first = statSync(firstPath, { bigint: true });
|
|
309
|
+
const second = statSync(secondPath, { bigint: true });
|
|
310
|
+
return first.dev === second.dev && first.ino === second.ino;
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function runtimeStartLockOwnersEqual(first, second) {
|
|
317
|
+
return first?.pid === second.pid
|
|
318
|
+
&& first.process_start_identity === second.process_start_identity;
|
|
319
|
+
}
|
|
320
|
+
function readRuntimeState(configPath) {
|
|
321
|
+
const statePath = runtimeStatePath(configPath);
|
|
322
|
+
const current = readRuntimeStateFile(statePath);
|
|
323
|
+
if (current) {
|
|
324
|
+
if (typeof current.process_start_identity === "string"
|
|
325
|
+
&& statePath !== legacyRuntimeStatePath(configPath)) {
|
|
326
|
+
return { path: statePath, state: current };
|
|
327
|
+
}
|
|
328
|
+
return migrateLegacyRuntimeState(configPath, statePath, current);
|
|
329
|
+
}
|
|
330
|
+
const legacyPath = legacyRuntimeStatePath(configPath);
|
|
331
|
+
if (legacyPath === statePath)
|
|
332
|
+
return null;
|
|
333
|
+
const legacy = readRuntimeStateFile(legacyPath);
|
|
334
|
+
if (!legacy)
|
|
335
|
+
return null;
|
|
336
|
+
return migrateLegacyRuntimeState(configPath, legacyPath, legacy);
|
|
337
|
+
}
|
|
338
|
+
function readRuntimeStateFile(path) {
|
|
339
|
+
try {
|
|
340
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
341
|
+
const pid = value.pid;
|
|
342
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0
|
|
343
|
+
|| !Array.isArray(value.projects))
|
|
344
|
+
return null;
|
|
345
|
+
return {
|
|
346
|
+
pid,
|
|
347
|
+
projects: value.projects.filter((project) => typeof project === "string"),
|
|
348
|
+
process_start_identity: value.process_start_identity,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function migrateLegacyRuntimeState(configPath, sourcePath, state) {
|
|
356
|
+
const destinationPath = runtimeStatePath(configPath);
|
|
357
|
+
if (sourcePath === legacyRuntimeStatePath(configPath)
|
|
358
|
+
&& !processCommandMatchesConfig(state.pid, configPath))
|
|
359
|
+
return null;
|
|
360
|
+
if (typeof state.process_start_identity === "string") {
|
|
361
|
+
if (sourcePath !== destinationPath) {
|
|
362
|
+
writeRuntimeStateWithIdentity(configPath, state.pid, state.projects, state.process_start_identity);
|
|
363
|
+
removeRuntimeStateFile(sourcePath);
|
|
364
|
+
}
|
|
365
|
+
return { path: destinationPath, state };
|
|
366
|
+
}
|
|
367
|
+
const processStartIdentity = readProcessStartIdentity(state.pid);
|
|
368
|
+
if (!processStartIdentity)
|
|
369
|
+
return null;
|
|
370
|
+
writeRuntimeStateWithIdentity(configPath, state.pid, state.projects, processStartIdentity);
|
|
371
|
+
if (sourcePath !== destinationPath)
|
|
372
|
+
removeRuntimeStateFile(sourcePath);
|
|
373
|
+
return {
|
|
374
|
+
path: destinationPath,
|
|
375
|
+
state: { ...state, process_start_identity: processStartIdentity },
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function legacyRuntimeStatePath(configPath) {
|
|
379
|
+
return runtimeArtifactPath(`${dirname(canonicalConfigPath(configPath))}/daemon.yaml`, "state");
|
|
380
|
+
}
|
|
381
|
+
function runtimeProcessMatches(state) {
|
|
382
|
+
return typeof state.process_start_identity === "string"
|
|
383
|
+
&& processIsRunning(state.pid)
|
|
384
|
+
&& readProcessStartIdentity(state.pid) === state.process_start_identity;
|
|
385
|
+
}
|
|
386
|
+
function readProcessStartIdentity(pid) {
|
|
387
|
+
try {
|
|
388
|
+
if (process.platform === "linux") {
|
|
389
|
+
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
390
|
+
const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
391
|
+
return fields[19] ?? null;
|
|
392
|
+
}
|
|
393
|
+
if (process.platform === "darwin" || process.platform === "freebsd") {
|
|
394
|
+
return execFileSync("/bin/ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
395
|
+
encoding: "utf8",
|
|
396
|
+
}).trim() || null;
|
|
397
|
+
}
|
|
398
|
+
if (process.platform === "win32") {
|
|
399
|
+
return execFileSync("powershell.exe", [
|
|
400
|
+
"-NoProfile",
|
|
401
|
+
"-NonInteractive",
|
|
402
|
+
"-Command",
|
|
403
|
+
`(Get-Process -Id ${pid}).StartTime.ToUniversalTime().Ticks`,
|
|
404
|
+
], { encoding: "utf8" }).trim() || null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
function processCommandMatchesConfig(pid, configPath) {
|
|
413
|
+
return inspectProcessCommandConfig(pid, configPath) === true;
|
|
414
|
+
}
|
|
415
|
+
function inspectProcessCommandConfig(pid, configPath) {
|
|
416
|
+
const expectedConfigPath = canonicalConfigPath(configPath);
|
|
417
|
+
try {
|
|
418
|
+
if (process.platform === "linux") {
|
|
419
|
+
const args = readFileSync(`/proc/${pid}/cmdline`, "utf8").split("\0").filter(Boolean);
|
|
420
|
+
return commandArgsMatchConfig(args, expectedConfigPath);
|
|
421
|
+
}
|
|
422
|
+
if (process.platform === "darwin" || process.platform === "freebsd") {
|
|
423
|
+
const command = execFileSync("/bin/ps", ["-ww", "-o", "command=", "-p", String(pid)], {
|
|
424
|
+
encoding: "utf8",
|
|
425
|
+
}).trim();
|
|
426
|
+
return commandLineMatchesRuntimeConfig(command, expectedConfigPath, process.platform);
|
|
427
|
+
}
|
|
428
|
+
if (process.platform === "win32") {
|
|
429
|
+
const command = execFileSync("powershell.exe", [
|
|
430
|
+
"-NoProfile",
|
|
431
|
+
"-NonInteractive",
|
|
432
|
+
"-Command",
|
|
433
|
+
`(Get-CimInstance Win32_Process -Filter \"ProcessId = ${pid}\").CommandLine`,
|
|
434
|
+
], { encoding: "utf8" }).trim();
|
|
435
|
+
return commandLineMatchesRuntimeConfig(command, expectedConfigPath, process.platform);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
export function commandLineMatchesRuntimeConfig(command, configPath, platform) {
|
|
444
|
+
if (platform === "win32") {
|
|
445
|
+
return commandArgsMatchConfig(splitWindowsCommandLine(command), canonicalConfigPath(configPath));
|
|
446
|
+
}
|
|
447
|
+
return commandStringMatchesConfig(command, canonicalConfigPath(configPath));
|
|
448
|
+
}
|
|
449
|
+
function splitWindowsCommandLine(command) {
|
|
450
|
+
const args = [];
|
|
451
|
+
let index = 0;
|
|
452
|
+
while (index < command.length) {
|
|
453
|
+
while (/\s/.test(command[index] ?? ""))
|
|
454
|
+
index += 1;
|
|
455
|
+
if (index >= command.length)
|
|
456
|
+
break;
|
|
457
|
+
let argument = "";
|
|
458
|
+
let inQuotes = false;
|
|
459
|
+
while (index < command.length) {
|
|
460
|
+
if (!inQuotes && /\s/.test(command[index] ?? ""))
|
|
461
|
+
break;
|
|
462
|
+
let backslashes = 0;
|
|
463
|
+
while (command[index] === "\\") {
|
|
464
|
+
backslashes += 1;
|
|
465
|
+
index += 1;
|
|
466
|
+
}
|
|
467
|
+
if (command[index] === '"') {
|
|
468
|
+
argument += "\\".repeat(Math.floor(backslashes / 2));
|
|
469
|
+
if (backslashes % 2 === 0) {
|
|
470
|
+
inQuotes = !inQuotes;
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
argument += '"';
|
|
474
|
+
}
|
|
475
|
+
index += 1;
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
argument += "\\".repeat(backslashes);
|
|
479
|
+
if (index < command.length) {
|
|
480
|
+
argument += command[index];
|
|
481
|
+
index += 1;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
args.push(argument);
|
|
485
|
+
}
|
|
486
|
+
return args;
|
|
487
|
+
}
|
|
488
|
+
function commandStringMatchesConfig(command, configPath) {
|
|
489
|
+
const runtimeCommand = findRuntimeCommand(command);
|
|
490
|
+
if (!runtimeCommand)
|
|
491
|
+
return false;
|
|
492
|
+
const executablePrefix = command.slice(0, runtimeCommand.index);
|
|
493
|
+
if (!commandPrefixHasCloudRuntimeLauncher(executablePrefix))
|
|
494
|
+
return false;
|
|
495
|
+
if (runtimeCommand.command === "login"
|
|
496
|
+
&& !/(?:^|\s)--foreground(?:\s|$)/.test(command.slice(runtimeCommand.index)))
|
|
497
|
+
return false;
|
|
498
|
+
const match = command.slice(runtimeCommand.index).match(/(?:^|\s)(?:--config|-c)(?:=|\s+)(.*?)(?=\s+-{1,2}[A-Za-z](?:[\w-]*)(?:=|\s|$)|$)/);
|
|
499
|
+
const configuredPath = match?.[1]?.trim().replace(/^(?:"([\s\S]*)"|'([\s\S]*)')$/, "$1$2");
|
|
500
|
+
return configuredPath !== undefined && canonicalConfigPath(configuredPath) === configPath;
|
|
501
|
+
}
|
|
502
|
+
function commandArgsMatchConfig(args, configPath) {
|
|
503
|
+
const runtimeCommand = findRuntimeCommandInArgs(args);
|
|
504
|
+
if (!runtimeCommand)
|
|
505
|
+
return false;
|
|
506
|
+
if (!argsHaveCloudRuntimeLauncher(args.slice(0, runtimeCommand.index)))
|
|
507
|
+
return false;
|
|
508
|
+
const runtimeArgs = args.slice(runtimeCommand.index + 1);
|
|
509
|
+
if (runtimeCommand.command === "login" && !runtimeArgs.includes("--foreground"))
|
|
510
|
+
return false;
|
|
511
|
+
const configIndex = runtimeArgs.findIndex((arg) => arg === "--config" || arg === "-c");
|
|
512
|
+
const configuredPath = configIndex >= 0 ? runtimeArgs[configIndex + 1] : undefined;
|
|
513
|
+
const equalsArgument = runtimeArgs.find((arg) => arg.startsWith("--config=") || arg.startsWith("-c="))
|
|
514
|
+
?.replace(/^(?:--config|-c)=/, "");
|
|
515
|
+
return [configuredPath, equalsArgument].some((value) => value !== undefined && canonicalConfigPath(value) === configPath);
|
|
516
|
+
}
|
|
517
|
+
function argsHaveCloudRuntimeLauncher(args) {
|
|
518
|
+
if (args.length === 1)
|
|
519
|
+
return isCloudRuntimeExecutable(args[0]);
|
|
520
|
+
return args.length === 2
|
|
521
|
+
&& isNodeExecutable(args[0])
|
|
522
|
+
&& isCloudRuntimeExecutable(args[1]);
|
|
523
|
+
}
|
|
524
|
+
function commandPrefixHasCloudRuntimeLauncher(prefix) {
|
|
525
|
+
const args = splitFlattenedCommandPrefix(prefix);
|
|
526
|
+
return args !== null && argsHaveCloudRuntimeLauncher(args);
|
|
527
|
+
}
|
|
528
|
+
function splitFlattenedCommandPrefix(prefix) {
|
|
529
|
+
const args = [];
|
|
530
|
+
const token = /\s*(?:"([^"]*)"|'([^']*)'|(\S+))/gy;
|
|
531
|
+
while (token.lastIndex < prefix.length) {
|
|
532
|
+
const match = token.exec(prefix);
|
|
533
|
+
if (!match)
|
|
534
|
+
return null;
|
|
535
|
+
args.push(match[1] ?? match[2] ?? match[3]);
|
|
536
|
+
}
|
|
537
|
+
return args;
|
|
538
|
+
}
|
|
539
|
+
function findRuntimeCommand(command) {
|
|
540
|
+
const match = /(?:^|\s)(_run|connect|login)(?=\s|$)/.exec(command);
|
|
541
|
+
return match ? { command: match[1], index: match.index } : null;
|
|
542
|
+
}
|
|
543
|
+
function findRuntimeCommandInArgs(args) {
|
|
544
|
+
const index = args.findIndex((arg) => arg === "_run" || arg === "connect" || arg === "login");
|
|
545
|
+
return index < 0 ? null : { command: args[index], index };
|
|
546
|
+
}
|
|
547
|
+
function isCloudRuntimeExecutable(argument) {
|
|
548
|
+
return /(?:^|[\\/])gsd-cloud(?:\.[cm]?js)?$/.test(argument);
|
|
549
|
+
}
|
|
550
|
+
function isNodeExecutable(argument) {
|
|
551
|
+
return /(?:^|[\\/])node(?:\.exe)?$/i.test(argument);
|
|
552
|
+
}
|
|
553
|
+
function processIsRunning(pid) {
|
|
554
|
+
try {
|
|
555
|
+
process.kill(pid, 0);
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
catch (error) {
|
|
559
|
+
return error.code === "EPERM";
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
async function waitForProcessExit(pid, expectedIdentity, timeoutMs) {
|
|
563
|
+
const deadline = Date.now() + timeoutMs;
|
|
564
|
+
while (processMatchesIdentity(pid, expectedIdentity)) {
|
|
565
|
+
if (Date.now() >= deadline)
|
|
566
|
+
return false;
|
|
567
|
+
await new Promise((resolve) => setTimeout(resolve, STOP_POLL_INTERVAL_MS));
|
|
568
|
+
}
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
async function terminateProcess(pid, expectedIdentity) {
|
|
572
|
+
if (!signalProcess(pid, expectedIdentity, "SIGTERM"))
|
|
573
|
+
return;
|
|
574
|
+
if (await waitForProcessExit(pid, expectedIdentity, STOP_GRACE_PERIOD_MS))
|
|
575
|
+
return;
|
|
576
|
+
if (!signalProcess(pid, expectedIdentity, "SIGKILL"))
|
|
577
|
+
return;
|
|
578
|
+
if (!await waitForProcessExit(pid, expectedIdentity, FORCED_STOP_TIMEOUT_MS)) {
|
|
579
|
+
throw new Error(`background runtime PID ${pid} did not stop`);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
async function terminateKnownChild(child) {
|
|
583
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
584
|
+
return;
|
|
585
|
+
child.kill("SIGTERM");
|
|
586
|
+
if (await waitForChildExit(child, STOP_GRACE_PERIOD_MS))
|
|
587
|
+
return;
|
|
588
|
+
child.kill("SIGKILL");
|
|
589
|
+
if (!await waitForChildExit(child, FORCED_STOP_TIMEOUT_MS)) {
|
|
590
|
+
throw new Error(`background runtime PID ${child.pid ?? "unknown"} did not stop`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function waitForChildExit(child, timeoutMs) {
|
|
594
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
595
|
+
return Promise.resolve(true);
|
|
596
|
+
return new Promise((resolve) => {
|
|
597
|
+
const onExit = () => {
|
|
598
|
+
clearTimeout(timeout);
|
|
599
|
+
resolve(true);
|
|
600
|
+
};
|
|
601
|
+
const timeout = setTimeout(() => {
|
|
602
|
+
child.removeListener("exit", onExit);
|
|
603
|
+
resolve(false);
|
|
604
|
+
}, timeoutMs);
|
|
605
|
+
child.once("exit", onExit);
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
function signalProcess(pid, expectedIdentity, signal) {
|
|
609
|
+
if (!processMatchesIdentity(pid, expectedIdentity))
|
|
610
|
+
return false;
|
|
611
|
+
try {
|
|
612
|
+
process.kill(pid, signal);
|
|
613
|
+
return true;
|
|
614
|
+
}
|
|
615
|
+
catch (error) {
|
|
616
|
+
if (error.code === "ESRCH")
|
|
617
|
+
return false;
|
|
618
|
+
throw error;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
function processMatchesIdentity(pid, expectedIdentity) {
|
|
622
|
+
return processIsRunning(pid) && readProcessStartIdentity(pid) === expectedIdentity;
|
|
623
|
+
}
|
|
624
|
+
function removeRuntimeState(configPath) {
|
|
625
|
+
removeRuntimeStateFile(runtimeStatePath(configPath));
|
|
626
|
+
}
|
|
627
|
+
function removeRuntimeStateFile(path) {
|
|
628
|
+
try {
|
|
629
|
+
unlinkSync(path);
|
|
630
|
+
}
|
|
631
|
+
catch (error) {
|
|
632
|
+
if (error.code !== "ENOENT")
|
|
633
|
+
throw error;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function writePrivateJson(path, value) {
|
|
637
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
638
|
+
chmodSync(path, 0o600);
|
|
639
|
+
}
|
|
640
|
+
//# sourceMappingURL=runtime-process.js.map
|