@agentteams/runner 0.0.85 → 0.0.86-dev.212

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 (123) 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 +63 -54
  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 +20 -22
  20. package/dist/commands/update.js.map +1 -1
  21. package/dist/commands/update.test.js +38 -42
  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 +21 -22
  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 +46 -49
  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 +118 -124
  38. package/dist/handlers/trigger-handler.js.map +1 -1
  39. package/dist/handlers/trigger-handler.test.js +373 -365
  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 +55 -54
  46. package/dist/poller.js.map +1 -1
  47. package/dist/poller.test.js +264 -180
  48. package/dist/poller.test.js.map +1 -1
  49. package/dist/runners/amp.d.ts +1 -1
  50. package/dist/runners/amp.js +74 -81
  51. package/dist/runners/amp.js.map +1 -1
  52. package/dist/runners/antigravity.d.ts +3 -3
  53. package/dist/runners/antigravity.js +116 -127
  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 +85 -84
  61. package/dist/runners/claude-code.js.map +1 -1
  62. package/dist/runners/claude-code.test.js +37 -40
  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 +87 -96
  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 +68 -75
  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 +13 -11
  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 +70 -67
  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 +76 -78
  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 +24 -29
  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 +30 -28
  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 +22 -21
  99. package/dist/utils/convention-evaluator.js.map +1 -1
  100. package/dist/utils/convention-evaluator.test.js +120 -110
  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 +86 -18
  104. package/dist/utils/convention-sync.js.map +1 -1
  105. package/dist/utils/convention-sync.test.js +122 -30
  106. package/dist/utils/convention-sync.test.js.map +1 -1
  107. package/dist/utils/git-worktree.js +20 -18
  108. package/dist/utils/git-worktree.js.map +1 -1
  109. package/dist/utils/git-worktree.test.js +98 -98
  110. package/dist/utils/git-worktree.test.js.map +1 -1
  111. package/dist/utils/origin-issue-safeguard.js +35 -30
  112. package/dist/utils/origin-issue-safeguard.js.map +1 -1
  113. package/dist/utils/power-save-blocker.d.ts +4 -4
  114. package/dist/utils/power-save-blocker.js +33 -33
  115. package/dist/utils/power-save-blocker.test.js +37 -37
  116. package/dist/utils/power-save-blocker.test.js.map +1 -1
  117. package/dist/utils/runner-cleanup.d.ts +3 -3
  118. package/dist/utils/runner-cleanup.js +13 -13
  119. package/dist/utils/runner-cleanup.test.js +35 -35
  120. package/dist/utils/runner-history.js +3 -3
  121. package/dist/utils/runner-history.test.js +17 -17
  122. package/package.json +4 -1
  123. 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
  };
