specrails-desktop 2.42.1 → 2.43.0
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/docs/ci-cd.md +1 -1
- package/docs/codex.md +2 -2
- package/docs/customizing.md +1 -1
- package/docs/gemini.md +3 -3
- package/docs/getting-started.md +1 -1
- package/docs/platforms/windows-parity.md +1 -1
- package/package.json +1 -1
- package/server/dist/code-explorer-router.js +1 -1
- package/server/dist/core-package.js +7 -4
- package/server/dist/file-provenance.js +15 -10
- package/server/dist/git-diagnostics.js +2 -2
- package/server/dist/pr-publisher.js +1 -1
- package/server/dist/project-git.js +1 -1
- package/server/dist/setup-manager.js +7 -1
- package/server/dist/terminal-manager.js +20 -0
package/docs/ci-cd.md
CHANGED
|
@@ -42,7 +42,7 @@ The macOS job retains `artifacts/chromium-signing/` as the `chromium-signing-mac
|
|
|
42
42
|
## Credentials and external requirements
|
|
43
43
|
|
|
44
44
|
- `RELEASE_PAT` must be authorized for release-please's pull requests and releases. It also permits tag pushes to trigger publication workflows; events created with the repository's ordinary `GITHUB_TOKEN` generally do not trigger another workflow.
|
|
45
|
-
- `NPM_TOKEN` must authenticate an account with publishing permission for `specrails-desktop
|
|
45
|
+
- Publication uses one of two credentials. When the `NPM_TOKEN` secret is set, it must authenticate an account with publishing permission for `specrails-desktop` (npm now requires such tokens to be granular with *bypass 2FA*, and they expire within 90 days); the workflow checks authentication and gives an explicit error before publishing. When the secret is empty, the job publishes through npm **trusted publishing**: it installs npm 11, removes the empty token line that `setup-node` writes, and lets npm exchange the job's OIDC identity (`id-token: write`) for a short-lived publish grant. That path only works once the package's *Trusted publisher* on npmjs.com names this repository (`fjpulidop/specrails-desktop`) and workflow file (`npm-publish.yml`, no environment); registry configuration is never changed from here. Provenance does **not** grant publishing permission in either mode. An npm `E404` during publication is npm's answer to an unauthorized publish and does not identify which token or permission failed; an `E403` mentioning two-factor authentication means the stored token lacks *bypass 2FA*. [npm publishing and provenance](https://docs.github.com/en/actions/tutorials/publish-packages/publish-nodejs-packages).
|
|
46
46
|
- Existing Apple signing/notarization and Tauri updater signing secrets remain required. Installer build steps retain their platform-specific checks.
|
|
47
47
|
- Hostinger credentials must support **explicit FTPS**, a valid TLS certificate, uploads and `RNFR`/`RNTO` in the release directory. There is no plaintext FTP fallback. A failed transfer or rename fails the workflow before old files are removed. [FTP action protocol and security options](https://github.com/SamKirkland/FTP-Deploy-Action/blob/v4.3.6/action.yml).
|
|
48
48
|
|
package/docs/codex.md
CHANGED
|
@@ -46,9 +46,9 @@ binary isn't on `PATH`; it shows install commands if you click "More info".
|
|
|
46
46
|
The first provider you select becomes the project default.
|
|
47
47
|
4. Submit. The app writes `.specrails/install-config.yaml` (with
|
|
48
48
|
`provider: codex` and `tier: quick` as YAML keys) and spawns
|
|
49
|
-
`npx --yes --prefer-online specrails-core@^
|
|
49
|
+
`npx --yes --prefer-online specrails-core@^5.1.0 init --yes --from-config <file>`
|
|
50
50
|
— the provider and tier live in the YAML, not as CLI flags. (The app
|
|
51
|
-
pins `specrails-core@^
|
|
51
|
+
pins `specrails-core@^5.1.0`; that floor is the version that ships the
|
|
52
52
|
current provider targets, including Kimi and the Codex skill set.) The install
|
|
53
53
|
produces:
|
|
54
54
|
- `.codex/config.toml` — model, reasoning effort, sandbox mode, and
|
package/docs/customizing.md
CHANGED
|
@@ -148,7 +148,7 @@ Most settings live in the UI. A few app-level switches are env-only because they
|
|
|
148
148
|
|
|
149
149
|
| Variable | Effect |
|
|
150
150
|
|----------|--------|
|
|
151
|
-
| `SPECRAILS_CORE_BIN` | Override the `specrails-core` binary (default: `npx --yes --prefer-online specrails-core@^
|
|
151
|
+
| `SPECRAILS_CORE_BIN` | Override the `specrails-core` binary (default: `npx --yes --prefer-online specrails-core@^5.1.0` — the 5.1.0 floor is the release that quotes the command path in its Windows shell runner, so OpenSpec init works from an install directory with spaces; it is also the version bundled with the app) |
|
|
152
152
|
| `SPECRAILS_TECH_URL` | Override the specrails-tech proxy base URL |
|
|
153
153
|
| `SPECRAILS_AGENTS_SECTION=false` | Hide the Agents section from every project |
|
|
154
154
|
| `SPECRAILS_PLUGINS_SECTION=false` | Hide the Integrations section from every project |
|
package/docs/gemini.md
CHANGED
|
@@ -24,11 +24,11 @@ Specrails supports **four AI providers**: Anthropic's
|
|
|
24
24
|
|---|---|---|
|
|
25
25
|
| `gemini` CLI ≥ 0.11.0 | Earlier versions lack `--output-format stream-json` + headless `--resume`, which the app relies on | `npm i -g @google/gemini-cli` · check with `gemini --version` |
|
|
26
26
|
| A Gemini API key | The app spawns Gemini headlessly, so it needs non-interactive auth | Set `GEMINI_API_KEY` to a paid Gemini Developer API key from [Google AI Studio](https://aistudio.google.com/apikey) |
|
|
27
|
-
| `specrails-core` ≥ 4.8.0 in the project | 4.8.0 ships the Gemini provider target (`.gemini/` commands + agents) that the rails pipeline needs | The current Add-Project install flow uses `specrails-core@^
|
|
27
|
+
| `specrails-core` ≥ 4.8.0 in the project | 4.8.0 ships the Gemini provider target (`.gemini/` commands + agents) that the rails pipeline needs | The current Add-Project install flow uses `specrails-core@^5.1.0` automatically |
|
|
28
28
|
| `git`, `node`, `npm`, `npx` | Same as Claude — needed for `specrails-core init` | Use your usual installer |
|
|
29
29
|
|
|
30
30
|
> **Two different minimums.** The `gemini` **binary** floor is **0.11.0**.
|
|
31
|
-
> The current Desktop `specrails-core` **package** floor is **
|
|
31
|
+
> The current Desktop `specrails-core` **package** floor is **5.1.0** (a
|
|
32
32
|
> single shared version for all providers, including Kimi). They are separate
|
|
33
33
|
> things — the binary on your
|
|
34
34
|
> machine vs. the artifacts installed into the project.
|
|
@@ -52,7 +52,7 @@ the Gemini provider checkbox with a "not found" hint when the binary isn't on
|
|
|
52
52
|
project](#running-multiple-providers-in-one-project)). The first provider
|
|
53
53
|
you select becomes the project default.
|
|
54
54
|
4. Submit. The app writes `.specrails/install-config.yaml` and spawns
|
|
55
|
-
`npx --yes --prefer-online specrails-core@^
|
|
55
|
+
`npx --yes --prefer-online specrails-core@^5.1.0 init --yes --from-config <file>`.
|
|
56
56
|
The install produces the `.gemini/` artifacts (commands + `sr-*` agents),
|
|
57
57
|
plus a `GEMINI.md` instructions file.
|
|
58
58
|
|
package/docs/getting-started.md
CHANGED
|
@@ -61,7 +61,7 @@ specrails-desktop list # verify
|
|
|
61
61
|
The setup wizard runs automatically. Three steps:
|
|
62
62
|
|
|
63
63
|
1. **Configure** — choose which agents to install (the baseline trio `sr-architect`, `sr-developer`, `sr-reviewer` is always selected; optional agents like Test Writer or Security Reviewer are opt-in). Pick a model preset (Balanced / Budget / Max) and optionally override the model per agent.
|
|
64
|
-
2. **Install** — the app runs the installer (`npx --yes --prefer-online specrails-core@^
|
|
64
|
+
2. **Install** — the app runs the installer (`npx --yes --prefer-online specrails-core@^5.1.0 init --yes --from-config <config>`) non-interactively and streams the output live. The version is pinned to the core release the app ships with — it never silently jumps to a new major.
|
|
65
65
|
3. **Done** — a summary tells you how many agents and commands landed. Click **Continue to project**.
|
|
66
66
|
|
|
67
67
|
That's the whole onboarding. No tier picker, no second wizard. You can manage agents and their per-agent models later from the **Agents** page (Profiles tab).
|
|
@@ -26,7 +26,7 @@ This audit covers the `codex/multi-repo-projects` branch, including mission stee
|
|
|
26
26
|
|
|
27
27
|
## Automated gates
|
|
28
28
|
|
|
29
|
-
`.github/workflows/windows-parity.yml` runs a `windows-parity` job on `windows-latest` (x64) for every
|
|
29
|
+
`.github/workflows/windows-parity.yml` runs a `windows-parity` job on `windows-latest` (x64) for every pull request and push to `main`, adding `windows-11-arm` on the `main` pushes. It installs dependencies, checks TypeScript, runs release/PTY helper regression tests, native filesystem/process tests (retried once, they drive real processes), the application build, native host build/tests, and four real native fixtures: WebView2 capture/selection, authentication popups, mission window handoff, and browser multiwindow transfer/parking. It is deliberately a separate workflow from `ci.yml`: `release.yml` waits for CI only, so a slow or flaky Windows run never delays the release PR, while Desktop Release still builds and installs on Windows before publication. The jsdom client suite is not repeated on Windows; it runs on Linux in CI. The bundled-core matrix in `ci.yml` exercises Windows junction/copy relocation for providers.
|
|
30
30
|
|
|
31
31
|
`.github/workflows/desktop-release.yml` builds the real installers and runs `scripts/smoke-windows-installers.ps1` before publishing artifacts. For each NSIS and MSI package, the script installs into a temporary path containing spaces and drives `scripts/smoke-installed-windows.mjs` with the installed Node runtime. The driver uses an isolated user profile and tests:
|
|
32
32
|
|
package/package.json
CHANGED
|
@@ -139,7 +139,7 @@ async function gitIgnoredSet(projectPath, relPaths, maxDurationMs = Number.POSIT
|
|
|
139
139
|
let stdout = '';
|
|
140
140
|
let settled = false;
|
|
141
141
|
let overflow = false;
|
|
142
|
-
const child = (0, child_process_1.spawn)('git', ['check-ignore', '--stdin', '-z'], { cwd: projectPath, stdio: ['pipe', 'pipe', 'ignore'], env: file_provenance_1.
|
|
142
|
+
const child = (0, child_process_1.spawn)('git', ['check-ignore', '--stdin', '-z'], { cwd: projectPath, stdio: ['pipe', 'pipe', 'ignore'], env: (0, file_provenance_1.gitExecEnv)() });
|
|
143
143
|
const finish = (complete) => { if (!settled) {
|
|
144
144
|
settled = true;
|
|
145
145
|
resolve({ stdout, complete: complete && !overflow });
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CORE_PACKAGE_SPEC = void 0;
|
|
4
|
-
/** Online fallback selects the supported Core 5 lifecycle.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
|
|
4
|
+
/** Online fallback selects the supported Core 5 lifecycle; 5.1.0 is the floor
|
|
5
|
+
* because it quotes the command path in its Windows shell runner (OpenSpec init
|
|
6
|
+
* from an install directory with spaces). The desktop bundles the same version.
|
|
7
|
+
* Bundled Core 4 remains readable for installed apps; the runtime resolver
|
|
8
|
+
* selects the newest usable compatible package and never downgrades a newer
|
|
9
|
+
* activated framework. */
|
|
10
|
+
exports.CORE_PACKAGE_SPEC = 'specrails-core@^5.1.0';
|
|
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.STORY_EXCERPT_MAX_BYTES =
|
|
6
|
+
exports.STORY_EXCERPT_MAX_BYTES = void 0;
|
|
7
|
+
exports.gitExecEnv = gitExecEnv;
|
|
7
8
|
exports.resolveHeadSha = resolveHeadSha;
|
|
8
9
|
exports.listUntracked = listUntracked;
|
|
9
10
|
exports.snapshotWorkingTree = snapshotWorkingTree;
|
|
@@ -34,9 +35,13 @@ const GIT_MAX_BUFFER = 16 * 1024 * 1024;
|
|
|
34
35
|
// the on-demand diff patches beyond this cap are skipped (the UI shows "diff
|
|
35
36
|
// unavailable" for them). Mirrors the existing large-job warn threshold (50).
|
|
36
37
|
const MAX_PATCH_FILES = 50;
|
|
37
|
-
// Exported: project-git.ts (the Agent-Mode git bar)
|
|
38
|
-
// git calls and
|
|
39
|
-
|
|
38
|
+
// Exported: project-git.ts (the Agent-Mode git bar), the code explorer and the
|
|
39
|
+
// git diagnostics run the same cwd-scoped git calls and need the identical
|
|
40
|
+
// hostile-repo hardening. Computed PER CALL, never at module load: the startup
|
|
41
|
+
// path resolver prepends the bundled git directory to process.env.PATH after
|
|
42
|
+
// this module was imported, and a snapshot taken earlier could not find git in
|
|
43
|
+
// a packaged app without a system git — every guard then failed closed.
|
|
44
|
+
function gitExecEnv() {
|
|
40
45
|
// Inherit the parent env but STRIP git-location vars. If the app process (or a
|
|
41
46
|
// parent) ever exports GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE, every cwd-scoped
|
|
42
47
|
// git call below would silently operate on that repo instead of the project —
|
|
@@ -61,7 +66,7 @@ exports.GIT_EXEC_ENV = (() => {
|
|
|
61
66
|
env.GIT_CONFIG_NOSYSTEM = '1';
|
|
62
67
|
env.GIT_CONFIG_PARAMETERS = "'core.fsmonitor=false' 'core.hooksPath=/dev/null' 'protocol.ext.allow=user'";
|
|
63
68
|
return env;
|
|
64
|
-
}
|
|
69
|
+
}
|
|
65
70
|
/** Resolve the current HEAD commit SHA. Best-effort: '' on any git failure
|
|
66
71
|
* (no repo, unborn HEAD, no git, timeout). */
|
|
67
72
|
function resolveHeadSha(cwd) {
|
|
@@ -72,7 +77,7 @@ function resolveHeadSha(cwd) {
|
|
|
72
77
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
73
78
|
timeout: GIT_TIMEOUT_MS,
|
|
74
79
|
maxBuffer: GIT_MAX_BUFFER,
|
|
75
|
-
env:
|
|
80
|
+
env: gitExecEnv(),
|
|
76
81
|
});
|
|
77
82
|
return out.trim();
|
|
78
83
|
}
|
|
@@ -90,7 +95,7 @@ function listUntracked(cwd) {
|
|
|
90
95
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
91
96
|
timeout: GIT_TIMEOUT_MS,
|
|
92
97
|
maxBuffer: GIT_MAX_BUFFER,
|
|
93
|
-
env:
|
|
98
|
+
env: gitExecEnv(),
|
|
94
99
|
});
|
|
95
100
|
return out.split('\0').filter((p) => p.length > 0);
|
|
96
101
|
}
|
|
@@ -107,7 +112,7 @@ function snapshotWorkingTree(cwd) {
|
|
|
107
112
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
108
113
|
timeout: GIT_TIMEOUT_MS,
|
|
109
114
|
maxBuffer: GIT_MAX_BUFFER,
|
|
110
|
-
env:
|
|
115
|
+
env: gitExecEnv(),
|
|
111
116
|
});
|
|
112
117
|
ref = out.trim();
|
|
113
118
|
}
|
|
@@ -141,7 +146,7 @@ function diffAgainstSnapshot(cwd, snapshotRef, untrackedBefore, baseSha) {
|
|
|
141
146
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
142
147
|
timeout: GIT_TIMEOUT_MS,
|
|
143
148
|
maxBuffer: GIT_MAX_BUFFER,
|
|
144
|
-
env:
|
|
149
|
+
env: gitExecEnv(),
|
|
145
150
|
});
|
|
146
151
|
}
|
|
147
152
|
catch {
|
|
@@ -309,7 +314,7 @@ function collectDiffPatches(cwd, snapshotRef, diff, baseSha) {
|
|
|
309
314
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
310
315
|
maxBuffer: MAX_PATCH_BYTES + 64 * 1024,
|
|
311
316
|
timeout: GIT_TIMEOUT_MS,
|
|
312
|
-
env:
|
|
317
|
+
env: gitExecEnv(),
|
|
313
318
|
});
|
|
314
319
|
}
|
|
315
320
|
catch (err) {
|
|
@@ -17,7 +17,7 @@ const win_spawn_1 = require("./util/win-spawn");
|
|
|
17
17
|
* SAFETY: the command list is a FIXED ALLOWLIST — the caller only picks an
|
|
18
18
|
* action name, never arguments, so no flag/ref/path can be smuggled in. Every
|
|
19
19
|
* command is read-only (no push/commit/checkout/pr-create). git runs with the
|
|
20
|
-
* hardened `
|
|
20
|
+
* hardened `gitExecEnv()` (hostile-repo config stripped, prompts disabled); gh
|
|
21
21
|
* runs with the normal env so it finds the user's `~/.config/gh` auth and the
|
|
22
22
|
* bundled `gh` on PATH.
|
|
23
23
|
*/
|
|
@@ -43,7 +43,7 @@ const OUTPUT_CAP = 8_000;
|
|
|
43
43
|
const defaultExec = (cmd, args, cwd) => new Promise((resolve) => {
|
|
44
44
|
// git: hardened, cwd-pinned env. gh: user env (needs ~/.config/gh auth) with
|
|
45
45
|
// the Windows shell-critical vars guaranteed present.
|
|
46
|
-
const env = cmd === 'gh' ? (0, win_spawn_1.windowsSpawnEnv)() : file_provenance_1.
|
|
46
|
+
const env = cmd === 'gh' ? (0, win_spawn_1.windowsSpawnEnv)() : (0, file_provenance_1.gitExecEnv)();
|
|
47
47
|
(0, child_process_1.execFile)(cmd, args, { cwd, env, timeout: 15_000, maxBuffer: 8 * 1024 * 1024, windowsHide: true }, (err, stdout, stderr) => {
|
|
48
48
|
const code = err && typeof err.code === 'number'
|
|
49
49
|
? err.code
|
|
@@ -35,7 +35,7 @@ function createBoundedExec(timeoutMs = exports.PR_COMMAND_TIMEOUT_MS) {
|
|
|
35
35
|
// `windowsSpawnEnv()` backfills SystemRoot / ComSpec / USERPROFILE, which a
|
|
36
36
|
// GUI-launched / pkg-stripped Windows sidecar can lack — without them the
|
|
37
37
|
// `git`/`gh` child (and the `sh -c` the `!gh …` credential helper runs) can
|
|
38
|
-
// fail to start. NOT `
|
|
38
|
+
// fail to start. NOT `gitExecEnv()`: that hardened env disables credentials
|
|
39
39
|
// (GIT_ASKPASS=echo / GIT_TERMINAL_PROMPT=0), which the push must keep. No-op
|
|
40
40
|
// on POSIX (returns process.env), so mac/Linux behaviour is byte-identical.
|
|
41
41
|
const env = (0, win_spawn_1.windowsSpawnEnv)();
|
|
@@ -21,7 +21,7 @@ const GIT_MAX_BUFFER = 4 * 1024 * 1024;
|
|
|
21
21
|
const COMMIT_SHA_RE = /^[0-9a-f]{40,64}$/i;
|
|
22
22
|
function git(repoDir, args) {
|
|
23
23
|
return new Promise((resolve, reject) => {
|
|
24
|
-
(0, child_process_1.execFile)('git', args, { cwd: repoDir, env: file_provenance_1.
|
|
24
|
+
(0, child_process_1.execFile)('git', args, { cwd: repoDir, env: (0, file_provenance_1.gitExecEnv)(), timeout: GIT_TIMEOUT_MS, maxBuffer: GIT_MAX_BUFFER, windowsHide: true }, (err, stdout, stderr) => {
|
|
25
25
|
if (err) {
|
|
26
26
|
const detail = String(stderr || err.message).trim();
|
|
27
27
|
reject(new Error(detail));
|
|
@@ -772,7 +772,13 @@ class SetupManager {
|
|
|
772
772
|
providers: list,
|
|
773
773
|
continueOnError: true,
|
|
774
774
|
onProviderStart: (p) => emit(p, 'running'),
|
|
775
|
-
onProviderResult: (r) =>
|
|
775
|
+
onProviderResult: (r) => {
|
|
776
|
+
// The WS event carries the reason to the app; the server log must too,
|
|
777
|
+
// or a packaged-app failure (installer smoke, support bundle) is blind.
|
|
778
|
+
if (!r.ok)
|
|
779
|
+
console.warn(`[SetupManager] silent assemble: ${r.provider} failed for ${projectId}: ${r.error ?? 'unknown error'}`);
|
|
780
|
+
emit(r.provider, r.ok ? 'done' : 'failed', r.error);
|
|
781
|
+
},
|
|
776
782
|
})
|
|
777
783
|
.then((results) => {
|
|
778
784
|
const failed = results.filter((r) => !r.ok).map((r) => r.provider);
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.TerminalManager = exports.TERMINAL_EARLY_EXIT_MS = exports.TerminalSpawnError = exports.TerminalNameInvalidError = exports.TerminalNotFoundError = exports.TerminalLimitExceededError = exports.RingBuffer = exports.TERMINAL_DEFAULT_ROWS = exports.TERMINAL_DEFAULT_COLS = exports.TERMINAL_NAME_MAX = exports.TERMINAL_MAX_PER_PROJECT = exports.TERMINAL_KILL_GRACE_MS = exports.TERMINAL_SCROLLBACK_BYTES = void 0;
|
|
7
|
+
exports.killTerminalTree = killTerminalTree;
|
|
7
8
|
exports.resolveShell = resolveShell;
|
|
8
9
|
exports.resolveShellFor = resolveShellFor;
|
|
9
10
|
exports.shellArgs = shellArgs;
|
|
@@ -20,6 +21,21 @@ const terminal_marks_store_1 = require("./terminal-marks-store");
|
|
|
20
21
|
const terminal_shell_integration_1 = require("./terminal-shell-integration");
|
|
21
22
|
exports.TERMINAL_SCROLLBACK_BYTES = 262_144;
|
|
22
23
|
exports.TERMINAL_KILL_GRACE_MS = 2_000;
|
|
24
|
+
/**
|
|
25
|
+
* Windows: node-pty ends a ConPTY session by enumerating the pseudo console's
|
|
26
|
+
* processes through a helper it starts with `process.execPath`. In the
|
|
27
|
+
* packaged sidecar that path is the sidecar binary itself, so the helper never
|
|
28
|
+
* runs, only the shell is terminated and whatever the shell started (a dev
|
|
29
|
+
* server, a watcher) survives the closed terminal. Kill the shell's process
|
|
30
|
+
* tree ourselves (`taskkill /T /F` through tree-kill). No-op elsewhere: the
|
|
31
|
+
* POSIX pty closes its process group.
|
|
32
|
+
*/
|
|
33
|
+
function killTerminalTree(pid, platform = process.platform, kill = win_spawn_1.treeKillSafe) {
|
|
34
|
+
if (platform !== 'win32' || !pid || !Number.isInteger(pid) || pid <= 0)
|
|
35
|
+
return false;
|
|
36
|
+
kill(pid, 'SIGKILL');
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
23
39
|
exports.TERMINAL_MAX_PER_PROJECT = 10;
|
|
24
40
|
exports.TERMINAL_NAME_MAX = 64;
|
|
25
41
|
exports.TERMINAL_DEFAULT_COLS = 80;
|
|
@@ -455,6 +471,7 @@ class TerminalManager {
|
|
|
455
471
|
async shutdown() {
|
|
456
472
|
const all = Array.from(this.sessions.values());
|
|
457
473
|
for (const s of all) {
|
|
474
|
+
killTerminalTree(s.pty.pid);
|
|
458
475
|
try {
|
|
459
476
|
s.pty.kill('SIGTERM');
|
|
460
477
|
}
|
|
@@ -462,6 +479,7 @@ class TerminalManager {
|
|
|
462
479
|
}
|
|
463
480
|
await new Promise((r) => setTimeout(r, exports.TERMINAL_KILL_GRACE_MS));
|
|
464
481
|
for (const s of Array.from(this.sessions.values())) {
|
|
482
|
+
killTerminalTree(s.pty.pid);
|
|
465
483
|
try {
|
|
466
484
|
s.pty.kill('SIGKILL');
|
|
467
485
|
}
|
|
@@ -540,12 +558,14 @@ class TerminalManager {
|
|
|
540
558
|
return;
|
|
541
559
|
this.detachFromRegistry(s);
|
|
542
560
|
if (!s.exited) {
|
|
561
|
+
killTerminalTree(s.pty.pid);
|
|
543
562
|
try {
|
|
544
563
|
s.pty.kill('SIGTERM');
|
|
545
564
|
}
|
|
546
565
|
catch { /* ignore */ }
|
|
547
566
|
s.killTimer = setTimeout(() => {
|
|
548
567
|
if (!s.exited) {
|
|
568
|
+
killTerminalTree(s.pty.pid);
|
|
549
569
|
try {
|
|
550
570
|
s.pty.kill('SIGKILL');
|
|
551
571
|
}
|