@agentteams/runner 0.0.84 → 0.0.85-dev.211
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/api-client.d.ts +2 -2
- package/dist/api-client.js +66 -66
- package/dist/api-client.js.map +1 -1
- package/dist/api-client.test.js +63 -54
- package/dist/api-client.test.js.map +1 -1
- package/dist/autostart.js +81 -81
- package/dist/autostart.js.map +1 -1
- package/dist/autostart.test.js +35 -35
- package/dist/autostart.test.js.map +1 -1
- package/dist/commands/cleanup.js +4 -4
- package/dist/commands/init.js +14 -14
- package/dist/commands/restart.js +3 -3
- package/dist/commands/start.js +10 -10
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/status.js +7 -7
- package/dist/commands/stop.js +7 -7
- package/dist/commands/uninstall.js +6 -6
- package/dist/commands/update.d.ts +4 -4
- package/dist/commands/update.js +20 -22
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +38 -42
- package/dist/commands/update.test.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +12 -12
- package/dist/config.test.js +61 -61
- package/dist/config.test.js.map +1 -1
- package/dist/daemon-control.d.ts +5 -5
- package/dist/daemon-control.js +21 -22
- package/dist/daemon-control.js.map +1 -1
- package/dist/daemon-control.test.js +29 -29
- package/dist/executable.d.ts +3 -3
- package/dist/executable.js +46 -49
- package/dist/executable.js.map +1 -1
- package/dist/executable.test.js +62 -62
- package/dist/executable.test.js.map +1 -1
- package/dist/handlers/trigger-handler.d.ts +6 -6
- package/dist/handlers/trigger-handler.js +172 -134
- package/dist/handlers/trigger-handler.js.map +1 -1
- package/dist/handlers/trigger-handler.test.js +442 -328
- package/dist/handlers/trigger-handler.test.js.map +1 -1
- package/dist/index.js +25 -25
- package/dist/logger.js +1 -1
- package/dist/pid.js +6 -6
- package/dist/poller.d.ts +7 -7
- package/dist/poller.js +55 -54
- package/dist/poller.js.map +1 -1
- package/dist/poller.test.js +264 -180
- package/dist/poller.test.js.map +1 -1
- package/dist/runners/amp.d.ts +1 -1
- package/dist/runners/amp.js +74 -81
- package/dist/runners/amp.js.map +1 -1
- package/dist/runners/antigravity.d.ts +3 -3
- package/dist/runners/antigravity.js +116 -127
- package/dist/runners/antigravity.js.map +1 -1
- package/dist/runners/antigravity.replay.test.js +25 -25
- package/dist/runners/antigravity.replay.test.js.map +1 -1
- package/dist/runners/antigravity.test.js +98 -98
- package/dist/runners/antigravity.test.js.map +1 -1
- package/dist/runners/claude-code.d.ts +1 -1
- package/dist/runners/claude-code.js +85 -84
- package/dist/runners/claude-code.js.map +1 -1
- package/dist/runners/claude-code.test.js +37 -40
- package/dist/runners/claude-code.test.js.map +1 -1
- package/dist/runners/codex.d.ts +1 -1
- package/dist/runners/codex.js +87 -96
- package/dist/runners/codex.js.map +1 -1
- package/dist/runners/codex.test.js +68 -68
- package/dist/runners/codex.test.js.map +1 -1
- package/dist/runners/index.d.ts +1 -1
- package/dist/runners/index.js +10 -10
- package/dist/runners/index.test.js +16 -16
- package/dist/runners/log-reporter.d.ts +2 -2
- package/dist/runners/log-reporter.js +12 -10
- package/dist/runners/log-reporter.js.map +1 -1
- package/dist/runners/log-reporter.test.js +70 -70
- package/dist/runners/log-reporter.test.js.map +1 -1
- package/dist/runners/opencode.d.ts +1 -1
- package/dist/runners/opencode.js +68 -75
- package/dist/runners/opencode.js.map +1 -1
- package/dist/runners/process-control.d.ts +1 -1
- package/dist/runners/process-control.js +13 -11
- package/dist/runners/process-control.js.map +1 -1
- package/dist/runners/stream-json-parser.d.ts +1 -1
- package/dist/runners/stream-json-parser.js +70 -67
- package/dist/runners/stream-json-parser.js.map +1 -1
- package/dist/runners/stream-json-parser.replay.test.js +17 -17
- package/dist/runners/stream-json-parser.replay.test.js.map +1 -1
- package/dist/runners/stream-json-parser.test.js +76 -78
- package/dist/runners/stream-json-parser.test.js.map +1 -1
- package/dist/types.d.ts +4 -4
- package/dist/utils/auth-path-store.js +7 -7
- package/dist/utils/auth-path-store.test.js +24 -29
- package/dist/utils/auth-path-store.test.js.map +1 -1
- package/dist/utils/auto-update.d.ts +4 -4
- package/dist/utils/auto-update.js +30 -28
- package/dist/utils/auto-update.js.map +1 -1
- package/dist/utils/auto-update.test.js +30 -30
- package/dist/utils/convention-evaluator.d.ts +1 -1
- package/dist/utils/convention-evaluator.js +22 -21
- package/dist/utils/convention-evaluator.js.map +1 -1
- package/dist/utils/convention-evaluator.test.js +120 -110
- package/dist/utils/convention-evaluator.test.js.map +1 -1
- package/dist/utils/convention-sync.d.ts +3 -3
- package/dist/utils/convention-sync.js +86 -18
- package/dist/utils/convention-sync.js.map +1 -1
- package/dist/utils/convention-sync.test.js +126 -30
- package/dist/utils/convention-sync.test.js.map +1 -1
- package/dist/utils/git-worktree.js +20 -18
- package/dist/utils/git-worktree.js.map +1 -1
- package/dist/utils/git-worktree.test.js +98 -98
- package/dist/utils/git-worktree.test.js.map +1 -1
- package/dist/utils/origin-issue-safeguard.js +35 -30
- package/dist/utils/origin-issue-safeguard.js.map +1 -1
- package/dist/utils/power-save-blocker.d.ts +4 -4
- package/dist/utils/power-save-blocker.js +33 -33
- package/dist/utils/power-save-blocker.test.js +37 -37
- package/dist/utils/power-save-blocker.test.js.map +1 -1
- package/dist/utils/runner-cleanup.d.ts +3 -3
- package/dist/utils/runner-cleanup.js +13 -13
- package/dist/utils/runner-cleanup.test.js +35 -35
- package/dist/utils/runner-history.js +3 -3
- package/dist/utils/runner-history.test.js +17 -17
- package/package.json +9 -2
- package/readme.md +10 -10
package/dist/poller.test.js
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import assert from
|
|
2
|
-
import * as fsModule from
|
|
3
|
-
import os from
|
|
4
|
-
import path from
|
|
5
|
-
import test, { mock } from
|
|
6
|
-
import { logger } from
|
|
7
|
-
import { startPolling } from
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import * as fsModule from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import test, { mock } from 'node:test';
|
|
6
|
+
import { logger } from './logger.js';
|
|
7
|
+
import { startPolling } from './poller.js';
|
|
8
8
|
const config = {
|
|
9
|
-
daemonToken:
|
|
10
|
-
apiUrl:
|
|
9
|
+
daemonToken: 'daemon-token',
|
|
10
|
+
apiUrl: 'https://api.example',
|
|
11
11
|
pollingIntervalMs: 5000,
|
|
12
12
|
timeoutMs: 1000,
|
|
13
13
|
idleTimeoutMs: 500,
|
|
14
|
-
runnerCmd:
|
|
15
|
-
preventSleepWhileBusy: false
|
|
14
|
+
runnerCmd: 'opencode',
|
|
15
|
+
preventSleepWhileBusy: false,
|
|
16
16
|
};
|
|
17
17
|
const trigger = {
|
|
18
|
-
id:
|
|
19
|
-
prompt:
|
|
20
|
-
runnerType:
|
|
21
|
-
model:
|
|
18
|
+
id: 'trigger-1',
|
|
19
|
+
prompt: 'hello',
|
|
20
|
+
runnerType: 'CODEX',
|
|
21
|
+
model: 'o4-mini',
|
|
22
22
|
fastMode: false,
|
|
23
|
-
status:
|
|
24
|
-
agentConfigId:
|
|
23
|
+
status: 'PENDING',
|
|
24
|
+
agentConfigId: 'agent-1',
|
|
25
25
|
startedAt: null,
|
|
26
26
|
errorMessage: null,
|
|
27
27
|
worktreeError: null,
|
|
28
28
|
lastHeartbeatAt: null,
|
|
29
29
|
conversationId: null,
|
|
30
30
|
parentTriggerId: null,
|
|
31
|
-
createdByMemberId:
|
|
31
|
+
createdByMemberId: 'member-1',
|
|
32
32
|
planMode: false,
|
|
33
33
|
targetDaemonId: null,
|
|
34
34
|
claimedByDaemonId: null,
|
|
@@ -36,8 +36,8 @@ const trigger = {
|
|
|
36
36
|
baseBranch: null,
|
|
37
37
|
worktreeId: null,
|
|
38
38
|
worktreeStatus: null,
|
|
39
|
-
createdAt:
|
|
40
|
-
updatedAt:
|
|
39
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
40
|
+
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
41
41
|
};
|
|
42
42
|
test.afterEach(() => {
|
|
43
43
|
mock.restoreAll();
|
|
@@ -48,15 +48,15 @@ const createRecordingBlocker = () => {
|
|
|
48
48
|
events,
|
|
49
49
|
blocker: {
|
|
50
50
|
acquire: (label) => {
|
|
51
|
-
events.push(`acquire:${label ??
|
|
52
|
-
return () => events.push(`release:${label ??
|
|
53
|
-
}
|
|
54
|
-
}
|
|
51
|
+
events.push(`acquire:${label ?? ''}`);
|
|
52
|
+
return () => events.push(`release:${label ?? ''}`);
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
55
|
};
|
|
56
56
|
};
|
|
57
|
-
test(
|
|
57
|
+
test('startPolling handles a claimed trigger, registers auth paths, and runs scheduled cleanup', async () => {
|
|
58
58
|
const infos = [];
|
|
59
|
-
mock.method(logger,
|
|
59
|
+
mock.method(logger, 'info', (message, meta) => {
|
|
60
60
|
infos.push({ message, meta });
|
|
61
61
|
});
|
|
62
62
|
const signals = new Map();
|
|
@@ -74,10 +74,10 @@ test("startPolling handles a claimed trigger, registers auth paths, and runs sch
|
|
|
74
74
|
fetchPendingWorktreeRemovals: async () => [],
|
|
75
75
|
reportWorktreeStatus: async () => undefined,
|
|
76
76
|
notifyUpdate: async () => undefined,
|
|
77
|
-
ackRestartRequest: async () => undefined
|
|
77
|
+
ackRestartRequest: async () => undefined,
|
|
78
78
|
};
|
|
79
79
|
const createHandler = (onAuthPathDiscovered) => {
|
|
80
|
-
onAuthPathDiscovered(
|
|
80
|
+
onAuthPathDiscovered('/auth/path');
|
|
81
81
|
return async (value) => {
|
|
82
82
|
handledTriggers.push(value.id);
|
|
83
83
|
};
|
|
@@ -88,6 +88,7 @@ test("startPolling handles a claimed trigger, registers auth paths, and runs sch
|
|
|
88
88
|
runCleanup: async (authPath) => {
|
|
89
89
|
cleanupCalls.push(authPath);
|
|
90
90
|
},
|
|
91
|
+
runConventionSync: async () => undefined,
|
|
91
92
|
setInterval: ((callback) => {
|
|
92
93
|
intervalCallbacks.push(callback);
|
|
93
94
|
return { ref() { }, unref() { } };
|
|
@@ -97,39 +98,95 @@ test("startPolling handles a claimed trigger, registers auth paths, and runs sch
|
|
|
97
98
|
signals.set(event, listener);
|
|
98
99
|
}),
|
|
99
100
|
processExit: (() => {
|
|
100
|
-
throw new Error(
|
|
101
|
+
throw new Error('should not exit');
|
|
101
102
|
}),
|
|
102
103
|
now: () => nowValue,
|
|
103
104
|
loadAuthPaths: () => [],
|
|
104
105
|
saveAuthPath: (authPath) => {
|
|
105
106
|
savedAuthPaths.push(authPath);
|
|
106
|
-
return
|
|
107
|
+
return '/tmp/auth-paths.json';
|
|
107
108
|
},
|
|
108
109
|
keepAlive: () => new Promise((resolve) => {
|
|
109
110
|
keepAliveResolve = resolve;
|
|
110
|
-
})
|
|
111
|
+
}),
|
|
111
112
|
});
|
|
112
113
|
await new Promise((resolve) => setImmediate(resolve));
|
|
113
|
-
assert.deepEqual(handledTriggers, [
|
|
114
|
+
assert.deepEqual(handledTriggers, ['trigger-1']);
|
|
114
115
|
assert.deepEqual(cleanupCalls, []);
|
|
115
116
|
assert.equal(intervalCallbacks.length, 1);
|
|
116
117
|
nowValue = 24 * 60 * 60 * 1000 + 1;
|
|
117
118
|
await intervalCallbacks[0]?.();
|
|
118
119
|
await new Promise((resolve) => setImmediate(resolve));
|
|
119
|
-
assert.deepEqual(cleanupCalls, [
|
|
120
|
-
assert.deepEqual(savedAuthPaths, [
|
|
121
|
-
assert.equal(signals.has(
|
|
122
|
-
assert.equal(signals.has(
|
|
123
|
-
assert.equal(infos.some((entry) => entry.message ===
|
|
124
|
-
const resolveKeepAlive = keepAliveResolve ??
|
|
125
|
-
|
|
120
|
+
assert.deepEqual(cleanupCalls, ['/auth/path']);
|
|
121
|
+
assert.deepEqual(savedAuthPaths, ['/auth/path']);
|
|
122
|
+
assert.equal(signals.has('SIGINT'), true);
|
|
123
|
+
assert.equal(signals.has('SIGTERM'), true);
|
|
124
|
+
assert.equal(infos.some((entry) => entry.message === 'Daemon polling started'), true);
|
|
125
|
+
const resolveKeepAlive = keepAliveResolve ??
|
|
126
|
+
(() => {
|
|
127
|
+
throw new Error('keepAlive resolver was not registered');
|
|
128
|
+
});
|
|
129
|
+
resolveKeepAlive();
|
|
130
|
+
await pollingPromise;
|
|
131
|
+
});
|
|
132
|
+
test('startPolling runs convention sync per auth path every 6 hours', async () => {
|
|
133
|
+
const intervalCallbacks = [];
|
|
134
|
+
const conventionSyncCalls = [];
|
|
135
|
+
let nowValue = 6 * 60 * 60 * 1000 + 1;
|
|
136
|
+
let keepAliveResolve = null;
|
|
137
|
+
const pollingPromise = startPolling(config, () => async () => undefined, {
|
|
138
|
+
createClient: () => ({
|
|
139
|
+
fetchPendingTrigger: async () => ({ data: null }),
|
|
140
|
+
claimTrigger: async () => ({ ok: true, conflict: false }),
|
|
141
|
+
fetchOrphanedCancelRequested: async () => [],
|
|
142
|
+
updateTriggerStatus: async () => undefined,
|
|
143
|
+
fetchPendingWorktreeRemovals: async () => [],
|
|
144
|
+
reportWorktreeStatus: async () => undefined,
|
|
145
|
+
notifyUpdate: async () => undefined,
|
|
146
|
+
ackRestartRequest: async () => undefined,
|
|
147
|
+
}),
|
|
148
|
+
runCleanup: async () => undefined,
|
|
149
|
+
runConventionSync: async (authPath) => {
|
|
150
|
+
conventionSyncCalls.push(authPath);
|
|
151
|
+
},
|
|
152
|
+
maybeAutoUpdate: async () => ({ cliUpdated: false, runnerUpdated: false }),
|
|
153
|
+
setInterval: ((callback) => {
|
|
154
|
+
intervalCallbacks.push(callback);
|
|
155
|
+
return { ref() { }, unref() { } };
|
|
156
|
+
}),
|
|
157
|
+
clearInterval: (() => undefined),
|
|
158
|
+
processOn: (() => undefined),
|
|
159
|
+
processExit: (() => {
|
|
160
|
+
throw new Error('should not exit');
|
|
161
|
+
}),
|
|
162
|
+
now: () => nowValue,
|
|
163
|
+
loadAuthPaths: () => ['/auth/path'],
|
|
164
|
+
saveAuthPath: () => '/tmp/auth-paths.json',
|
|
165
|
+
keepAlive: () => new Promise((resolve) => {
|
|
166
|
+
keepAliveResolve = resolve;
|
|
167
|
+
}),
|
|
126
168
|
});
|
|
169
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
170
|
+
assert.deepEqual(conventionSyncCalls, ['/auth/path']);
|
|
171
|
+
assert.equal(intervalCallbacks.length, 1);
|
|
172
|
+
nowValue += 6 * 60 * 60 * 1000 - 1;
|
|
173
|
+
await intervalCallbacks[0]?.();
|
|
174
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
175
|
+
assert.deepEqual(conventionSyncCalls, ['/auth/path']);
|
|
176
|
+
nowValue += 1;
|
|
177
|
+
await intervalCallbacks[0]?.();
|
|
178
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
179
|
+
assert.deepEqual(conventionSyncCalls, ['/auth/path', '/auth/path']);
|
|
180
|
+
const resolveKeepAlive = keepAliveResolve ??
|
|
181
|
+
(() => {
|
|
182
|
+
throw new Error('keepAlive resolver was not registered');
|
|
183
|
+
});
|
|
127
184
|
resolveKeepAlive();
|
|
128
185
|
await pollingPromise;
|
|
129
186
|
});
|
|
130
|
-
test(
|
|
187
|
+
test('startPolling logs conflicts and suppresses overlapping polling cycles', async () => {
|
|
131
188
|
const infos = [];
|
|
132
|
-
mock.method(logger,
|
|
189
|
+
mock.method(logger, 'info', (message, meta) => {
|
|
133
190
|
infos.push({ message, meta });
|
|
134
191
|
});
|
|
135
192
|
let releaseFetch = null;
|
|
@@ -143,12 +200,12 @@ test("startPolling logs conflicts and suppresses overlapping polling cycles", as
|
|
|
143
200
|
fetchPendingWorktreeRemovals: async () => [],
|
|
144
201
|
reportWorktreeStatus: async () => undefined,
|
|
145
202
|
notifyUpdate: async () => undefined,
|
|
146
|
-
ackRestartRequest: async () => undefined
|
|
203
|
+
ackRestartRequest: async () => undefined,
|
|
147
204
|
};
|
|
148
205
|
const intervalCallbacks = [];
|
|
149
206
|
let keepAliveResolve = null;
|
|
150
207
|
const pollingPromise = startPolling(config, () => async () => {
|
|
151
|
-
throw new Error(
|
|
208
|
+
throw new Error('handler should not run');
|
|
152
209
|
}, {
|
|
153
210
|
createClient: () => client,
|
|
154
211
|
runCleanup: async () => undefined,
|
|
@@ -159,30 +216,32 @@ test("startPolling logs conflicts and suppresses overlapping polling cycles", as
|
|
|
159
216
|
clearInterval: (() => undefined),
|
|
160
217
|
processOn: (() => undefined),
|
|
161
218
|
processExit: (() => {
|
|
162
|
-
throw new Error(
|
|
219
|
+
throw new Error('should not exit');
|
|
163
220
|
}),
|
|
164
221
|
now: () => 0,
|
|
165
222
|
loadAuthPaths: () => [],
|
|
166
|
-
saveAuthPath: () =>
|
|
223
|
+
saveAuthPath: () => '/tmp/auth-paths.json',
|
|
167
224
|
keepAlive: () => new Promise((resolve) => {
|
|
168
225
|
keepAliveResolve = resolve;
|
|
169
|
-
})
|
|
226
|
+
}),
|
|
170
227
|
});
|
|
171
228
|
await new Promise((resolve) => setImmediate(resolve));
|
|
172
229
|
await intervalCallbacks[0]?.();
|
|
173
|
-
const releasePendingFetch = releaseFetch ??
|
|
174
|
-
|
|
175
|
-
|
|
230
|
+
const releasePendingFetch = releaseFetch ??
|
|
231
|
+
(() => {
|
|
232
|
+
throw new Error('fetch release was not registered');
|
|
233
|
+
});
|
|
176
234
|
releasePendingFetch();
|
|
177
235
|
await new Promise((resolve) => setImmediate(resolve));
|
|
178
|
-
assert.equal(infos.some((entry) => entry.message ===
|
|
179
|
-
const resolveKeepAlive = keepAliveResolve ??
|
|
180
|
-
|
|
181
|
-
|
|
236
|
+
assert.equal(infos.some((entry) => entry.message === 'Trigger already claimed by another daemon'), true);
|
|
237
|
+
const resolveKeepAlive = keepAliveResolve ??
|
|
238
|
+
(() => {
|
|
239
|
+
throw new Error('keepAlive resolver was not registered');
|
|
240
|
+
});
|
|
182
241
|
resolveKeepAlive();
|
|
183
242
|
await pollingPromise;
|
|
184
243
|
});
|
|
185
|
-
test(
|
|
244
|
+
test('startPolling clears the interval and exits on shutdown signals', async () => {
|
|
186
245
|
const signals = new Map();
|
|
187
246
|
const cleared = [];
|
|
188
247
|
let keepAliveResolve = null;
|
|
@@ -197,7 +256,7 @@ test("startPolling clears the interval and exits on shutdown signals", async ()
|
|
|
197
256
|
fetchPendingWorktreeRemovals: async () => [],
|
|
198
257
|
reportWorktreeStatus: async () => undefined,
|
|
199
258
|
notifyUpdate: async () => undefined,
|
|
200
|
-
ackRestartRequest: async () => undefined
|
|
259
|
+
ackRestartRequest: async () => undefined,
|
|
201
260
|
}),
|
|
202
261
|
runCleanup: async () => undefined,
|
|
203
262
|
setInterval: (() => intervalHandle),
|
|
@@ -213,36 +272,37 @@ test("startPolling clears the interval and exits on shutdown signals", async ()
|
|
|
213
272
|
}),
|
|
214
273
|
now: () => 0,
|
|
215
274
|
loadAuthPaths: () => [],
|
|
216
|
-
saveAuthPath: () =>
|
|
275
|
+
saveAuthPath: () => '/tmp/auth-paths.json',
|
|
217
276
|
keepAlive: () => new Promise((resolve) => {
|
|
218
277
|
keepAliveResolve = resolve;
|
|
219
|
-
})
|
|
278
|
+
}),
|
|
220
279
|
}).catch((error) => {
|
|
221
|
-
if (error.message !==
|
|
280
|
+
if (error.message !== 'exit') {
|
|
222
281
|
throw error;
|
|
223
282
|
}
|
|
224
283
|
});
|
|
225
284
|
await new Promise((resolve) => setImmediate(resolve));
|
|
226
|
-
assert.ok(signals.get(
|
|
227
|
-
const sigterm = signals.get(
|
|
285
|
+
assert.ok(signals.get('SIGTERM'));
|
|
286
|
+
const sigterm = signals.get('SIGTERM');
|
|
228
287
|
assert.ok(sigterm);
|
|
229
288
|
sigterm();
|
|
230
289
|
assert.equal(exitCode, 0);
|
|
231
290
|
assert.deepEqual(cleared, [intervalHandle]);
|
|
232
|
-
const resolveKeepAlive = keepAliveResolve ??
|
|
233
|
-
|
|
234
|
-
|
|
291
|
+
const resolveKeepAlive = keepAliveResolve ??
|
|
292
|
+
(() => {
|
|
293
|
+
throw new Error('keepAlive resolver was not registered');
|
|
294
|
+
});
|
|
235
295
|
resolveKeepAlive();
|
|
236
296
|
await pollingPromise;
|
|
237
297
|
});
|
|
238
|
-
test(
|
|
298
|
+
test('startPolling delegates auto-update via injected dependency', async () => {
|
|
239
299
|
let autoUpdateCallCount = 0;
|
|
240
300
|
let keepAliveResolve = null;
|
|
241
301
|
const pollingPromise = startPolling(config, () => async () => undefined, {
|
|
242
302
|
createClient: () => ({
|
|
243
303
|
fetchPendingTrigger: async () => ({
|
|
244
304
|
data: null,
|
|
245
|
-
meta: { cliLatestVersion: null, runnerLatestVersion:
|
|
305
|
+
meta: { cliLatestVersion: null, runnerLatestVersion: '99.0.0' },
|
|
246
306
|
}),
|
|
247
307
|
claimTrigger: async () => ({ ok: true, conflict: false }),
|
|
248
308
|
fetchOrphanedCancelRequested: async () => [],
|
|
@@ -250,7 +310,7 @@ test("startPolling delegates auto-update via injected dependency", async () => {
|
|
|
250
310
|
fetchPendingWorktreeRemovals: async () => [],
|
|
251
311
|
reportWorktreeStatus: async () => undefined,
|
|
252
312
|
notifyUpdate: async () => undefined,
|
|
253
|
-
ackRestartRequest: async () => undefined
|
|
313
|
+
ackRestartRequest: async () => undefined,
|
|
254
314
|
}),
|
|
255
315
|
runCleanup: async () => undefined,
|
|
256
316
|
maybeAutoUpdate: async () => {
|
|
@@ -263,33 +323,34 @@ test("startPolling delegates auto-update via injected dependency", async () => {
|
|
|
263
323
|
processExit: (() => undefined),
|
|
264
324
|
now: () => 0,
|
|
265
325
|
loadAuthPaths: () => [],
|
|
266
|
-
saveAuthPath: () =>
|
|
326
|
+
saveAuthPath: () => '/tmp/auth-paths.json',
|
|
267
327
|
keepAlive: () => new Promise((resolve) => {
|
|
268
328
|
keepAliveResolve = resolve;
|
|
269
|
-
})
|
|
329
|
+
}),
|
|
270
330
|
});
|
|
271
331
|
await new Promise((resolve) => setImmediate(resolve));
|
|
272
|
-
assert.equal(autoUpdateCallCount, 1,
|
|
273
|
-
const resolveKeepAlive = keepAliveResolve ??
|
|
274
|
-
|
|
275
|
-
|
|
332
|
+
assert.equal(autoUpdateCallCount, 1, 'should call injected maybeAutoUpdate on idle poll');
|
|
333
|
+
const resolveKeepAlive = keepAliveResolve ??
|
|
334
|
+
(() => {
|
|
335
|
+
throw new Error('keepAlive resolver was not registered');
|
|
336
|
+
});
|
|
276
337
|
resolveKeepAlive();
|
|
277
338
|
await pollingPromise;
|
|
278
339
|
});
|
|
279
|
-
test(
|
|
340
|
+
test('startPolling restores persisted auth paths for worktree removals after restart', async () => {
|
|
280
341
|
const removedWorktrees = [];
|
|
281
342
|
const reportedStatuses = [];
|
|
282
|
-
const tempRoot = fsModule.mkdtempSync(path.join(os.tmpdir(),
|
|
283
|
-
const authPath = path.join(tempRoot,
|
|
284
|
-
const worktreePath = path.join(tempRoot,
|
|
343
|
+
const tempRoot = fsModule.mkdtempSync(path.join(os.tmpdir(), 'agentrunner-poller-'));
|
|
344
|
+
const authPath = path.join(tempRoot, 'path');
|
|
345
|
+
const worktreePath = path.join(tempRoot, '.path-worktrees', 'wt-worktree-1');
|
|
285
346
|
fsModule.mkdirSync(authPath, { recursive: true });
|
|
286
347
|
fsModule.mkdirSync(worktreePath, { recursive: true });
|
|
287
348
|
const worktreeRemovalTrigger = {
|
|
288
349
|
...trigger,
|
|
289
|
-
id:
|
|
350
|
+
id: 'trigger-remove',
|
|
290
351
|
useWorktree: true,
|
|
291
|
-
worktreeId:
|
|
292
|
-
worktreeStatus:
|
|
352
|
+
worktreeId: 'worktree-1',
|
|
353
|
+
worktreeStatus: 'REMOVE_REQUESTED',
|
|
293
354
|
};
|
|
294
355
|
let keepAliveResolve = null;
|
|
295
356
|
const pollingPromise = startPolling(config, () => async () => undefined, {
|
|
@@ -303,7 +364,7 @@ test("startPolling restores persisted auth paths for worktree removals after res
|
|
|
303
364
|
reportedStatuses.push({ triggerId, status, worktreeError });
|
|
304
365
|
},
|
|
305
366
|
notifyUpdate: async () => undefined,
|
|
306
|
-
ackRestartRequest: async () => undefined
|
|
367
|
+
ackRestartRequest: async () => undefined,
|
|
307
368
|
}),
|
|
308
369
|
runCleanup: async () => undefined,
|
|
309
370
|
removeWorktree: (authPath, worktreePath, worktreeId) => {
|
|
@@ -313,37 +374,40 @@ test("startPolling restores persisted auth paths for worktree removals after res
|
|
|
313
374
|
clearInterval: (() => undefined),
|
|
314
375
|
processOn: (() => undefined),
|
|
315
376
|
processExit: (() => {
|
|
316
|
-
throw new Error(
|
|
377
|
+
throw new Error('should not exit');
|
|
317
378
|
}),
|
|
318
379
|
now: () => 0,
|
|
319
380
|
loadAuthPaths: () => [authPath],
|
|
320
|
-
saveAuthPath: () =>
|
|
381
|
+
saveAuthPath: () => '/tmp/auth-paths.json',
|
|
321
382
|
keepAlive: () => new Promise((resolve) => {
|
|
322
383
|
keepAliveResolve = resolve;
|
|
323
|
-
})
|
|
384
|
+
}),
|
|
324
385
|
});
|
|
325
386
|
await new Promise((resolve) => setImmediate(resolve));
|
|
326
|
-
assert.deepEqual(removedWorktrees, [
|
|
387
|
+
assert.deepEqual(removedWorktrees, [
|
|
388
|
+
{
|
|
327
389
|
authPath,
|
|
328
390
|
worktreePath,
|
|
329
|
-
worktreeId:
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
391
|
+
worktreeId: 'worktree-1',
|
|
392
|
+
},
|
|
393
|
+
]);
|
|
394
|
+
assert.deepEqual(reportedStatuses, [{ triggerId: 'trigger-remove', status: 'REMOVED', worktreeError: undefined }]);
|
|
395
|
+
const resolveKeepAlive = keepAliveResolve ??
|
|
396
|
+
(() => {
|
|
397
|
+
throw new Error('keepAlive resolver was not registered');
|
|
398
|
+
});
|
|
335
399
|
resolveKeepAlive();
|
|
336
400
|
await pollingPromise;
|
|
337
401
|
fsModule.rmSync(tempRoot, { recursive: true, force: true });
|
|
338
402
|
});
|
|
339
|
-
test(
|
|
403
|
+
test('startPolling reports FAILED when no persisted auth path matches the worktree', async () => {
|
|
340
404
|
const reportedStatuses = [];
|
|
341
405
|
const worktreeRemovalTrigger = {
|
|
342
406
|
...trigger,
|
|
343
|
-
id:
|
|
407
|
+
id: 'trigger-remove-missing',
|
|
344
408
|
useWorktree: true,
|
|
345
|
-
worktreeId:
|
|
346
|
-
worktreeStatus:
|
|
409
|
+
worktreeId: 'worktree-missing',
|
|
410
|
+
worktreeStatus: 'REMOVE_REQUESTED',
|
|
347
411
|
};
|
|
348
412
|
let keepAliveResolve = null;
|
|
349
413
|
const pollingPromise = startPolling(config, () => async () => undefined, {
|
|
@@ -357,50 +421,53 @@ test("startPolling reports FAILED when no persisted auth path matches the worktr
|
|
|
357
421
|
reportedStatuses.push({ triggerId, status, worktreeError });
|
|
358
422
|
},
|
|
359
423
|
notifyUpdate: async () => undefined,
|
|
360
|
-
ackRestartRequest: async () => undefined
|
|
424
|
+
ackRestartRequest: async () => undefined,
|
|
361
425
|
}),
|
|
362
426
|
runCleanup: async () => undefined,
|
|
363
427
|
removeWorktree: () => {
|
|
364
|
-
throw new Error(
|
|
428
|
+
throw new Error('removeWorktree should not be called');
|
|
365
429
|
},
|
|
366
430
|
setInterval: (() => ({ ref() { }, unref() { } })),
|
|
367
431
|
clearInterval: (() => undefined),
|
|
368
432
|
processOn: (() => undefined),
|
|
369
433
|
processExit: (() => {
|
|
370
|
-
throw new Error(
|
|
434
|
+
throw new Error('should not exit');
|
|
371
435
|
}),
|
|
372
436
|
now: () => 0,
|
|
373
|
-
loadAuthPaths: () => [
|
|
374
|
-
saveAuthPath: () =>
|
|
437
|
+
loadAuthPaths: () => ['/persisted/auth/path'],
|
|
438
|
+
saveAuthPath: () => '/tmp/auth-paths.json',
|
|
375
439
|
keepAlive: () => new Promise((resolve) => {
|
|
376
440
|
keepAliveResolve = resolve;
|
|
377
|
-
})
|
|
441
|
+
}),
|
|
378
442
|
});
|
|
379
443
|
await new Promise((resolve) => setImmediate(resolve));
|
|
380
|
-
assert.deepEqual(reportedStatuses, [
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
444
|
+
assert.deepEqual(reportedStatuses, [
|
|
445
|
+
{
|
|
446
|
+
triggerId: 'trigger-remove-missing',
|
|
447
|
+
status: 'FAILED',
|
|
448
|
+
worktreeError: 'Failed to remove RunnerBox: worktree path was not found for worktree-missing',
|
|
449
|
+
},
|
|
450
|
+
]);
|
|
451
|
+
const resolveKeepAlive = keepAliveResolve ??
|
|
452
|
+
(() => {
|
|
453
|
+
throw new Error('keepAlive resolver was not registered');
|
|
454
|
+
});
|
|
388
455
|
resolveKeepAlive();
|
|
389
456
|
await pollingPromise;
|
|
390
457
|
});
|
|
391
|
-
test(
|
|
458
|
+
test('startPolling reports FAILED when worktree removal throws after matching a path', async () => {
|
|
392
459
|
const reportedStatuses = [];
|
|
393
|
-
const tempRoot = fsModule.mkdtempSync(path.join(os.tmpdir(),
|
|
394
|
-
const authPath = path.join(tempRoot,
|
|
395
|
-
const worktreePath = path.join(tempRoot,
|
|
460
|
+
const tempRoot = fsModule.mkdtempSync(path.join(os.tmpdir(), 'agentrunner-poller-'));
|
|
461
|
+
const authPath = path.join(tempRoot, 'path');
|
|
462
|
+
const worktreePath = path.join(tempRoot, '.path-worktrees', 'wt-worktree-failed');
|
|
396
463
|
fsModule.mkdirSync(authPath, { recursive: true });
|
|
397
464
|
fsModule.mkdirSync(worktreePath, { recursive: true });
|
|
398
465
|
const worktreeRemovalTrigger = {
|
|
399
466
|
...trigger,
|
|
400
|
-
id:
|
|
467
|
+
id: 'trigger-remove-failed',
|
|
401
468
|
useWorktree: true,
|
|
402
|
-
worktreeId:
|
|
403
|
-
worktreeStatus:
|
|
469
|
+
worktreeId: 'worktree-failed',
|
|
470
|
+
worktreeStatus: 'REMOVE_REQUESTED',
|
|
404
471
|
};
|
|
405
472
|
let keepAliveResolve = null;
|
|
406
473
|
const pollingPromise = startPolling(config, () => async () => undefined, {
|
|
@@ -414,45 +481,51 @@ test("startPolling reports FAILED when worktree removal throws after matching a
|
|
|
414
481
|
reportedStatuses.push({ triggerId, status, worktreeError });
|
|
415
482
|
},
|
|
416
483
|
notifyUpdate: async () => undefined,
|
|
417
|
-
ackRestartRequest: async () => undefined
|
|
484
|
+
ackRestartRequest: async () => undefined,
|
|
418
485
|
}),
|
|
419
486
|
runCleanup: async () => undefined,
|
|
420
487
|
removeWorktree: () => {
|
|
421
|
-
throw new Error(
|
|
488
|
+
throw new Error('permission denied');
|
|
422
489
|
},
|
|
423
490
|
setInterval: (() => ({ ref() { }, unref() { } })),
|
|
424
491
|
clearInterval: (() => undefined),
|
|
425
492
|
processOn: (() => undefined),
|
|
426
493
|
processExit: (() => {
|
|
427
|
-
throw new Error(
|
|
494
|
+
throw new Error('should not exit');
|
|
428
495
|
}),
|
|
429
496
|
now: () => 0,
|
|
430
497
|
loadAuthPaths: () => [authPath],
|
|
431
|
-
saveAuthPath: () =>
|
|
498
|
+
saveAuthPath: () => '/tmp/auth-paths.json',
|
|
432
499
|
keepAlive: () => new Promise((resolve) => {
|
|
433
500
|
keepAliveResolve = resolve;
|
|
434
|
-
})
|
|
501
|
+
}),
|
|
435
502
|
});
|
|
436
503
|
await new Promise((resolve) => setImmediate(resolve));
|
|
437
|
-
assert.deepEqual(reportedStatuses, [
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
504
|
+
assert.deepEqual(reportedStatuses, [
|
|
505
|
+
{
|
|
506
|
+
triggerId: 'trigger-remove-failed',
|
|
507
|
+
status: 'FAILED',
|
|
508
|
+
worktreeError: 'permission denied',
|
|
509
|
+
},
|
|
510
|
+
]);
|
|
511
|
+
const resolveKeepAlive = keepAliveResolve ??
|
|
512
|
+
(() => {
|
|
513
|
+
throw new Error('keepAlive resolver was not registered');
|
|
514
|
+
});
|
|
445
515
|
resolveKeepAlive();
|
|
446
516
|
await pollingPromise;
|
|
447
517
|
fsModule.rmSync(tempRoot, { recursive: true, force: true });
|
|
448
518
|
});
|
|
449
|
-
test(
|
|
519
|
+
test('startPolling acks and runs restart when meta.restartRequested is true with no pending trigger', async () => {
|
|
450
520
|
let ackCalled = 0;
|
|
451
521
|
let restartCalled = 0;
|
|
452
522
|
let keepAliveResolve = null;
|
|
453
523
|
const pollingPromise = startPolling(config, () => async () => undefined, {
|
|
454
524
|
createClient: () => ({
|
|
455
|
-
fetchPendingTrigger: async () => ({
|
|
525
|
+
fetchPendingTrigger: async () => ({
|
|
526
|
+
data: null,
|
|
527
|
+
meta: { cliLatestVersion: null, runnerLatestVersion: null, restartRequested: true },
|
|
528
|
+
}),
|
|
456
529
|
claimTrigger: async () => ({ ok: true, conflict: false }),
|
|
457
530
|
fetchOrphanedCancelRequested: async () => [],
|
|
458
531
|
updateTriggerStatus: async () => undefined,
|
|
@@ -461,11 +534,11 @@ test("startPolling acks and runs restart when meta.restartRequested is true with
|
|
|
461
534
|
notifyUpdate: async () => undefined,
|
|
462
535
|
ackRestartRequest: async () => {
|
|
463
536
|
ackCalled += 1;
|
|
464
|
-
}
|
|
537
|
+
},
|
|
465
538
|
}),
|
|
466
539
|
runCleanup: async () => undefined,
|
|
467
540
|
maybeAutoUpdate: async () => {
|
|
468
|
-
throw new Error(
|
|
541
|
+
throw new Error('auto-update must be skipped while restarting');
|
|
469
542
|
},
|
|
470
543
|
executeRestartRequest: () => {
|
|
471
544
|
restartCalled += 1;
|
|
@@ -476,30 +549,34 @@ test("startPolling acks and runs restart when meta.restartRequested is true with
|
|
|
476
549
|
processExit: (() => undefined),
|
|
477
550
|
now: () => 0,
|
|
478
551
|
loadAuthPaths: () => [],
|
|
479
|
-
saveAuthPath: () =>
|
|
552
|
+
saveAuthPath: () => '/tmp/auth-paths.json',
|
|
480
553
|
keepAlive: () => new Promise((resolve) => {
|
|
481
554
|
keepAliveResolve = resolve;
|
|
482
|
-
})
|
|
555
|
+
}),
|
|
483
556
|
});
|
|
484
557
|
await new Promise((resolve) => setImmediate(resolve));
|
|
485
|
-
assert.equal(ackCalled, 1,
|
|
486
|
-
assert.equal(restartCalled, 1,
|
|
487
|
-
const resolveKeepAlive = keepAliveResolve ??
|
|
488
|
-
|
|
489
|
-
|
|
558
|
+
assert.equal(ackCalled, 1, 'ackRestartRequest should be called exactly once');
|
|
559
|
+
assert.equal(restartCalled, 1, 'executeRestartRequest should be called exactly once');
|
|
560
|
+
const resolveKeepAlive = keepAliveResolve ??
|
|
561
|
+
(() => {
|
|
562
|
+
throw new Error('keepAlive resolver was not registered');
|
|
563
|
+
});
|
|
490
564
|
resolveKeepAlive();
|
|
491
565
|
await pollingPromise;
|
|
492
566
|
});
|
|
493
|
-
test(
|
|
567
|
+
test('startPolling restarts even when a pending trigger is present, ignoring the trigger', async () => {
|
|
494
568
|
let ackCalled = 0;
|
|
495
569
|
let restartCalled = 0;
|
|
496
570
|
let claimCalled = 0;
|
|
497
571
|
let keepAliveResolve = null;
|
|
498
572
|
const pollingPromise = startPolling(config, () => async () => {
|
|
499
|
-
throw new Error(
|
|
573
|
+
throw new Error('trigger handler must not run when restart wins');
|
|
500
574
|
}, {
|
|
501
575
|
createClient: () => ({
|
|
502
|
-
fetchPendingTrigger: async () => ({
|
|
576
|
+
fetchPendingTrigger: async () => ({
|
|
577
|
+
data: trigger,
|
|
578
|
+
meta: { cliLatestVersion: null, runnerLatestVersion: null, restartRequested: true },
|
|
579
|
+
}),
|
|
503
580
|
claimTrigger: async () => {
|
|
504
581
|
claimCalled += 1;
|
|
505
582
|
return { ok: true, conflict: false };
|
|
@@ -511,7 +588,7 @@ test("startPolling restarts even when a pending trigger is present, ignoring the
|
|
|
511
588
|
notifyUpdate: async () => undefined,
|
|
512
589
|
ackRestartRequest: async () => {
|
|
513
590
|
ackCalled += 1;
|
|
514
|
-
}
|
|
591
|
+
},
|
|
515
592
|
}),
|
|
516
593
|
runCleanup: async () => undefined,
|
|
517
594
|
executeRestartRequest: () => {
|
|
@@ -523,27 +600,31 @@ test("startPolling restarts even when a pending trigger is present, ignoring the
|
|
|
523
600
|
processExit: (() => undefined),
|
|
524
601
|
now: () => 0,
|
|
525
602
|
loadAuthPaths: () => [],
|
|
526
|
-
saveAuthPath: () =>
|
|
603
|
+
saveAuthPath: () => '/tmp/auth-paths.json',
|
|
527
604
|
keepAlive: () => new Promise((resolve) => {
|
|
528
605
|
keepAliveResolve = resolve;
|
|
529
|
-
})
|
|
606
|
+
}),
|
|
530
607
|
});
|
|
531
608
|
await new Promise((resolve) => setImmediate(resolve));
|
|
532
|
-
assert.equal(ackCalled, 1,
|
|
533
|
-
assert.equal(restartCalled, 1,
|
|
534
|
-
assert.equal(claimCalled, 0,
|
|
535
|
-
const resolveKeepAlive = keepAliveResolve ??
|
|
536
|
-
|
|
537
|
-
|
|
609
|
+
assert.equal(ackCalled, 1, 'ack should run before restart');
|
|
610
|
+
assert.equal(restartCalled, 1, 'restart should run even with a pending trigger');
|
|
611
|
+
assert.equal(claimCalled, 0, 'trigger must not be claimed while restarting');
|
|
612
|
+
const resolveKeepAlive = keepAliveResolve ??
|
|
613
|
+
(() => {
|
|
614
|
+
throw new Error('keepAlive resolver was not registered');
|
|
615
|
+
});
|
|
538
616
|
resolveKeepAlive();
|
|
539
617
|
await pollingPromise;
|
|
540
618
|
});
|
|
541
|
-
test(
|
|
619
|
+
test('startPolling skips restart and leaves the flag for retry when ack fails', async () => {
|
|
542
620
|
let restartCalled = 0;
|
|
543
621
|
let keepAliveResolve = null;
|
|
544
622
|
const pollingPromise = startPolling(config, () => async () => undefined, {
|
|
545
623
|
createClient: () => ({
|
|
546
|
-
fetchPendingTrigger: async () => ({
|
|
624
|
+
fetchPendingTrigger: async () => ({
|
|
625
|
+
data: null,
|
|
626
|
+
meta: { cliLatestVersion: null, runnerLatestVersion: null, restartRequested: true },
|
|
627
|
+
}),
|
|
547
628
|
claimTrigger: async () => ({ ok: true, conflict: false }),
|
|
548
629
|
fetchOrphanedCancelRequested: async () => [],
|
|
549
630
|
updateTriggerStatus: async () => undefined,
|
|
@@ -551,8 +632,8 @@ test("startPolling skips restart and leaves the flag for retry when ack fails",
|
|
|
551
632
|
reportWorktreeStatus: async () => undefined,
|
|
552
633
|
notifyUpdate: async () => undefined,
|
|
553
634
|
ackRestartRequest: async () => {
|
|
554
|
-
throw new Error(
|
|
555
|
-
}
|
|
635
|
+
throw new Error('network down');
|
|
636
|
+
},
|
|
556
637
|
}),
|
|
557
638
|
runCleanup: async () => undefined,
|
|
558
639
|
executeRestartRequest: () => {
|
|
@@ -564,20 +645,21 @@ test("startPolling skips restart and leaves the flag for retry when ack fails",
|
|
|
564
645
|
processExit: (() => undefined),
|
|
565
646
|
now: () => 0,
|
|
566
647
|
loadAuthPaths: () => [],
|
|
567
|
-
saveAuthPath: () =>
|
|
648
|
+
saveAuthPath: () => '/tmp/auth-paths.json',
|
|
568
649
|
keepAlive: () => new Promise((resolve) => {
|
|
569
650
|
keepAliveResolve = resolve;
|
|
570
|
-
})
|
|
651
|
+
}),
|
|
571
652
|
});
|
|
572
653
|
await new Promise((resolve) => setImmediate(resolve));
|
|
573
|
-
assert.equal(restartCalled, 0,
|
|
574
|
-
const resolveKeepAlive = keepAliveResolve ??
|
|
575
|
-
|
|
576
|
-
|
|
654
|
+
assert.equal(restartCalled, 0, 'restart must not run when ack fails');
|
|
655
|
+
const resolveKeepAlive = keepAliveResolve ??
|
|
656
|
+
(() => {
|
|
657
|
+
throw new Error('keepAlive resolver was not registered');
|
|
658
|
+
});
|
|
577
659
|
resolveKeepAlive();
|
|
578
660
|
await pollingPromise;
|
|
579
661
|
});
|
|
580
|
-
test(
|
|
662
|
+
test('startPolling acquires the power save blocker on start and releases it on shutdown', async () => {
|
|
581
663
|
const { events, blocker } = createRecordingBlocker();
|
|
582
664
|
const signals = new Map();
|
|
583
665
|
let keepAliveResolve = null;
|
|
@@ -590,7 +672,7 @@ test("startPolling acquires the power save blocker on start and releases it on s
|
|
|
590
672
|
fetchPendingWorktreeRemovals: async () => [],
|
|
591
673
|
reportWorktreeStatus: async () => undefined,
|
|
592
674
|
notifyUpdate: async () => undefined,
|
|
593
|
-
ackRestartRequest: async () => undefined
|
|
675
|
+
ackRestartRequest: async () => undefined,
|
|
594
676
|
}),
|
|
595
677
|
runCleanup: async () => undefined,
|
|
596
678
|
setInterval: (() => ({})),
|
|
@@ -601,25 +683,26 @@ test("startPolling acquires the power save blocker on start and releases it on s
|
|
|
601
683
|
processExit: (() => undefined),
|
|
602
684
|
now: () => 0,
|
|
603
685
|
loadAuthPaths: () => [],
|
|
604
|
-
saveAuthPath: () =>
|
|
686
|
+
saveAuthPath: () => '/tmp/auth-paths.json',
|
|
605
687
|
powerSaveBlocker: blocker,
|
|
606
688
|
keepAlive: () => new Promise((resolve) => {
|
|
607
689
|
keepAliveResolve = resolve;
|
|
608
|
-
})
|
|
690
|
+
}),
|
|
609
691
|
});
|
|
610
692
|
await new Promise((resolve) => setImmediate(resolve));
|
|
611
|
-
assert.deepEqual(events, [
|
|
612
|
-
const sigterm = signals.get(
|
|
693
|
+
assert.deepEqual(events, ['acquire:daemon-polling'], 'blocker should be acquired exactly once on start');
|
|
694
|
+
const sigterm = signals.get('SIGTERM');
|
|
613
695
|
assert.ok(sigterm);
|
|
614
696
|
sigterm();
|
|
615
|
-
assert.deepEqual(events, [
|
|
616
|
-
const resolveKeepAlive = keepAliveResolve ??
|
|
617
|
-
|
|
618
|
-
|
|
697
|
+
assert.deepEqual(events, ['acquire:daemon-polling', 'release:daemon-polling'], 'blocker should be released on shutdown');
|
|
698
|
+
const resolveKeepAlive = keepAliveResolve ??
|
|
699
|
+
(() => {
|
|
700
|
+
throw new Error('keepAlive resolver was not registered');
|
|
701
|
+
});
|
|
619
702
|
resolveKeepAlive();
|
|
620
703
|
await pollingPromise;
|
|
621
704
|
});
|
|
622
|
-
test(
|
|
705
|
+
test('startPolling releases the power save blocker when keepAlive resolves', async () => {
|
|
623
706
|
const { events, blocker } = createRecordingBlocker();
|
|
624
707
|
let keepAliveResolve = null;
|
|
625
708
|
const pollingPromise = startPolling(config, () => async () => undefined, {
|
|
@@ -631,7 +714,7 @@ test("startPolling releases the power save blocker when keepAlive resolves", asy
|
|
|
631
714
|
fetchPendingWorktreeRemovals: async () => [],
|
|
632
715
|
reportWorktreeStatus: async () => undefined,
|
|
633
716
|
notifyUpdate: async () => undefined,
|
|
634
|
-
ackRestartRequest: async () => undefined
|
|
717
|
+
ackRestartRequest: async () => undefined,
|
|
635
718
|
}),
|
|
636
719
|
runCleanup: async () => undefined,
|
|
637
720
|
setInterval: (() => ({})),
|
|
@@ -640,19 +723,20 @@ test("startPolling releases the power save blocker when keepAlive resolves", asy
|
|
|
640
723
|
processExit: (() => undefined),
|
|
641
724
|
now: () => 0,
|
|
642
725
|
loadAuthPaths: () => [],
|
|
643
|
-
saveAuthPath: () =>
|
|
726
|
+
saveAuthPath: () => '/tmp/auth-paths.json',
|
|
644
727
|
powerSaveBlocker: blocker,
|
|
645
728
|
keepAlive: () => new Promise((resolve) => {
|
|
646
729
|
keepAliveResolve = resolve;
|
|
647
|
-
})
|
|
730
|
+
}),
|
|
648
731
|
});
|
|
649
732
|
await new Promise((resolve) => setImmediate(resolve));
|
|
650
|
-
assert.deepEqual(events, [
|
|
651
|
-
const resolveKeepAlive = keepAliveResolve ??
|
|
652
|
-
|
|
653
|
-
|
|
733
|
+
assert.deepEqual(events, ['acquire:daemon-polling']);
|
|
734
|
+
const resolveKeepAlive = keepAliveResolve ??
|
|
735
|
+
(() => {
|
|
736
|
+
throw new Error('keepAlive resolver was not registered');
|
|
737
|
+
});
|
|
654
738
|
resolveKeepAlive();
|
|
655
739
|
await pollingPromise;
|
|
656
|
-
assert.deepEqual(events, [
|
|
740
|
+
assert.deepEqual(events, ['acquire:daemon-polling', 'release:daemon-polling'], 'blocker should be released after keepAlive resolves');
|
|
657
741
|
});
|
|
658
742
|
//# sourceMappingURL=poller.test.js.map
|