@camerontaylor/paseo-cli 0.13.0-fork.2 → 0.15.2-fork.261106-2234.gae9279071e78
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { readDaemonInstance, isSameDaemonInstance, DaemonInstanceError, } from "@camerontaylor/paseo-server/daemon-control";
|
|
2
|
+
import { readDaemonInstance, isSameDaemonInstance, DaemonInstanceError, retryUnconfirmedTerminalCleanup, TerminalCleanupNotConfirmedError, } from "@camerontaylor/paseo-server/daemon-control";
|
|
3
3
|
import { setTimeout as delay } from "node:timers/promises";
|
|
4
4
|
import { connectToDaemon } from "../../utils/client.js";
|
|
5
5
|
import { withOutput } from "../../output/index.js";
|
|
@@ -9,12 +9,29 @@ import { parseTimeoutMs, rejectRemovedLaunchFlags } from "./local-daemon.js";
|
|
|
9
9
|
export function daemonRestartCommand() {
|
|
10
10
|
return rejectRemovedLaunchFlags(addJsonAndDaemonHostOptions(new Command("restart").description("Restart the selected daemon worker, retaining its supervisor launch")))
|
|
11
11
|
.option("--timeout <seconds>", "Replacement readiness deadline (default: 600)")
|
|
12
|
+
.option("--kill-terminals", "Close every terminal instead of keeping them across the restart")
|
|
12
13
|
.action(withOutput(runRestartCommand));
|
|
13
14
|
}
|
|
14
15
|
export async function runRestartCommand(options, _command) {
|
|
15
16
|
const target = options.daemonTarget;
|
|
16
17
|
const deadline = Date.now() + parseTimeoutMs(options.timeout);
|
|
17
18
|
const remaining = () => Math.max(1, deadline - Date.now());
|
|
19
|
+
const killTerminals = options.killTerminals === true;
|
|
20
|
+
// The supervisor reaps before the respawn and the replacement retries what that left;
|
|
21
|
+
// anything still named by a closing record survived both. A local caller retries once more
|
|
22
|
+
// itself; a remote one has only what the replacement reports.
|
|
23
|
+
const confirmTerminalsClosed = async (status) => {
|
|
24
|
+
if (!killTerminals)
|
|
25
|
+
return;
|
|
26
|
+
if (target.kind === "instance") {
|
|
27
|
+
await retryUnconfirmedTerminalCleanup(target.home);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// Absent on daemons without terminalsSurviveRestart, which have no terminal cleanup.
|
|
31
|
+
const unconfirmed = status.terminalCleanupUnconfirmed ?? [];
|
|
32
|
+
if (unconfirmed.length > 0)
|
|
33
|
+
throw new TerminalCleanupNotConfirmedError(unconfirmed);
|
|
34
|
+
};
|
|
18
35
|
const instance = target.kind === "instance" ? await readDaemonInstance(target.home) : null;
|
|
19
36
|
async function checkSupervisor() {
|
|
20
37
|
if (target.kind !== "instance")
|
|
@@ -37,7 +54,10 @@ export async function runRestartCommand(options, _command) {
|
|
|
37
54
|
workerPid = (await client.getDaemonStatus({ timeout: remaining() })).pid;
|
|
38
55
|
await checkSupervisor();
|
|
39
56
|
try {
|
|
40
|
-
await client.restartServer("cli_restart", undefined, {
|
|
57
|
+
await client.restartServer("cli_restart", undefined, {
|
|
58
|
+
timeout: remaining(),
|
|
59
|
+
killTerminals,
|
|
60
|
+
});
|
|
41
61
|
acknowledged = true;
|
|
42
62
|
}
|
|
43
63
|
catch (error) {
|
|
@@ -63,6 +83,7 @@ export async function runRestartCommand(options, _command) {
|
|
|
63
83
|
const status = await replacement.getDaemonStatus({ timeout: remaining() });
|
|
64
84
|
if (status.pid !== workerPid) {
|
|
65
85
|
await checkSupervisor();
|
|
86
|
+
await confirmTerminalsClosed(status);
|
|
66
87
|
return {
|
|
67
88
|
type: "single",
|
|
68
89
|
data: {
|
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { type DaemonClient } from "@camerontaylor/paseo-client/internal/daemon-client";
|
|
3
|
+
import { type ConnectOptions } from "../../utils/client.js";
|
|
2
4
|
import { type CommandOptions } from "../../output/index.js";
|
|
3
5
|
export declare function daemonStopCommand(): Command;
|
|
4
|
-
export
|
|
6
|
+
export type StopDaemonConnection = Pick<DaemonClient, "getLastServerInfoMessage" | "shutdownServer" | "close">;
|
|
7
|
+
export interface StopCommandDeps {
|
|
8
|
+
connectToDaemon(options: ConnectOptions): Promise<StopDaemonConnection>;
|
|
9
|
+
}
|
|
10
|
+
export declare const runStopCommand: (options: CommandOptions, _command: Command) => Promise<{
|
|
11
|
+
type: "single";
|
|
12
|
+
data: {
|
|
13
|
+
action: string;
|
|
14
|
+
home?: string;
|
|
15
|
+
host?: string;
|
|
16
|
+
pid?: number | null;
|
|
17
|
+
forced?: boolean;
|
|
18
|
+
usedLifecycleRpc?: boolean;
|
|
19
|
+
};
|
|
20
|
+
schema: {
|
|
21
|
+
idField: "action";
|
|
22
|
+
columns: never[];
|
|
23
|
+
renderHuman: () => string;
|
|
24
|
+
};
|
|
25
|
+
}>;
|
|
26
|
+
export declare function createStopCommandRunner(deps: StopCommandDeps): (options: CommandOptions, _command: Command) => Promise<{
|
|
5
27
|
type: "single";
|
|
6
28
|
data: {
|
|
7
29
|
action: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { requireTerminalKeepCapability, } from "@camerontaylor/paseo-client/internal/daemon-client";
|
|
2
3
|
import { stopDaemonInstance } from "@camerontaylor/paseo-server/daemon-control";
|
|
3
4
|
import { connectToDaemon } from "../../utils/client.js";
|
|
4
5
|
import { withOutput } from "../../output/index.js";
|
|
@@ -9,18 +10,28 @@ export function daemonStopCommand() {
|
|
|
9
10
|
return addJsonAndDaemonHostOptions(new Command("stop").description("Stop the selected daemon"))
|
|
10
11
|
.option("--timeout <seconds>", "Graceful exit deadline (default: 15)")
|
|
11
12
|
.option("--force", "Permit forced local process cleanup")
|
|
13
|
+
.option("--keep-terminals", "Leave terminals running; the next daemon started for this home takes them back")
|
|
12
14
|
.option("--kill-timeout <seconds>", "Forced exit deadline (default: 3)")
|
|
13
15
|
.action(withOutput(runStopCommand));
|
|
14
16
|
}
|
|
15
|
-
export
|
|
17
|
+
export const runStopCommand = createStopCommandRunner({ connectToDaemon });
|
|
18
|
+
export function createStopCommandRunner(deps) {
|
|
19
|
+
return (options, _command) => runStop(options, deps);
|
|
20
|
+
}
|
|
21
|
+
async function runStop(options, deps) {
|
|
16
22
|
const target = options.daemonTarget;
|
|
17
23
|
const timeoutMs = parseTimeoutMs(options.timeout, 15000);
|
|
18
24
|
const deadline = Date.now() + timeoutMs;
|
|
19
25
|
const remaining = () => Math.max(1, deadline - Date.now());
|
|
26
|
+
const keepTerminals = options.keepTerminals === true;
|
|
20
27
|
const requestShutdown = async (instance) => {
|
|
21
|
-
const client = await connectToDaemon({ target, timeout: remaining(), instance });
|
|
28
|
+
const client = await deps.connectToDaemon({ target, timeout: remaining(), instance });
|
|
22
29
|
try {
|
|
23
|
-
|
|
30
|
+
// Checked on the connection that carries the shutdown: a daemon without the capability
|
|
31
|
+
// would close the terminals it was asked to keep.
|
|
32
|
+
if (keepTerminals)
|
|
33
|
+
requireTerminalKeepCapability(client, describeDaemonTarget(target));
|
|
34
|
+
await client.shutdownServer({ timeout: remaining(), keepTerminals });
|
|
24
35
|
}
|
|
25
36
|
finally {
|
|
26
37
|
await client.close();
|
|
@@ -31,10 +42,16 @@ export async function runStopCommand(options, _command) {
|
|
|
31
42
|
code: "INVALID_OPTIONS",
|
|
32
43
|
message: "--force requires a local --home; an endpoint gives no remote process authority.",
|
|
33
44
|
};
|
|
45
|
+
if (options.force && keepTerminals)
|
|
46
|
+
throw {
|
|
47
|
+
code: "INVALID_OPTIONS",
|
|
48
|
+
message: "--force kills terminals; it cannot be combined with --keep-terminals.",
|
|
49
|
+
};
|
|
34
50
|
const result = target.kind === "instance"
|
|
35
51
|
? {
|
|
36
52
|
...(await stopDaemonInstance(target.home, {
|
|
37
53
|
force: options.force === true,
|
|
54
|
+
keepTerminals,
|
|
38
55
|
timeoutMs,
|
|
39
56
|
killTimeoutMs: parseTimeoutMs(options.killTimeout, 3000),
|
|
40
57
|
requestShutdown,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camerontaylor/paseo-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.2-fork.261106-2234.gae9279071e78",
|
|
4
4
|
"description": "Paseo CLI - control your AI coding agents from the command line",
|
|
5
5
|
"bin": {
|
|
6
6
|
"paseo": "bin/paseo"
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@clack/prompts": "^1.0.0",
|
|
31
|
-
"@camerontaylor/paseo-client": "0.
|
|
32
|
-
"@camerontaylor/paseo-protocol": "0.
|
|
33
|
-
"@camerontaylor/paseo-server": "0.
|
|
31
|
+
"@camerontaylor/paseo-client": "0.15.2-fork.261106-2234.gae9279071e78",
|
|
32
|
+
"@camerontaylor/paseo-protocol": "0.15.2-fork.261106-2234.gae9279071e78",
|
|
33
|
+
"@camerontaylor/paseo-server": "0.15.2-fork.261106-2234.gae9279071e78",
|
|
34
34
|
"chalk": "^5.3.0",
|
|
35
35
|
"commander": "^12.0.0",
|
|
36
36
|
"mime-types": "^2.1.35",
|