pi-simple-background-tasks 0.2.0 → 0.3.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/FORK-CHANGES.md +10 -3
- package/README.md +19 -9
- package/package.json +3 -2
- package/src/core/common.ts +26 -207
- package/src/core/detached-spawn.ts +317 -0
- package/src/core/registry.ts +586 -1203
- package/src/core/task-cleanup.ts +6 -0
- package/src/core/windows-taskkill.ts +4 -11
- package/src/core/windows-wmi-spawn.ts +192 -0
- package/src/extension.ts +34 -180
- package/src/core/reload-shell-owner.ts +0 -1512
package/FORK-CHANGES.md
CHANGED
|
@@ -12,11 +12,12 @@ This package is a **KISS fork** of
|
|
|
12
12
|
|
|
13
13
|
## Divergence summary
|
|
14
14
|
|
|
15
|
-
Relative to the 2.6.9 source, the fork changes **
|
|
15
|
+
Relative to the 2.6.9 source, the fork changes **69 files (34,235 lines removed, 5,275 added)**, ending with **14 source files** plus a 3-file `node:test` platform suite:
|
|
16
16
|
|
|
17
17
|
| Subsystem (as in 2.6.9) | Fork |
|
|
18
18
|
| --- | --- |
|
|
19
|
-
| Background-task core: `registry`, `common`, `extension-api`, `durable-fs`, `task-durable`, `shell-policy`, `windows-taskkill`, `
|
|
19
|
+
| Background-task core: `registry`, `common`, `extension-api`, `durable-fs`, `task-durable`, `shell-policy`, `windows-taskkill`, `ui/background-tasks-manager` | **Kept, simplified** |
|
|
20
|
+
| `reload-shell-owner` (lease/nonce handoff protocol for reload survival) | **Removed** — replaced by the detached-spawn design below |
|
|
20
21
|
| Fusion workflows (`src/core/fusion/*` + `fusion-extension.ts` + `fusion-child-extension.ts` + `ui/fusion-model-selector.ts`) | **Removed** (~15k lines) |
|
|
21
22
|
| Delegate (`src/core/delegate/*` + `delegate-extension.ts` + `delegate-child-extension.ts`) | **Removed** (~5k lines) |
|
|
22
23
|
| Claude/Anthropic impersonation (`anthropic-attribution*.ts`) | **Removed** (~3.7k lines) |
|
|
@@ -31,9 +32,15 @@ Relative to the 2.6.9 source, the fork changes **63 files (31,260 lines removed,
|
|
|
31
32
|
|
|
32
33
|
## What the fork adds / changes
|
|
33
34
|
|
|
35
|
+
- **Single detached task path, survival without flags** — every task spawns fully detached: on POSIX in its own process group (no pipes, no in-process ownership); on Windows as an outer process created **by the WMI service** (`Win32_Process.Create`), because direct children of the Pi process — no matter how detached — die together with it through job/console-session teardown (reproduced on a real Windows 11 VM with `taskkill /F` on the Pi process; WMI-created processes survive it). Ownership facts (pid, pgid, wrapper-written `<id>.exit` marker) live in the on-disk metadata and the extension holds no output file descriptors: the spawned command itself redirects output and the wrapper records the exit code. Each activation adopts still-running records from disk; a same-process `/reload` or a full Pi restart never kills a running task, and a Pi session shutdown kills it deliberately. Upstream's `reload-shell-owner` lease/handoff protocol (`--survive-reload`) was removed along with its ~1.5k lines: there is exactly one launch path and nothing to negotiate per task.
|
|
36
|
+
|
|
37
|
+
- **One Windows kill: `taskkill /T /F`** — the old two-phase ladder (graceful `taskkill /T` window-close termination, then `/F` escalation after a grace timer) is gone: console applications do not honor window-close termination, so the graceful phase could neither stop anything on Windows nor leave time to escalate during a session shutdown (the Pi process would exit first and the un-ref'd escalation timer would die with it). One forced tree kill, outcome checked once. Cap/timeout notices are appended after the tree is terminated because the child's own `1>` redirection holds the output file locked until then.
|
|
38
|
+
|
|
39
|
+
- **Platform test suite** (`npm test`) — `tests/registry.test.ts` runs the registry against real processes on the platform it is executed on, with the node:test runner through tsx (no new dependencies): completion/exit markers, quoting, timeout and cap kills, detachment across a genuine parent exit, adoption of a dead activation's records, kill of adopted tasks with notification, and no-adoption of terminal records. The two launch races it found — reaping a task while its WMI spawn is still in flight, and flood bytes overwriting cap/timeout notices — had product fixes (see the git history). All tests pass on Linux (8 run, 1 windows-only skip) and on a real Windows 11 VM (8 run, 1 posix-only skip).
|
|
40
|
+
|
|
34
41
|
- **`/clean` command + `bg_clean` tool** — delete finished tasks (`session` / `all` / `<id>`). New file `src/core/task-cleanup.ts`; empty run directories are removed after cleanup (tasks live under `~/.pi/agent/tasks`).
|
|
35
42
|
- **Task runtime moved to `~/.pi/agent/tasks`** with the PI extension-config pattern (`pi-background-tasks.json`, `tasksDir`).
|
|
36
|
-
- **KISS packaging** — single entry point `src/extension.ts`, no `dist/`, no tests
|
|
43
|
+
- **KISS packaging** — single entry point `src/extension.ts`, no `dist/`, no docs tree; one `node:test` platform suite under `tests/` (`npm test`, not shipped to npm); fork identity in `package.json`.
|
|
37
44
|
- **Task manager UI** — opens top-right as a fixed 96-column modal (96 % width left the box stuck left on wide terminals); `d` deletes the selected task; both shortcuts registered (Shift↓ / Ctrl+Alt+B); footer options contextual to the selected task; empty-state footer shows only sensible actions; task detail adapts to terminal height (modal title dropped); **f** toggles fullscreen (gated on terminal size), **w** toggles output wrap.
|
|
38
45
|
- **`--agent` flag** removed from `/bg` (stale).
|
|
39
46
|
- **Lazy module loader simplified.**
|
package/README.md
CHANGED
|
@@ -7,19 +7,22 @@ A KISS fork of [pi-background-tasks](https://github.com/ismailsaleekh/pi-backgro
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- **Run long jobs without blocking**: start a named shell command in the background, keep talking to Pi, and get a completion notification in chat when it finishes.
|
|
10
|
-
- **Durable tasks**: task state is persisted under `~/.pi/agent/tasks`, so it
|
|
10
|
+
- **Durable tasks**: task state is persisted under `~/.pi/agent/tasks`, and every task spawns detached, so a same-process `/reload` or a full Pi restart never kills a running task — the next activation adopts it from disk.
|
|
11
|
+
- **Deliberate lifecycle**: a Pi session shutdown kills running tasks by design; `/reload`, crashes and restarts never do.
|
|
11
12
|
- **Task manager overlay**: a TUI panel lists running and recent tasks with status and bounded output (default **Shift↓**); **f** toggles fullscreen, **w** wraps long output lines.
|
|
12
|
-
- **
|
|
13
|
-
- **Cross-platform**: POSIX shell and Windows (process-group kill).
|
|
13
|
+
- **Cross-platform**: POSIX (own process group, `-pgid` kill authority) and Windows (detached via a WMI-created outer process, `taskkill /T /F` tree ownership, `cmd`-runner exit markers).
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
#
|
|
19
|
-
pi install
|
|
18
|
+
# Default install (npm)
|
|
19
|
+
pi install npm:pi-simple-background-tasks
|
|
20
|
+
|
|
21
|
+
# Project-local install (npm)
|
|
22
|
+
pi install -l npm:pi-simple-background-tasks
|
|
20
23
|
|
|
21
|
-
#
|
|
22
|
-
pi install git:github.com/jlsalvador/pi-simple-background-tasks@main
|
|
24
|
+
# Or from the git main branch
|
|
25
|
+
pi install git:github.com/jlsalvador/pi-simple-background-tasks@main
|
|
23
26
|
```
|
|
24
27
|
|
|
25
28
|
## Quick start
|
|
@@ -30,7 +33,7 @@ pi install git:github.com/jlsalvador/pi-simple-background-tasks@main -l
|
|
|
30
33
|
/bg --name "Typecheck watch" npm run typecheck -- --watch
|
|
31
34
|
```
|
|
32
35
|
|
|
33
|
-
`/bg` starts a tracked shell
|
|
36
|
+
`/bg` starts a tracked shell command and returns the task id plus output path. User-launched `/bg` tasks notify in the UI but do not automatically wake a follow-up model turn. Tasks survive `/reload` and Pi restarts unconditionally; quitting the agent kills them.
|
|
34
37
|
|
|
35
38
|
2. List tasks or open the task manager:
|
|
36
39
|
|
|
@@ -58,7 +61,7 @@ pi install git:github.com/jlsalvador/pi-simple-background-tasks@main -l
|
|
|
58
61
|
|
|
59
62
|
| Command | Purpose |
|
|
60
63
|
| --- | --- |
|
|
61
|
-
| `/bg [--
|
|
64
|
+
| `/bg [--name "name"] <command>` | Start a tracked shell command. |
|
|
62
65
|
| `/jobs` | List running and recent tasks. |
|
|
63
66
|
| `/tasks [id]` | Open the task manager overlay. |
|
|
64
67
|
| `/logs <id> [maxBytes]` | Show bounded output from a task. |
|
|
@@ -94,8 +97,15 @@ Example:
|
|
|
94
97
|
npm install
|
|
95
98
|
npm run check # tsc --noEmit && biome check .
|
|
96
99
|
npm run fix # biome check --write .
|
|
100
|
+
npm test # registry platform suite (tests/), run per-platform
|
|
97
101
|
```
|
|
98
102
|
|
|
103
|
+
`npm test` verifies the detached-task promises (survival across a real parent
|
|
104
|
+
exit, adoption, kills) with `node:test` against processes it genuinely spawns.
|
|
105
|
+
Run it on the platform whose behavior you want to verify: on POSIX it exercises
|
|
106
|
+
process groups and the group kill; on Windows it exercises WMI-created tasks,
|
|
107
|
+
the cmd runner and `taskkill /T /F`. Only the platform-specific pieces skip.
|
|
108
|
+
|
|
99
109
|
## License
|
|
100
110
|
|
|
101
111
|
ISC — see [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-simple-background-tasks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Durable background shell tasks for Pi. KISS fork of pi-background-tasks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "ISC",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
],
|
|
33
33
|
"scripts": {
|
|
34
34
|
"check": "tsc --noEmit && biome check .",
|
|
35
|
-
"fix": "biome check --write ."
|
|
35
|
+
"fix": "biome check --write .",
|
|
36
|
+
"test": "tsx --test \"tests/*.test.ts\""
|
|
36
37
|
},
|
|
37
38
|
"pi": {
|
|
38
39
|
"extensions": [
|
package/src/core/common.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { accessSync, constants, statSync
|
|
1
|
+
import { accessSync, constants, statSync } from 'node:fs';
|
|
2
2
|
import { open } from 'node:fs/promises';
|
|
3
3
|
import { basename, delimiter, extname, isAbsolute, join, resolve, win32 } from 'node:path';
|
|
4
4
|
import { DEFAULT_MAX_BYTES } from '@earendil-works/pi-coding-agent';
|
|
5
|
-
import type { BackgroundTaskChildProcess } from './registry.js';
|
|
6
5
|
|
|
7
6
|
export const TASK_STATUS_VALUES = ['running', 'completed', 'failed', 'killed'] as const;
|
|
8
7
|
export const TERMINAL_TASK_STATUS_VALUES = ['completed', 'failed', 'killed'] as const;
|
|
@@ -10,7 +9,6 @@ export const TERMINAL_TASK_STATUS_VALUES = ['completed', 'failed', 'killed'] as
|
|
|
10
9
|
export type TaskStatus = (typeof TASK_STATUS_VALUES)[number];
|
|
11
10
|
export type TerminalTaskStatus = (typeof TERMINAL_TASK_STATUS_VALUES)[number];
|
|
12
11
|
export type KillKind = 'user' | 'timeout' | 'output_cap' | 'shutdown';
|
|
13
|
-
export type ReloadShellStopKind = KillKind | 'handoff_expired';
|
|
14
12
|
|
|
15
13
|
export type TerminalPublicationState = 'pending' | 'delivered' | 'abandoned';
|
|
16
14
|
export type TerminalPublicationAbandonReason =
|
|
@@ -18,178 +16,7 @@ export type TerminalPublicationAbandonReason =
|
|
|
18
16
|
| 'publisher_closed'
|
|
19
17
|
| 'gate_rejected'
|
|
20
18
|
| 'retry_exhausted'
|
|
21
|
-
| 'retention_limit'
|
|
22
|
-
| 'reload_handoff_expired';
|
|
23
|
-
|
|
24
|
-
export type ReloadSurvivalErrorCode =
|
|
25
|
-
| 'pi_bg_survive_reload_invalid'
|
|
26
|
-
| 'pi_bg_survive_reload_requires_non_agent'
|
|
27
|
-
| 'pi_bg_survive_reload_unsupported_task_kind'
|
|
28
|
-
| 'pi_bg_reload_owner_unavailable'
|
|
29
|
-
| 'pi_bg_reload_owner_protocol_incompatible'
|
|
30
|
-
| 'pi_bg_reload_owner_activation_conflict'
|
|
31
|
-
| 'pi_bg_reload_owner_stale_claim'
|
|
32
|
-
| 'pi_bg_reload_handoff_expired';
|
|
33
|
-
|
|
34
|
-
export class ReloadSurvivalError extends Error {
|
|
35
|
-
constructor(
|
|
36
|
-
readonly code: ReloadSurvivalErrorCode,
|
|
37
|
-
message: string,
|
|
38
|
-
) {
|
|
39
|
-
super(`${code}: ${message}`);
|
|
40
|
-
this.name = 'ReloadSurvivalError';
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface ReloadSurvivalSnapshotV1 {
|
|
45
|
-
schemaVersion: 'pi-background-tasks.reload-shell.v1';
|
|
46
|
-
authority: 'same-process-live-owner';
|
|
47
|
-
hostPid: number;
|
|
48
|
-
sessionId: string;
|
|
49
|
-
cwdRealpath: string;
|
|
50
|
-
launchNonce: string;
|
|
51
|
-
completionId: string;
|
|
52
|
-
spawnedAt: number;
|
|
53
|
-
childPid: number;
|
|
54
|
-
timeoutDeadlineAt?: number | undefined;
|
|
55
|
-
outputCapBytes: number;
|
|
56
|
-
posixProcessGroupId?: number | undefined;
|
|
57
|
-
windowsTreeRootPid?: number | undefined;
|
|
58
|
-
leaseGeneration: number;
|
|
59
|
-
handoffCount: number;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export interface ReloadShellIdentityV1 {
|
|
63
|
-
readonly hostPid: number;
|
|
64
|
-
readonly sessionId: string;
|
|
65
|
-
readonly cwdRealpath: string;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface ReloadShellActivationLeaseV1 {
|
|
69
|
-
readonly protocol: 'pi-background-tasks.reload-shell-owner.v1';
|
|
70
|
-
readonly hubNonce: string;
|
|
71
|
-
readonly identityKey: string;
|
|
72
|
-
readonly generation: number;
|
|
73
|
-
readonly activationNonce: string;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface ReloadShellActivationClaimV1 {
|
|
77
|
-
readonly protocol: 'pi-background-tasks.reload-shell-owner.v1';
|
|
78
|
-
readonly hubNonce: string;
|
|
79
|
-
readonly claimNonce: string;
|
|
80
|
-
readonly identity: ReloadShellIdentityV1;
|
|
81
|
-
readonly identityKey: string;
|
|
82
|
-
readonly generation: number;
|
|
83
|
-
readonly activationNonce: string;
|
|
84
|
-
readonly expiresAt?: number | undefined;
|
|
85
|
-
readonly executions: readonly ReloadableShellExecutionV1[];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export interface ReloadShellProcessV1 {
|
|
89
|
-
readonly pid?: number | undefined;
|
|
90
|
-
stdout?:
|
|
91
|
-
| {
|
|
92
|
-
on(event: 'data', listener: (data: Buffer | string) => void): unknown;
|
|
93
|
-
off?(event: 'data', listener: (data: Buffer | string) => void): unknown;
|
|
94
|
-
}
|
|
95
|
-
| null
|
|
96
|
-
| undefined;
|
|
97
|
-
stderr?:
|
|
98
|
-
| {
|
|
99
|
-
on(event: 'data', listener: (data: Buffer | string) => void): unknown;
|
|
100
|
-
off?(event: 'data', listener: (data: Buffer | string) => void): unknown;
|
|
101
|
-
}
|
|
102
|
-
| null
|
|
103
|
-
| undefined;
|
|
104
|
-
kill(signal?: NodeJS.Signals): boolean;
|
|
105
|
-
on(event: 'error', listener: (error: Error) => void): unknown;
|
|
106
|
-
on(
|
|
107
|
-
event: 'close',
|
|
108
|
-
listener: (code: number | null, signal: NodeJS.Signals | null) => void,
|
|
109
|
-
): unknown;
|
|
110
|
-
off?(event: 'error', listener: (error: Error) => void): unknown;
|
|
111
|
-
off?(
|
|
112
|
-
event: 'close',
|
|
113
|
-
listener: (code: number | null, signal: NodeJS.Signals | null) => void,
|
|
114
|
-
): unknown;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export interface ReloadShellOwnerEventSinkV1 {
|
|
118
|
-
readonly onChanged: (execution: ReloadableShellExecutionV1) => void;
|
|
119
|
-
readonly onTerminal: (execution: ReloadableShellExecutionV1) => void;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export type ReloadShellNotificationState = 'disabled' | 'pending' | 'sending' | 'delivered';
|
|
123
|
-
|
|
124
|
-
export interface ReloadableShellExecutionV1 {
|
|
125
|
-
readonly protocol: 'pi-background-tasks.reload-shell-owner.v1';
|
|
126
|
-
readonly launchNonce: string;
|
|
127
|
-
readonly completionId: string;
|
|
128
|
-
readonly task: BgTask;
|
|
129
|
-
child: ReloadShellProcessV1 | undefined;
|
|
130
|
-
outputStream: WriteStream | undefined;
|
|
131
|
-
readonly spawnedAt: number;
|
|
132
|
-
readonly timeoutDeadlineAt?: number | undefined;
|
|
133
|
-
readonly outputCapBytes: number;
|
|
134
|
-
readonly terminal: Promise<BgTask>;
|
|
135
|
-
readonly requestStop: (kind: ReloadShellStopKind, reason?: string) => Promise<BgTask>;
|
|
136
|
-
phase: 'starting' | 'running' | 'stop_requested' | 'finalizing' | 'terminal' | 'released';
|
|
137
|
-
closeObservation?:
|
|
138
|
-
| {
|
|
139
|
-
code: number | null;
|
|
140
|
-
signal: NodeJS.Signals | null;
|
|
141
|
-
observedAt: number;
|
|
142
|
-
}
|
|
143
|
-
| undefined;
|
|
144
|
-
admissionCommitted: boolean;
|
|
145
|
-
notificationState: ReloadShellNotificationState;
|
|
146
|
-
readonly commitInitialMetadata: (signal?: AbortSignal) => Promise<void>;
|
|
147
|
-
readonly failAdmission: (error: Error) => void;
|
|
148
|
-
readonly setOwnerEventSink: (sink: ReloadShellOwnerEventSinkV1 | undefined) => void;
|
|
149
|
-
readonly markAdmissionCommitted: (generation: number, handoffCount: number) => void;
|
|
150
|
-
readonly updateLeaseAudit: (generation: number, handoffCount: number) => void;
|
|
151
|
-
readonly abandonReloadHandoff: () => void;
|
|
152
|
-
readonly beginNotification: (lease: ReloadShellActivationLeaseV1) => string | undefined;
|
|
153
|
-
readonly finishNotification: (token: string, delivered: boolean) => void;
|
|
154
|
-
readonly releaseResources: () => void;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
export interface ReloadShellHostAdapterV1 {
|
|
158
|
-
readonly activationNonce: string;
|
|
159
|
-
readonly onBound: (lease: ReloadShellActivationLeaseV1) => void;
|
|
160
|
-
readonly onChanged: (execution: ReloadableShellExecutionV1) => void;
|
|
161
|
-
readonly onTerminal: (execution: ReloadableShellExecutionV1) => void;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export interface ReloadShellOwnerHubV1 {
|
|
165
|
-
readonly protocol: 'pi-background-tasks.reload-shell-owner.v1';
|
|
166
|
-
readonly hubNonce: string;
|
|
167
|
-
beginActivation(
|
|
168
|
-
identity: ReloadShellIdentityV1,
|
|
169
|
-
startReason: string,
|
|
170
|
-
activationNonce: string,
|
|
171
|
-
): ReloadShellActivationClaimV1;
|
|
172
|
-
commitActivation(
|
|
173
|
-
claim: ReloadShellActivationClaimV1,
|
|
174
|
-
adapter: ReloadShellHostAdapterV1,
|
|
175
|
-
): ReloadShellActivationLeaseV1;
|
|
176
|
-
abortActivation(claim: ReloadShellActivationClaimV1, error: unknown): void;
|
|
177
|
-
beginReloadHandoff(lease: ReloadShellActivationLeaseV1): readonly ReloadableShellExecutionV1[];
|
|
178
|
-
releaseActivation(lease: ReloadShellActivationLeaseV1): void;
|
|
179
|
-
registerExecution(
|
|
180
|
-
lease: ReloadShellActivationLeaseV1,
|
|
181
|
-
execution: ReloadableShellExecutionV1,
|
|
182
|
-
): void;
|
|
183
|
-
markAdmissionCommitted(
|
|
184
|
-
lease: ReloadShellActivationLeaseV1,
|
|
185
|
-
execution: ReloadableShellExecutionV1,
|
|
186
|
-
): void;
|
|
187
|
-
releaseExecution(
|
|
188
|
-
leaseOrClaim: ReloadShellActivationLeaseV1 | ReloadShellActivationClaimV1,
|
|
189
|
-
execution: ReloadableShellExecutionV1,
|
|
190
|
-
): void;
|
|
191
|
-
isCurrentLease(lease: ReloadShellActivationLeaseV1): boolean;
|
|
192
|
-
}
|
|
19
|
+
| 'retention_limit';
|
|
193
20
|
|
|
194
21
|
export interface BgTaskSnapshot {
|
|
195
22
|
id: string;
|
|
@@ -204,9 +31,8 @@ export interface BgTaskSnapshot {
|
|
|
204
31
|
exitCode?: number | null | undefined;
|
|
205
32
|
signal?: string | null | undefined;
|
|
206
33
|
pid?: number | undefined;
|
|
34
|
+
posixProcessGroupId?: number | undefined;
|
|
207
35
|
bytesWritten: number;
|
|
208
|
-
surviveReload: boolean;
|
|
209
|
-
reloadSurvival?: ReloadSurvivalSnapshotV1 | undefined;
|
|
210
36
|
error?: string | undefined;
|
|
211
37
|
notified: boolean;
|
|
212
38
|
notifyOnCompletion: boolean;
|
|
@@ -222,13 +48,18 @@ export interface BgTask extends Omit<BgTaskSnapshot, 'name'> {
|
|
|
222
48
|
metadataAbsPath: string;
|
|
223
49
|
eventsAbsPath?: string | undefined;
|
|
224
50
|
stderrAbsPath?: string | undefined;
|
|
225
|
-
|
|
226
|
-
/** Immutable in-memory ownership captured from a detached POSIX spawn; never restored from metadata. */
|
|
51
|
+
/** POSIX group authority captured at spawn; never restored except by adoption. */
|
|
227
52
|
ownedPosixProcessGroupId?: number | undefined;
|
|
228
53
|
/** One-way latch preventing any later signal after group authority is released. */
|
|
229
54
|
posixProcessGroupSignalAuthorityReleased?: boolean | undefined;
|
|
230
|
-
|
|
231
|
-
|
|
55
|
+
/** Absolute marker path; the wrapper writes the exit code here on settlement. */
|
|
56
|
+
exitMarkerAbsPath?: string | undefined;
|
|
57
|
+
/** False while the detached spawn is still in flight; the poller must not reap it then. */
|
|
58
|
+
launched?: boolean | undefined;
|
|
59
|
+
/** False only for an unclassified user shell launched without a wrapper. */
|
|
60
|
+
exitMarkerTrusted?: boolean | undefined;
|
|
61
|
+
runnerScriptFile?: string | undefined;
|
|
62
|
+
pollInFlight?: boolean | undefined;
|
|
232
63
|
killKind?: KillKind | undefined;
|
|
233
64
|
killSignalSent?: boolean | undefined;
|
|
234
65
|
killEscalationTimer?: NodeJS.Timeout | undefined;
|
|
@@ -245,12 +76,6 @@ export interface BgTask extends Omit<BgTaskSnapshot, 'name'> {
|
|
|
245
76
|
terminalPublishRetryHandle?: NodeJS.Timeout | undefined;
|
|
246
77
|
/** Optional protocol barrier used by EventBus run requests so early child exits cannot publish before the run response is observable. */
|
|
247
78
|
terminalPublicationGate?: Promise<void> | undefined;
|
|
248
|
-
/** In-memory same-process authority for an opted ordinary shell task; never serialized. */
|
|
249
|
-
reloadExecution?: ReloadableShellExecutionV1 | undefined;
|
|
250
|
-
/** Fresh-registry terminal host delivery state for an imported owner execution. */
|
|
251
|
-
reloadHostDeliveryInFlight?: boolean | undefined;
|
|
252
|
-
reloadHostDeliverySettled?: boolean | undefined;
|
|
253
|
-
reloadHostNotificationSettled?: boolean | undefined;
|
|
254
79
|
metadataWriteChain?: Promise<void> | undefined;
|
|
255
80
|
waiters: Array<() => void>;
|
|
256
81
|
}
|
|
@@ -342,7 +167,6 @@ export interface StartTaskOptions {
|
|
|
342
167
|
timeoutSeconds?: number | undefined;
|
|
343
168
|
notifyOnCompletion?: boolean | undefined;
|
|
344
169
|
triggerOnCompletion?: boolean | undefined;
|
|
345
|
-
surviveReload?: boolean | undefined;
|
|
346
170
|
/** @internal EventBus protocol barrier; callers should not set this outside the extension service. */
|
|
347
171
|
terminalPublicationGate?: Promise<void> | undefined;
|
|
348
172
|
}
|
|
@@ -452,11 +276,9 @@ function parseNameValueAndRest(valueAndRest: string): { value: string; rest: str
|
|
|
452
276
|
export function parseBgCommandArgs(args: string): {
|
|
453
277
|
name?: string;
|
|
454
278
|
command: string;
|
|
455
|
-
surviveReload: boolean;
|
|
456
279
|
} {
|
|
457
280
|
let input = args.trim();
|
|
458
281
|
let name: string | undefined;
|
|
459
|
-
let surviveReload = false;
|
|
460
282
|
|
|
461
283
|
while (input) {
|
|
462
284
|
let consumed = false;
|
|
@@ -484,27 +306,25 @@ export function parseBgCommandArgs(args: string): {
|
|
|
484
306
|
}
|
|
485
307
|
if (consumed) continue;
|
|
486
308
|
|
|
309
|
+
// Legacy flag: recognized and silently dropped. Task survival across /reload
|
|
310
|
+
// is now the only behavior, so the flag has nothing left to toggle.
|
|
487
311
|
if (
|
|
488
312
|
input === '--survive-reload' ||
|
|
489
313
|
input.startsWith('--survive-reload ') ||
|
|
490
|
-
input.startsWith('--survive-reload\t')
|
|
314
|
+
input.startsWith('--survive-reload\t') ||
|
|
315
|
+
input.startsWith('--survive-reload=')
|
|
491
316
|
) {
|
|
492
|
-
if (
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
);
|
|
317
|
+
if (input.startsWith('--survive-reload=')) {
|
|
318
|
+
// Legacy bare-flag-only policy: a `=` value was never honored, so the
|
|
319
|
+
// ignored flag consumes its whole value up to the next token.
|
|
320
|
+
const afterEquals = input.slice('--survive-reload='.length);
|
|
321
|
+
const valueEnd = afterEquals.search(/\s/u);
|
|
322
|
+
input = valueEnd === -1 ? '' : afterEquals.slice(valueEnd).trimStart();
|
|
323
|
+
} else {
|
|
324
|
+
input = input.slice('--survive-reload'.length).trimStart();
|
|
497
325
|
}
|
|
498
|
-
surviveReload = true;
|
|
499
|
-
input = input.slice('--survive-reload'.length).trimStart();
|
|
500
326
|
continue;
|
|
501
327
|
}
|
|
502
|
-
if (input.startsWith('--survive-reload=')) {
|
|
503
|
-
throw new ReloadSurvivalError(
|
|
504
|
-
'pi_bg_survive_reload_invalid',
|
|
505
|
-
'/bg accepts only the bare --survive-reload flag',
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
328
|
|
|
509
329
|
if (input === '--') {
|
|
510
330
|
input = '';
|
|
@@ -517,7 +337,7 @@ export function parseBgCommandArgs(args: string): {
|
|
|
517
337
|
break;
|
|
518
338
|
}
|
|
519
339
|
|
|
520
|
-
return name ? { name, command: input
|
|
340
|
+
return name ? { name, command: input } : { command: input };
|
|
521
341
|
}
|
|
522
342
|
|
|
523
343
|
export function formatDuration(ms: number): string {
|
|
@@ -850,9 +670,8 @@ export function snapshot(task: BgTask): BgTaskSnapshot {
|
|
|
850
670
|
exitCode: task.exitCode,
|
|
851
671
|
signal: task.signal,
|
|
852
672
|
pid: task.pid,
|
|
673
|
+
posixProcessGroupId: task.ownedPosixProcessGroupId,
|
|
853
674
|
bytesWritten: task.bytesWritten,
|
|
854
|
-
surviveReload: task.surviveReload === true,
|
|
855
|
-
reloadSurvival: task.reloadSurvival,
|
|
856
675
|
error: task.error,
|
|
857
676
|
notified: task.notified,
|
|
858
677
|
notifyOnCompletion: task.notifyOnCompletion,
|