@haemmid/pi-processes 0.9.2 → 0.9.3
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/CHANGELOG.md +12 -0
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/manager.ts +42 -0
- package/src/tools/actions/restart.ts +6 -26
- package/src/tools/actions/utils.ts +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project follows semantic versioning for public releases.
|
|
6
6
|
|
|
7
|
+
## [0.9.3] - 2026-07-02
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `restart` waits for old process to fully exit before starting replacement;
|
|
12
|
+
does not start a new process if the old one does not exit within timeout.
|
|
13
|
+
- `restart` error message now shows the actual signal used (SIGTERM vs SIGKILL)
|
|
14
|
+
depending on `force` parameter.
|
|
15
|
+
- Ambiguous name errors now include exact process IDs in the suggestion.
|
|
16
|
+
- Added manager-level regression tests for `restart`: no premature spawn,
|
|
17
|
+
timeout handling, force=SIGKILL, post-restart resolve, ambiguous name rejection.
|
|
18
|
+
|
|
7
19
|
## [0.9.2] - 2026-07-02
|
|
8
20
|
|
|
9
21
|
### Added
|
package/README.md
CHANGED
|
@@ -194,6 +194,8 @@ process clear
|
|
|
194
194
|
- `ensure` reuses existing process when name+command+cwd match; returns conflict error otherwise.
|
|
195
195
|
- `restart` safely kills the existing process (awaited) before starting a new one.
|
|
196
196
|
- `restart` accepts `force=true` to send `SIGKILL` instead of `SIGTERM`.
|
|
197
|
+
- `restart` waits for the old process to exit; if it does not exit within the
|
|
198
|
+
timeout, the replacement is not started. Use `force=true` or inspect logs.
|
|
197
199
|
- `start`/`ensure`/`restart` accept `cwd` to override the working directory (defaults to session cwd).
|
|
198
200
|
- `output`, `logs`, and `kill` return an error if both `id` and `name` are provided.
|
|
199
201
|
|
package/package.json
CHANGED
package/src/manager.ts
CHANGED
|
@@ -235,6 +235,48 @@ export class ProcessManager {
|
|
|
235
235
|
return this.toProcessInfo(managed);
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Restart a process by name: kill existing (awaited), then start new.
|
|
240
|
+
*
|
|
241
|
+
* - Waits for existing process to exit (polls every 500ms, up to timeoutMs).
|
|
242
|
+
* - Returns null if existing process does not exit after timeout.
|
|
243
|
+
* - Returns null if a different live process with the same name appears.
|
|
244
|
+
* - Returns new ProcessInfo on success.
|
|
245
|
+
*/
|
|
246
|
+
async restart(
|
|
247
|
+
name: string,
|
|
248
|
+
command: string,
|
|
249
|
+
cwd: string,
|
|
250
|
+
opts?: { force?: boolean; timeoutMs?: number },
|
|
251
|
+
): Promise<ProcessInfo | null> {
|
|
252
|
+
const existing = this.resolve(name);
|
|
253
|
+
if (!existing.ok) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Kill existing process
|
|
258
|
+
const killResult = await this.kill(existing.info.id, {
|
|
259
|
+
signal: opts?.force ? "SIGKILL" : "SIGTERM",
|
|
260
|
+
timeoutMs: opts?.timeoutMs ?? 5000,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
if (!killResult.ok) {
|
|
264
|
+
// Process did not exit — return null so caller can decide
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Verify no other live process with this name appeared
|
|
269
|
+
const liveMatch = Array.from(this.processes.values()).find(
|
|
270
|
+
(proc) => proc.name === name && LIVE_STATUSES.has(proc.status),
|
|
271
|
+
);
|
|
272
|
+
if (liveMatch) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Start new process
|
|
277
|
+
return this.start(name, command, cwd);
|
|
278
|
+
}
|
|
279
|
+
|
|
238
280
|
/**
|
|
239
281
|
* Ensure a process is running with the given name, command, and cwd.
|
|
240
282
|
*
|
|
@@ -2,7 +2,6 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import type { ExecuteResult } from "../../constants";
|
|
3
3
|
import type { ProcessManager } from "../../manager";
|
|
4
4
|
import { formatTimestamp, sanitizeLine } from "../../utils";
|
|
5
|
-
import { executeKill } from "./kill";
|
|
6
5
|
import { buildNextCommands } from "./utils";
|
|
7
6
|
|
|
8
7
|
interface RestartParams {
|
|
@@ -56,46 +55,27 @@ export async function executeRestart(
|
|
|
56
55
|
};
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
//
|
|
60
|
-
const
|
|
61
|
-
{ id: existing.info.id, force: params.force },
|
|
62
|
-
manager,
|
|
63
|
-
);
|
|
64
|
-
if (!killResult.details.success) {
|
|
65
|
-
return {
|
|
66
|
-
content: [
|
|
67
|
-
{
|
|
68
|
-
type: "text",
|
|
69
|
-
text: `Failed to kill existing process: ${killResult.details.message}`,
|
|
70
|
-
},
|
|
71
|
-
],
|
|
72
|
-
details: {
|
|
73
|
-
action: "restart",
|
|
74
|
-
success: false,
|
|
75
|
-
message: `Failed to kill existing process: ${killResult.details.message}`,
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Start new process
|
|
81
|
-
const proc = manager.start(
|
|
58
|
+
// Restart via manager (atomic: kill → await exit → start)
|
|
59
|
+
const proc = await manager.restart(
|
|
82
60
|
params.name,
|
|
83
61
|
params.command,
|
|
84
62
|
params.cwd ?? ctx.cwd,
|
|
63
|
+
{ force: params.force },
|
|
85
64
|
);
|
|
86
65
|
|
|
87
66
|
if (proc === null) {
|
|
67
|
+
const signal = params.force ? "SIGKILL" : "SIGTERM";
|
|
88
68
|
return {
|
|
89
69
|
content: [
|
|
90
70
|
{
|
|
91
71
|
type: "text",
|
|
92
|
-
text: `
|
|
72
|
+
text: `Could not restart "${sanitizeLine(params.name)}": previous process did not exit after ${signal}. Use restart force=true or inspect logs.`,
|
|
93
73
|
},
|
|
94
74
|
],
|
|
95
75
|
details: {
|
|
96
76
|
action: "restart",
|
|
97
77
|
success: false,
|
|
98
|
-
message: `
|
|
78
|
+
message: `Previous process did not exit after ${signal}.`,
|
|
99
79
|
},
|
|
100
80
|
};
|
|
101
81
|
}
|
|
@@ -62,17 +62,22 @@ export function resolveSelector(
|
|
|
62
62
|
const choices = (resolved.matches ?? [])
|
|
63
63
|
.map((match) => `${match.id} ("${sanitizeLine(match.name)}")`)
|
|
64
64
|
.join(", ");
|
|
65
|
+
const ids = (resolved.matches ?? []).map((m) => m.id);
|
|
66
|
+
const suggestion =
|
|
67
|
+
ids.length === 2
|
|
68
|
+
? `Use id="${ids[0]}" or id="${ids[1]}", or inspect process list.`
|
|
69
|
+
: `Use an exact process ID instead. Matches: ${choices}`;
|
|
65
70
|
return {
|
|
66
71
|
content: [
|
|
67
72
|
{
|
|
68
73
|
type: "text",
|
|
69
|
-
text: `
|
|
74
|
+
text: `Multiple processes named "${query}" found. ${suggestion}`,
|
|
70
75
|
},
|
|
71
76
|
],
|
|
72
77
|
details: {
|
|
73
78
|
action: "output",
|
|
74
79
|
success: false,
|
|
75
|
-
message: `
|
|
80
|
+
message: `Multiple processes named "${query}" found. Matches: ${choices}`,
|
|
76
81
|
},
|
|
77
82
|
};
|
|
78
83
|
}
|