@@ -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("should not exit");
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 "/tmp/auth-paths.json";
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, ["trigger-1"]);
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, ["/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");
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("startPolling logs conflicts and suppresses overlapping polling cycles", async () => {
187
+ test('startPolling logs conflicts and suppresses overlapping polling cycles', async () => {
131
188
  const infos = [];
132
- mock.method(logger, "info", (message, meta) => {
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("handler should not run");
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("should not exit");
219
+ throw new Error('should not exit');
163
220
  }),
164
221
  now: () => 0,
165
222
  loadAuthPaths: () => [],
166
- saveAuthPath: () => "/tmp/auth-paths.json",
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
- throw new Error("fetch release was not registered");
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 === "Trigger already claimed by another daemon"), true);
179
- const resolveKeepAlive = keepAliveResolve ?? (() => {
180
- throw new Error("keepAlive resolver was not registered");
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("startPolling clears the interval and exits on shutdown signals", async () => {
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: () => "/tmp/auth-paths.json",
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 !== "exit") {
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("SIGTERM"));
227
- const sigterm = signals.get("SIGTERM");
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
- throw new Error("keepAlive resolver was not registered");
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("startPolling delegates auto-update via injected dependency", async () => {
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: "99.0.0" }
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: () => "/tmp/auth-paths.json",
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, "should call injected maybeAutoUpdate on idle poll");
273
- const resolveKeepAlive = keepAliveResolve ?? (() => {
274
- throw new Error("keepAlive resolver was not registered");
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("startPolling restores persisted auth paths for worktree removals after restart", async () => {
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(), "agentrunner-poller-"));
283
- const authPath = path.join(tempRoot, "path");
284
- const worktreePath = path.join(tempRoot, ".path-worktrees", "wt-worktree-1");
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: "trigger-remove",
350
+ id: 'trigger-remove',
290
351
  useWorktree: true,
291
- worktreeId: "worktree-1",
292
- worktreeStatus: "REMOVE_REQUESTED"
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("should not exit");
377
+ throw new Error('should not exit');
317
378
  }),
318
379
  now: () => 0,
319
380
  loadAuthPaths: () => [authPath],
320
- saveAuthPath: () => "/tmp/auth-paths.json",
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: "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
- });
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("startPolling reports FAILED when no persisted auth path matches the worktree", async () => {
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: "trigger-remove-missing",
407
+ id: 'trigger-remove-missing',
344
408
  useWorktree: true,
345
- worktreeId: "worktree-missing",
346
- worktreeStatus: "REMOVE_REQUESTED"
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("removeWorktree should not be called");
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("should not exit");
434
+ throw new Error('should not exit');
371
435
  }),
372
436
  now: () => 0,
373
- loadAuthPaths: () => ["/persisted/auth/path"],
374
- saveAuthPath: () => "/tmp/auth-paths.json",
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
- 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
- });
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("startPolling reports FAILED when worktree removal throws after matching a path", async () => {
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(), "agentrunner-poller-"));
394
- const authPath = path.join(tempRoot, "path");
395
- const worktreePath = path.join(tempRoot, ".path-worktrees", "wt-worktree-failed");
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: "trigger-remove-failed",
467
+ id: 'trigger-remove-failed',
401
468
  useWorktree: true,
402
- worktreeId: "worktree-failed",
403
- worktreeStatus: "REMOVE_REQUESTED"
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("permission denied");
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("should not exit");
494
+ throw new Error('should not exit');
428
495
  }),
429
496
  now: () => 0,
430
497
  loadAuthPaths: () => [authPath],
431
- saveAuthPath: () => "/tmp/auth-paths.json",
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
- 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
- });
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("startPolling acks and runs restart when meta.restartRequested is true with no pending trigger", async () => {
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 () => ({ data: null, meta: { cliLatestVersion: null, runnerLatestVersion: null, restartRequested: true } }),
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("auto-update must be skipped while restarting");
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: () => "/tmp/auth-paths.json",
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, "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
- });
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("startPolling restarts even when a pending trigger is present, ignoring the trigger", async () => {
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("trigger handler must not run when restart wins");
573
+ throw new Error('trigger handler must not run when restart wins');
500
574
  }, {
501
575
  createClient: () => ({
502
- fetchPendingTrigger: async () => ({ data: trigger, meta: { cliLatestVersion: null, runnerLatestVersion: null, restartRequested: true } }),
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: () => "/tmp/auth-paths.json",
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, "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
- });
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("startPolling skips restart and leaves the flag for retry when ack fails", async () => {
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 () => ({ data: null, meta: { cliLatestVersion: null, runnerLatestVersion: null, restartRequested: true } }),
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("network down");
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: () => "/tmp/auth-paths.json",
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, "restart must not run when ack fails");
574
- const resolveKeepAlive = keepAliveResolve ?? (() => {
575
- throw new Error("keepAlive resolver was not registered");
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("startPolling acquires the power save blocker on start and releases it on shutdown", async () => {
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: () => "/tmp/auth-paths.json",
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, ["acquire:daemon-polling"], "blocker should be acquired exactly once on start");
612
- const sigterm = signals.get("SIGTERM");
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, ["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
- });
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("startPolling releases the power save blocker when keepAlive resolves", async () => {
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: () => "/tmp/auth-paths.json",
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, ["acquire:daemon-polling"]);
651
- const resolveKeepAlive = keepAliveResolve ?? (() => {
652
- throw new Error("keepAlive resolver was not registered");
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, ["acquire:daemon-polling", "release:daemon-polling"], "blocker should be released after keepAlive resolves");
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