@agentteams/runner 0.0.104 → 0.0.106
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/dist/autostart.d.ts +12 -1
- package/dist/autostart.js +163 -27
- package/dist/autostart.js.map +1 -1
- package/dist/autostart.test.js +67 -17
- package/dist/autostart.test.js.map +1 -1
- package/dist/commands/start.js +5 -0
- package/dist/commands/start.js.map +1 -1
- package/dist/daemon-control.d.ts +25 -6
- package/dist/daemon-control.js +179 -19
- package/dist/daemon-control.js.map +1 -1
- package/dist/daemon-control.test.js +217 -34
- package/dist/daemon-control.test.js.map +1 -1
- package/dist/executable.test.js +6 -9
- package/dist/executable.test.js.map +1 -1
- package/dist/handlers/trigger-handler.test.js +46 -0
- package/dist/handlers/trigger-handler.test.js.map +1 -1
- package/dist/poller.js +12 -11
- package/dist/poller.js.map +1 -1
- package/dist/poller.test.js +51 -7
- package/dist/poller.test.js.map +1 -1
- package/dist/restart-handoff.d.ts +71 -0
- package/dist/restart-handoff.js +169 -0
- package/dist/restart-handoff.js.map +1 -0
- package/dist/restart-handoff.test.d.ts +1 -0
- package/dist/restart-handoff.test.js +228 -0
- package/dist/restart-handoff.test.js.map +1 -0
- package/dist/runners/amp.js +2 -1
- package/dist/runners/amp.js.map +1 -1
- package/dist/runners/antigravity.js +2 -1
- package/dist/runners/antigravity.js.map +1 -1
- package/dist/runners/claude-code.js +2 -1
- package/dist/runners/claude-code.js.map +1 -1
- package/dist/runners/codex.js +2 -1
- package/dist/runners/codex.js.map +1 -1
- package/dist/runners/copilot-cli.js +2 -1
- package/dist/runners/copilot-cli.js.map +1 -1
- package/dist/runners/cursor-cli.js +2 -1
- package/dist/runners/cursor-cli.js.map +1 -1
- package/dist/runners/cursor-cli.test.js +3 -1
- package/dist/runners/cursor-cli.test.js.map +1 -1
- package/dist/runners/failure-message.d.ts +7 -0
- package/dist/runners/failure-message.js +12 -0
- package/dist/runners/failure-message.js.map +1 -0
- package/dist/runners/failure-message.test.d.ts +1 -0
- package/dist/runners/failure-message.test.js +41 -0
- package/dist/runners/failure-message.test.js.map +1 -0
- package/dist/runners/kimi-cli.js +4 -1
- package/dist/runners/kimi-cli.js.map +1 -1
- package/dist/runners/kimi-cli.test.js +40 -2
- package/dist/runners/kimi-cli.test.js.map +1 -1
- package/dist/runners/opencode.js +2 -1
- package/dist/runners/opencode.js.map +1 -1
- package/dist/utils/resolve-member-repo.test.js +1 -1
- package/dist/utils/resolve-member-repo.test.js.map +1 -1
- package/package.json +3 -2
package/dist/daemon-control.d.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { platform as getPlatform } from 'node:os';
|
|
2
|
-
import { getAutostartStatus, restartAutostartService, scheduleWindowsTaskRestart } from './autostart.js';
|
|
2
|
+
import { getAutostartStatus, registerWindowsTask, restartAutostartService, scheduleWindowsTaskRestart } from './autostart.js';
|
|
3
3
|
import { logger } from './logger.js';
|
|
4
|
+
import { acknowledgePreparedRestartHandoff, waitForPreparedRestartHandoff, type RestartExecutionResult, type RestartHandoffLaunch, type RestartHandoffPreparation } from './restart-handoff.js';
|
|
4
5
|
type RunningDaemonStatus = {
|
|
5
6
|
running: boolean;
|
|
6
7
|
pid: number | null;
|
|
7
8
|
};
|
|
8
9
|
type DetachedChildProcess = {
|
|
10
|
+
pid?: number;
|
|
9
11
|
unref: () => void;
|
|
10
12
|
};
|
|
11
13
|
type RestartDeps = {
|
|
12
14
|
getDaemonStatus?: () => Promise<RunningDaemonStatus>;
|
|
13
15
|
getAutostartStatus?: typeof getAutostartStatus;
|
|
14
16
|
restartAutostartService?: typeof restartAutostartService;
|
|
15
|
-
spawnDetachedDaemon?: () => DetachedChildProcess | void;
|
|
17
|
+
spawnDetachedDaemon?: (launch?: RestartHandoffLaunch) => DetachedChildProcess | void;
|
|
16
18
|
kill?: typeof process.kill;
|
|
17
19
|
sleep?: (milliseconds: number) => Promise<void>;
|
|
18
20
|
logger?: Pick<typeof logger, 'info'>;
|
|
@@ -20,9 +22,17 @@ type RestartDeps = {
|
|
|
20
22
|
type ExecuteRestartDeps = {
|
|
21
23
|
getAutostartStatus?: typeof getAutostartStatus;
|
|
22
24
|
scheduleWindowsTaskRestart?: typeof scheduleWindowsTaskRestart;
|
|
23
|
-
|
|
25
|
+
registerWindowsTask?: typeof registerWindowsTask;
|
|
26
|
+
prepareDetachedDaemon?: () => Promise<RestartHandoffPreparation>;
|
|
27
|
+
spawnDetachedDaemon?: (launch?: RestartHandoffLaunch) => DetachedChildProcess | void;
|
|
28
|
+
acknowledgeRestart?: () => Promise<void>;
|
|
29
|
+
acknowledgePreparedHandoff?: typeof acknowledgePreparedRestartHandoff;
|
|
30
|
+
config?: {
|
|
31
|
+
daemonToken: string;
|
|
32
|
+
apiUrl: string;
|
|
33
|
+
};
|
|
24
34
|
platform?: typeof getPlatform;
|
|
25
|
-
processExit?: (code: number) =>
|
|
35
|
+
processExit?: (code: number) => void;
|
|
26
36
|
logger?: Pick<typeof logger, 'info'>;
|
|
27
37
|
};
|
|
28
38
|
type WaitForStartDeps = {
|
|
@@ -32,7 +42,16 @@ type WaitForStartDeps = {
|
|
|
32
42
|
timeoutMs?: number;
|
|
33
43
|
};
|
|
34
44
|
export declare const waitForDaemonToStart: (deps?: WaitForStartDeps) => Promise<RunningDaemonStatus>;
|
|
35
|
-
export declare const spawnDetachedDaemon: () => DetachedChildProcess | void;
|
|
36
|
-
|
|
45
|
+
export declare const spawnDetachedDaemon: (launch?: RestartHandoffLaunch) => DetachedChildProcess | void;
|
|
46
|
+
type PrepareDetachedRestartDeps = {
|
|
47
|
+
spawnDetachedDaemon?: (launch: RestartHandoffLaunch) => DetachedChildProcess | void;
|
|
48
|
+
waitForPreparedRestartHandoff?: typeof waitForPreparedRestartHandoff;
|
|
49
|
+
unlink?: (path: string) => Promise<void>;
|
|
50
|
+
handoffId?: string;
|
|
51
|
+
parentPid?: number;
|
|
52
|
+
markerPath?: string;
|
|
53
|
+
};
|
|
54
|
+
export declare const prepareDetachedRestartHandoff: (deps?: PrepareDetachedRestartDeps) => Promise<RestartHandoffPreparation>;
|
|
55
|
+
export declare const executeRestartRequest: (deps?: ExecuteRestartDeps) => Promise<RestartExecutionResult>;
|
|
37
56
|
export declare const restartDaemon: (deps?: RestartDeps) => Promise<void>;
|
|
38
57
|
export {};
|
package/dist/daemon-control.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { platform as getPlatform } from 'node:os';
|
|
3
|
-
import { getAutostartStatus, launchWindowsHiddenDaemon, restartAutostartService, scheduleWindowsTaskRestart, } from './autostart.js';
|
|
4
|
+
import { getAutostartStatus, launchWindowsHiddenDaemon, registerWindowsTask, restartAutostartService, scheduleWindowsTaskRestart, } from './autostart.js';
|
|
4
5
|
import { logger } from './logger.js';
|
|
5
6
|
import { getDaemonStatus } from './pid.js';
|
|
6
7
|
import { spawnExecutable } from './executable.js';
|
|
8
|
+
import { acknowledgePreparedRestartHandoff, buildRestartHandoffEnv, getRestartHandoffPath, waitForPreparedRestartHandoff, } from './restart-handoff.js';
|
|
9
|
+
import { promises as fs } from 'node:fs';
|
|
7
10
|
const restartPollIntervalMs = 100;
|
|
8
11
|
const stopTimeoutMs = 10_000;
|
|
9
12
|
const startTimeoutMs = 15_000;
|
|
@@ -36,62 +39,219 @@ const waitForDaemonToStop = async (pid, deps) => {
|
|
|
36
39
|
}
|
|
37
40
|
throw new Error(`Timed out waiting for AgentRunner process ${pid} to stop.`);
|
|
38
41
|
};
|
|
39
|
-
export const spawnDetachedDaemon = () => {
|
|
42
|
+
export const spawnDetachedDaemon = (launch) => {
|
|
43
|
+
const env = launch ? buildRestartHandoffEnv(launch) : process.env;
|
|
40
44
|
// On Windows, use the dedicated hidden PowerShell launcher so the `.cmd`
|
|
41
45
|
// shim never creates a visible console host.
|
|
42
46
|
if (getPlatform() === 'win32') {
|
|
43
|
-
launchWindowsHiddenDaemon();
|
|
47
|
+
launchWindowsHiddenDaemon({ env });
|
|
44
48
|
return;
|
|
45
49
|
}
|
|
46
50
|
const child = spawnExecutable('agentrunner', ['start'], {
|
|
47
51
|
detached: true,
|
|
48
52
|
stdio: 'ignore',
|
|
49
|
-
env
|
|
53
|
+
env,
|
|
50
54
|
cwd: process.cwd(),
|
|
51
55
|
});
|
|
52
56
|
child.unref();
|
|
53
57
|
return child;
|
|
54
58
|
};
|
|
59
|
+
export const prepareDetachedRestartHandoff = async (deps = {}) => {
|
|
60
|
+
const handoffId = deps.handoffId ?? randomUUID();
|
|
61
|
+
const parentPid = deps.parentPid ?? process.pid;
|
|
62
|
+
const markerPath = deps.markerPath ?? getRestartHandoffPath(handoffId);
|
|
63
|
+
const launch = { handoffId, parentPid, markerPath };
|
|
64
|
+
try {
|
|
65
|
+
await (deps.unlink ?? fs.unlink)(markerPath);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Missing or stale marker is fine.
|
|
69
|
+
}
|
|
70
|
+
let child;
|
|
71
|
+
try {
|
|
72
|
+
child = (deps.spawnDetachedDaemon ?? spawnDetachedDaemon)(launch);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return {
|
|
76
|
+
status: 'retryable-failure',
|
|
77
|
+
handoffId,
|
|
78
|
+
replacementReady: false,
|
|
79
|
+
acknowledged: false,
|
|
80
|
+
retryableFailure: true,
|
|
81
|
+
reason: 'replacement-preparation-failed',
|
|
82
|
+
error: error instanceof Error ? error.message : String(error),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (!child?.pid) {
|
|
86
|
+
return {
|
|
87
|
+
status: 'retryable-failure',
|
|
88
|
+
handoffId,
|
|
89
|
+
replacementReady: false,
|
|
90
|
+
acknowledged: false,
|
|
91
|
+
retryableFailure: true,
|
|
92
|
+
reason: 'replacement-preparation-failed',
|
|
93
|
+
error: 'Replacement runner process did not expose a PID.',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return (deps.waitForPreparedRestartHandoff ?? waitForPreparedRestartHandoff)(launch);
|
|
97
|
+
};
|
|
55
98
|
// Run from within the daemon itself when a web restart request is received.
|
|
56
99
|
// We can't call restartDaemon() here because that would SIGTERM our own PID
|
|
57
100
|
// before we get a chance to spawn the replacement; instead we either exit and
|
|
58
101
|
// let the OS supervisor restart us, or spawn a replacement and exit cleanly.
|
|
59
|
-
export const executeRestartRequest = (deps = {}) => {
|
|
102
|
+
export const executeRestartRequest = async (deps = {}) => {
|
|
60
103
|
const resolvedGetAutostartStatus = deps.getAutostartStatus ?? getAutostartStatus;
|
|
61
104
|
const resolvedScheduleWindowsTaskRestart = deps.scheduleWindowsTaskRestart ?? scheduleWindowsTaskRestart;
|
|
105
|
+
const resolvedRegisterWindowsTask = deps.registerWindowsTask ?? registerWindowsTask;
|
|
62
106
|
const resolvedSpawnDetachedDaemon = deps.spawnDetachedDaemon ?? spawnDetachedDaemon;
|
|
107
|
+
const acknowledgeRestart = deps.acknowledgeRestart ?? (async () => undefined);
|
|
108
|
+
const acknowledgePreparedHandoff = deps.acknowledgePreparedHandoff ?? acknowledgePreparedRestartHandoff;
|
|
63
109
|
const resolvedPlatform = (deps.platform ?? getPlatform)();
|
|
64
110
|
const exitProcess = deps.processExit ?? ((code) => process.exit(code));
|
|
65
111
|
const resolvedLogger = deps.logger ?? logger;
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
112
|
+
let autostartStatus = resolvedGetAutostartStatus();
|
|
113
|
+
if (resolvedPlatform === 'win32' || autostartStatus.platform === 'task-scheduler') {
|
|
114
|
+
if (!autostartStatus.registered) {
|
|
115
|
+
if (!deps.config) {
|
|
116
|
+
return {
|
|
117
|
+
status: 'retryable-failure',
|
|
118
|
+
handoffId: randomUUID(),
|
|
119
|
+
replacementReady: false,
|
|
120
|
+
acknowledged: false,
|
|
121
|
+
retryableFailure: true,
|
|
122
|
+
reason: 'autostart-repair-failed',
|
|
123
|
+
error: 'Windows Task Scheduler autostart is missing and runtime configuration is unavailable.',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
resolvedLogger.info('Windows Task Scheduler autostart is missing — repairing it before restart');
|
|
128
|
+
await resolvedRegisterWindowsTask({ token: deps.config.daemonToken, apiUrl: deps.config.apiUrl }, { startImmediately: false });
|
|
129
|
+
autostartStatus = { registered: true, platform: 'task-scheduler' };
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
return {
|
|
133
|
+
status: 'retryable-failure',
|
|
134
|
+
handoffId: randomUUID(),
|
|
135
|
+
replacementReady: false,
|
|
136
|
+
acknowledged: false,
|
|
137
|
+
retryableFailure: true,
|
|
138
|
+
reason: 'autostart-repair-failed',
|
|
139
|
+
error: error instanceof Error ? error.message : String(error),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
resolvedLogger.info('Restart requested — preparing an out-of-job Task Scheduler handoff');
|
|
144
|
+
const preparation = await resolvedScheduleWindowsTaskRestart();
|
|
145
|
+
if (preparation.status === 'retryable-failure') {
|
|
146
|
+
resolvedLogger.info('Restart handoff preparation failed — keeping the current runner alive for retry');
|
|
147
|
+
return preparation;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
await acknowledgeRestart();
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return {
|
|
154
|
+
status: 'retryable-failure',
|
|
155
|
+
handoffId: preparation.handoffId,
|
|
156
|
+
replacementReady: false,
|
|
157
|
+
acknowledged: false,
|
|
158
|
+
retryableFailure: true,
|
|
159
|
+
reason: 'acknowledgement-failed',
|
|
160
|
+
error: error instanceof Error ? error.message : String(error),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (!(await acknowledgePreparedHandoff(preparation))) {
|
|
164
|
+
return {
|
|
165
|
+
status: 'retryable-failure',
|
|
166
|
+
handoffId: preparation.handoffId,
|
|
167
|
+
replacementReady: false,
|
|
168
|
+
acknowledged: false,
|
|
169
|
+
retryableFailure: true,
|
|
170
|
+
reason: 'replacement-confirmation-failed',
|
|
171
|
+
error: 'The prepared Windows restart helper was no longer available after acknowledgement.',
|
|
172
|
+
};
|
|
76
173
|
}
|
|
77
174
|
exitProcess(0);
|
|
78
|
-
return
|
|
175
|
+
return {
|
|
176
|
+
status: 'acknowledged',
|
|
177
|
+
handoffId: preparation.handoffId,
|
|
178
|
+
replacementReady: true,
|
|
179
|
+
acknowledged: true,
|
|
180
|
+
retryableFailure: false,
|
|
181
|
+
};
|
|
79
182
|
}
|
|
80
183
|
const supervisedRespawn = autostartStatus.registered && (autostartStatus.platform === 'launchd' || autostartStatus.platform === 'systemd');
|
|
81
184
|
if (supervisedRespawn) {
|
|
185
|
+
const handoffId = randomUUID();
|
|
186
|
+
try {
|
|
187
|
+
await acknowledgeRestart();
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
return {
|
|
191
|
+
status: 'retryable-failure',
|
|
192
|
+
handoffId,
|
|
193
|
+
replacementReady: false,
|
|
194
|
+
acknowledged: false,
|
|
195
|
+
retryableFailure: true,
|
|
196
|
+
reason: 'acknowledgement-failed',
|
|
197
|
+
error: error instanceof Error ? error.message : String(error),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
82
200
|
resolvedLogger.info('Restart requested — exiting non-zero so the OS supervisor restarts the daemon', {
|
|
83
201
|
platform: autostartStatus.platform,
|
|
84
202
|
});
|
|
85
203
|
exitProcess(1);
|
|
86
|
-
return
|
|
204
|
+
return {
|
|
205
|
+
status: 'acknowledged',
|
|
206
|
+
handoffId,
|
|
207
|
+
replacementReady: true,
|
|
208
|
+
acknowledged: true,
|
|
209
|
+
retryableFailure: false,
|
|
210
|
+
};
|
|
87
211
|
}
|
|
88
|
-
resolvedLogger.info('Restart requested —
|
|
212
|
+
resolvedLogger.info('Restart requested — preparing a detached replacement runner', {
|
|
89
213
|
platform: autostartStatus.platform,
|
|
90
214
|
registered: autostartStatus.registered,
|
|
91
215
|
osPlatform: resolvedPlatform,
|
|
92
216
|
});
|
|
93
|
-
|
|
217
|
+
const preparation = await (deps.prepareDetachedDaemon ??
|
|
218
|
+
(() => prepareDetachedRestartHandoff({ spawnDetachedDaemon: resolvedSpawnDetachedDaemon })))();
|
|
219
|
+
if (preparation.status === 'retryable-failure') {
|
|
220
|
+
return preparation;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
await acknowledgeRestart();
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
return {
|
|
227
|
+
status: 'retryable-failure',
|
|
228
|
+
handoffId: preparation.handoffId,
|
|
229
|
+
replacementReady: false,
|
|
230
|
+
acknowledged: false,
|
|
231
|
+
retryableFailure: true,
|
|
232
|
+
reason: 'acknowledgement-failed',
|
|
233
|
+
error: error instanceof Error ? error.message : String(error),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
if (!(await acknowledgePreparedHandoff(preparation))) {
|
|
237
|
+
return {
|
|
238
|
+
status: 'retryable-failure',
|
|
239
|
+
handoffId: preparation.handoffId,
|
|
240
|
+
replacementReady: false,
|
|
241
|
+
acknowledged: false,
|
|
242
|
+
retryableFailure: true,
|
|
243
|
+
reason: 'replacement-confirmation-failed',
|
|
244
|
+
error: 'The prepared replacement runner was no longer available after acknowledgement.',
|
|
245
|
+
};
|
|
246
|
+
}
|
|
94
247
|
exitProcess(0);
|
|
248
|
+
return {
|
|
249
|
+
status: 'acknowledged',
|
|
250
|
+
handoffId: preparation.handoffId,
|
|
251
|
+
replacementReady: true,
|
|
252
|
+
acknowledged: true,
|
|
253
|
+
retryableFailure: false,
|
|
254
|
+
};
|
|
95
255
|
};
|
|
96
256
|
export const restartDaemon = async (deps = {}) => {
|
|
97
257
|
const resolvedGetDaemonStatus = deps.getDaemonStatus ?? getDaemonStatus;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-control.js","sourceRoot":"","sources":["../src/daemon-control.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,QAAQ,IAAI,WAAW,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"daemon-control.js","sourceRoot":"","sources":["../src/daemon-control.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import test from 'node:test';
|
|
3
|
-
import { executeRestartRequest, restartDaemon, waitForDaemonToStart } from './daemon-control.js';
|
|
3
|
+
import { executeRestartRequest, prepareDetachedRestartHandoff, restartDaemon, waitForDaemonToStart, } from './daemon-control.js';
|
|
4
4
|
test('restartDaemon stops running daemon and restarts via autostart when registered', async () => {
|
|
5
5
|
const signals = [];
|
|
6
6
|
let statusChecks = 0;
|
|
@@ -99,97 +99,280 @@ test('waitForDaemonToStart gives up after the deadline and reports not running',
|
|
|
99
99
|
// Stops polling once now() passes the deadline instead of looping forever.
|
|
100
100
|
assert.ok(checks >= 1);
|
|
101
101
|
});
|
|
102
|
-
test('executeRestartRequest exits non-zero when supervised by launchd', () => {
|
|
102
|
+
test('executeRestartRequest exits non-zero when supervised by launchd', async () => {
|
|
103
103
|
const exitCodes = [];
|
|
104
104
|
let spawned = false;
|
|
105
|
-
executeRestartRequest({
|
|
105
|
+
await executeRestartRequest({
|
|
106
106
|
getAutostartStatus: () => ({ registered: true, platform: 'launchd' }),
|
|
107
107
|
spawnDetachedDaemon: () => {
|
|
108
108
|
spawned = true;
|
|
109
109
|
},
|
|
110
110
|
platform: () => 'darwin',
|
|
111
|
-
processExit: (
|
|
111
|
+
processExit: (code) => {
|
|
112
112
|
exitCodes.push(code);
|
|
113
|
-
|
|
114
|
-
}),
|
|
113
|
+
},
|
|
115
114
|
logger: { info: () => undefined },
|
|
116
115
|
});
|
|
117
116
|
assert.deepEqual(exitCodes, [1]);
|
|
118
117
|
assert.equal(spawned, false);
|
|
119
118
|
});
|
|
120
|
-
test('executeRestartRequest exits non-zero when supervised by systemd', () => {
|
|
119
|
+
test('executeRestartRequest exits non-zero when supervised by systemd', async () => {
|
|
121
120
|
const exitCodes = [];
|
|
122
121
|
let spawned = false;
|
|
123
|
-
executeRestartRequest({
|
|
122
|
+
await executeRestartRequest({
|
|
124
123
|
getAutostartStatus: () => ({ registered: true, platform: 'systemd' }),
|
|
125
124
|
spawnDetachedDaemon: () => {
|
|
126
125
|
spawned = true;
|
|
127
126
|
},
|
|
128
127
|
platform: () => 'linux',
|
|
129
|
-
processExit: (
|
|
128
|
+
processExit: (code) => {
|
|
130
129
|
exitCodes.push(code);
|
|
131
|
-
|
|
132
|
-
}),
|
|
130
|
+
},
|
|
133
131
|
logger: { info: () => undefined },
|
|
134
132
|
});
|
|
135
133
|
assert.deepEqual(exitCodes, [1]);
|
|
136
134
|
assert.equal(spawned, false);
|
|
137
135
|
});
|
|
138
|
-
test('executeRestartRequest schedules an explicit restart and exits cleanly for Windows Task Scheduler', () => {
|
|
136
|
+
test('executeRestartRequest schedules an explicit restart and exits cleanly for Windows Task Scheduler', async () => {
|
|
139
137
|
const exitCodes = [];
|
|
140
138
|
let spawned = false;
|
|
141
139
|
let scheduled = false;
|
|
142
|
-
executeRestartRequest({
|
|
140
|
+
await executeRestartRequest({
|
|
143
141
|
getAutostartStatus: () => ({ registered: true, platform: 'task-scheduler' }),
|
|
144
|
-
scheduleWindowsTaskRestart: () => {
|
|
142
|
+
scheduleWindowsTaskRestart: async () => {
|
|
145
143
|
scheduled = true;
|
|
146
|
-
return
|
|
144
|
+
return {
|
|
145
|
+
status: 'prepared',
|
|
146
|
+
handoffId: 'windows-success',
|
|
147
|
+
markerPath: '/tmp/restart-handoff-windows-success.json',
|
|
148
|
+
replacementPid: 9002,
|
|
149
|
+
replacementReady: true,
|
|
150
|
+
acknowledged: false,
|
|
151
|
+
retryableFailure: false,
|
|
152
|
+
};
|
|
147
153
|
},
|
|
148
154
|
spawnDetachedDaemon: () => {
|
|
149
155
|
spawned = true;
|
|
150
156
|
},
|
|
151
157
|
platform: () => 'win32',
|
|
152
|
-
|
|
158
|
+
acknowledgePreparedHandoff: async () => true,
|
|
159
|
+
processExit: (code) => {
|
|
153
160
|
exitCodes.push(code);
|
|
154
|
-
|
|
155
|
-
}),
|
|
161
|
+
},
|
|
156
162
|
logger: { info: () => undefined },
|
|
157
163
|
});
|
|
158
164
|
assert.equal(spawned, false);
|
|
159
165
|
assert.equal(scheduled, true);
|
|
160
166
|
assert.deepEqual(exitCodes, [0]);
|
|
161
167
|
});
|
|
162
|
-
test('executeRestartRequest keeps the daemon alive when the Windows restart helper fails to schedule', () => {
|
|
168
|
+
test('executeRestartRequest keeps the daemon alive when the Windows restart helper fails to schedule', async () => {
|
|
163
169
|
const exitCodes = [];
|
|
164
|
-
executeRestartRequest({
|
|
170
|
+
await executeRestartRequest({
|
|
165
171
|
getAutostartStatus: () => ({ registered: true, platform: 'task-scheduler' }),
|
|
166
172
|
// Helper creation failed — must NOT exit, or the runner dies with no replacement.
|
|
167
|
-
scheduleWindowsTaskRestart: () =>
|
|
173
|
+
scheduleWindowsTaskRestart: async () => ({
|
|
174
|
+
status: 'retryable-failure',
|
|
175
|
+
handoffId: 'windows-failure',
|
|
176
|
+
replacementReady: false,
|
|
177
|
+
acknowledged: false,
|
|
178
|
+
retryableFailure: true,
|
|
179
|
+
reason: 'helper-preparation-failed',
|
|
180
|
+
error: 'helper creation failed',
|
|
181
|
+
}),
|
|
168
182
|
platform: () => 'win32',
|
|
169
|
-
processExit: (
|
|
183
|
+
processExit: (code) => {
|
|
170
184
|
exitCodes.push(code);
|
|
171
|
-
|
|
172
|
-
}),
|
|
185
|
+
},
|
|
173
186
|
logger: { info: () => undefined },
|
|
174
187
|
});
|
|
175
188
|
assert.deepEqual(exitCodes, [], 'daemon must not exit when the restart helper could not be created');
|
|
176
189
|
});
|
|
177
|
-
test('executeRestartRequest
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
executeRestartRequest({
|
|
190
|
+
test('executeRestartRequest acks and exits only after a manual replacement is prepared', async () => {
|
|
191
|
+
const events = [];
|
|
192
|
+
await executeRestartRequest({
|
|
181
193
|
getAutostartStatus: () => ({ registered: false, platform: 'launchd' }),
|
|
182
|
-
|
|
183
|
-
|
|
194
|
+
prepareDetachedDaemon: async () => {
|
|
195
|
+
events.push('prepared');
|
|
196
|
+
return {
|
|
197
|
+
status: 'prepared',
|
|
198
|
+
handoffId: 'manual-success',
|
|
199
|
+
markerPath: '/tmp/restart-handoff-manual-success.json',
|
|
200
|
+
replacementPid: 9002,
|
|
201
|
+
replacementReady: true,
|
|
202
|
+
acknowledged: false,
|
|
203
|
+
retryableFailure: false,
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
acknowledgeRestart: async () => {
|
|
207
|
+
events.push('ack');
|
|
208
|
+
},
|
|
209
|
+
acknowledgePreparedHandoff: async () => {
|
|
210
|
+
events.push('commit');
|
|
211
|
+
return true;
|
|
212
|
+
},
|
|
213
|
+
platform: () => 'darwin',
|
|
214
|
+
processExit: (code) => {
|
|
215
|
+
events.push(`exit:${code}`);
|
|
216
|
+
},
|
|
217
|
+
logger: { info: () => undefined },
|
|
218
|
+
});
|
|
219
|
+
assert.deepEqual(events, ['prepared', 'ack', 'commit', 'exit:0']);
|
|
220
|
+
});
|
|
221
|
+
test('executeRestartRequest keeps the daemon alive when the prepared standby disappears during acknowledgement', async () => {
|
|
222
|
+
const events = [];
|
|
223
|
+
const result = await executeRestartRequest({
|
|
224
|
+
getAutostartStatus: () => ({ registered: false, platform: 'manual' }),
|
|
225
|
+
prepareDetachedDaemon: async () => ({
|
|
226
|
+
status: 'prepared',
|
|
227
|
+
handoffId: 'manual-disappeared',
|
|
228
|
+
markerPath: '/tmp/restart-handoff-manual-disappeared.json',
|
|
229
|
+
replacementPid: 9002,
|
|
230
|
+
replacementReady: true,
|
|
231
|
+
acknowledged: false,
|
|
232
|
+
retryableFailure: false,
|
|
233
|
+
}),
|
|
234
|
+
acknowledgeRestart: async () => {
|
|
235
|
+
events.push('ack');
|
|
236
|
+
},
|
|
237
|
+
acknowledgePreparedHandoff: async () => {
|
|
238
|
+
events.push('commit-failed');
|
|
239
|
+
return false;
|
|
240
|
+
},
|
|
241
|
+
processExit: (code) => {
|
|
242
|
+
events.push(`exit:${code}`);
|
|
184
243
|
},
|
|
185
244
|
platform: () => 'darwin',
|
|
186
|
-
|
|
245
|
+
logger: { info: () => undefined },
|
|
246
|
+
});
|
|
247
|
+
assert.deepEqual(events, ['ack', 'commit-failed']);
|
|
248
|
+
assert.equal(result.status, 'retryable-failure');
|
|
249
|
+
});
|
|
250
|
+
test('prepareDetachedRestartHandoff reports spawn failure without waiting for readiness', async () => {
|
|
251
|
+
let waited = false;
|
|
252
|
+
const result = await prepareDetachedRestartHandoff({
|
|
253
|
+
handoffId: 'manual-spawn-failure',
|
|
254
|
+
parentPid: 4321,
|
|
255
|
+
markerPath: '/tmp/restart-handoff.json',
|
|
256
|
+
unlink: async () => undefined,
|
|
257
|
+
spawnDetachedDaemon: () => {
|
|
258
|
+
throw new Error('spawn failed');
|
|
259
|
+
},
|
|
260
|
+
waitForPreparedRestartHandoff: async () => {
|
|
261
|
+
waited = true;
|
|
262
|
+
throw new Error('should not wait');
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
assert.equal(result.status, 'retryable-failure');
|
|
266
|
+
assert.equal(result.reason, 'replacement-preparation-failed');
|
|
267
|
+
assert.equal(waited, false);
|
|
268
|
+
});
|
|
269
|
+
test('executeRestartRequest keeps supervised runners alive when acknowledgement fails', async () => {
|
|
270
|
+
const exitCodes = [];
|
|
271
|
+
const result = await executeRestartRequest({
|
|
272
|
+
getAutostartStatus: () => ({ registered: true, platform: 'systemd' }),
|
|
273
|
+
acknowledgeRestart: async () => {
|
|
274
|
+
throw new Error('network down');
|
|
275
|
+
},
|
|
276
|
+
platform: () => 'linux',
|
|
277
|
+
processExit: (code) => {
|
|
187
278
|
exitCodes.push(code);
|
|
188
|
-
|
|
279
|
+
},
|
|
280
|
+
logger: { info: () => undefined },
|
|
281
|
+
});
|
|
282
|
+
assert.equal(result.status, 'retryable-failure');
|
|
283
|
+
assert.equal(result.reason, 'acknowledgement-failed');
|
|
284
|
+
assert.deepEqual(exitCodes, []);
|
|
285
|
+
});
|
|
286
|
+
test('executeRestartRequest preserves the current runner and request when the Windows helper reports a post-create failure', async () => {
|
|
287
|
+
const events = [];
|
|
288
|
+
const result = await executeRestartRequest({
|
|
289
|
+
getAutostartStatus: () => ({ registered: true, platform: 'task-scheduler' }),
|
|
290
|
+
scheduleWindowsTaskRestart: async () => ({
|
|
291
|
+
status: 'retryable-failure',
|
|
292
|
+
handoffId: 'handoff-query-failure',
|
|
293
|
+
replacementReady: false,
|
|
294
|
+
acknowledged: false,
|
|
295
|
+
retryableFailure: true,
|
|
296
|
+
reason: 'helper-preparation-failed',
|
|
297
|
+
error: 'schtasks /Query failed with exit 1',
|
|
189
298
|
}),
|
|
299
|
+
acknowledgeRestart: async () => {
|
|
300
|
+
events.push('ack');
|
|
301
|
+
},
|
|
302
|
+
spawnDetachedDaemon: () => {
|
|
303
|
+
events.push('spawn');
|
|
304
|
+
},
|
|
305
|
+
processExit: (code) => {
|
|
306
|
+
events.push(`exit:${code}`);
|
|
307
|
+
},
|
|
308
|
+
platform: () => 'win32',
|
|
190
309
|
logger: { info: () => undefined },
|
|
191
310
|
});
|
|
192
|
-
assert.
|
|
193
|
-
assert.
|
|
311
|
+
assert.deepEqual(events, [], 'a post-create helper failure must not ack or terminate the current runner');
|
|
312
|
+
assert.equal(result.status, 'retryable-failure');
|
|
313
|
+
});
|
|
314
|
+
test('executeRestartRequest repairs a missing Windows scheduled task before preparing handoff', async () => {
|
|
315
|
+
const events = [];
|
|
316
|
+
const result = await executeRestartRequest({
|
|
317
|
+
getAutostartStatus: () => ({ registered: false, platform: 'task-scheduler' }),
|
|
318
|
+
registerWindowsTask: async () => {
|
|
319
|
+
events.push('register');
|
|
320
|
+
return { registered: true, servicePath: 'task.xml', platform: 'task-scheduler' };
|
|
321
|
+
},
|
|
322
|
+
scheduleWindowsTaskRestart: async () => {
|
|
323
|
+
events.push('prepare');
|
|
324
|
+
return {
|
|
325
|
+
status: 'retryable-failure',
|
|
326
|
+
handoffId: 'handoff-after-repair',
|
|
327
|
+
replacementReady: false,
|
|
328
|
+
acknowledged: false,
|
|
329
|
+
retryableFailure: true,
|
|
330
|
+
reason: 'helper-preparation-failed',
|
|
331
|
+
error: 'replacement did not become ready',
|
|
332
|
+
};
|
|
333
|
+
},
|
|
334
|
+
acknowledgeRestart: async () => {
|
|
335
|
+
events.push('ack');
|
|
336
|
+
},
|
|
337
|
+
spawnDetachedDaemon: () => {
|
|
338
|
+
events.push('spawn');
|
|
339
|
+
},
|
|
340
|
+
processExit: (code) => {
|
|
341
|
+
events.push(`exit:${code}`);
|
|
342
|
+
},
|
|
343
|
+
platform: () => 'win32',
|
|
344
|
+
config: { daemonToken: 'secret-token', apiUrl: 'https://api.example' },
|
|
345
|
+
logger: { info: () => undefined },
|
|
346
|
+
});
|
|
347
|
+
assert.deepEqual(events, ['register', 'prepare']);
|
|
348
|
+
assert.equal(result.status, 'retryable-failure');
|
|
349
|
+
});
|
|
350
|
+
test('executeRestartRequest does not ack or exit when a manual replacement never becomes ready', async () => {
|
|
351
|
+
const events = [];
|
|
352
|
+
const result = await executeRestartRequest({
|
|
353
|
+
getAutostartStatus: () => ({ registered: false, platform: 'manual' }),
|
|
354
|
+
prepareDetachedDaemon: async () => ({
|
|
355
|
+
status: 'retryable-failure',
|
|
356
|
+
handoffId: 'manual-timeout',
|
|
357
|
+
replacementReady: false,
|
|
358
|
+
acknowledged: false,
|
|
359
|
+
retryableFailure: true,
|
|
360
|
+
reason: 'replacement-preparation-failed',
|
|
361
|
+
error: 'replacement readiness timed out',
|
|
362
|
+
}),
|
|
363
|
+
acknowledgeRestart: async () => {
|
|
364
|
+
events.push('ack');
|
|
365
|
+
},
|
|
366
|
+
spawnDetachedDaemon: () => {
|
|
367
|
+
events.push('spawn');
|
|
368
|
+
},
|
|
369
|
+
processExit: (code) => {
|
|
370
|
+
events.push(`exit:${code}`);
|
|
371
|
+
},
|
|
372
|
+
platform: () => 'darwin',
|
|
373
|
+
logger: { info: () => undefined },
|
|
374
|
+
});
|
|
375
|
+
assert.deepEqual(events, [], 'spawn success alone must not ack or terminate the current runner');
|
|
376
|
+
assert.equal(result.status, 'retryable-failure');
|
|
194
377
|
});
|
|
195
378
|
//# sourceMappingURL=daemon-control.test.js.map
|