@agentteams/runner 0.0.84-dev.208 → 0.0.85

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