@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.
Files changed (124) 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 +172 -134
  38. package/dist/handlers/trigger-handler.js.map +1 -1
  39. package/dist/handlers/trigger-handler.test.js +442 -328
  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 +12 -10
  74. package/dist/runners/log-reporter.js.map +1 -1
  75. package/dist/runners/log-reporter.test.js +70 -70
  76. package/dist/runners/log-reporter.test.js.map +1 -1
  77. package/dist/runners/opencode.d.ts +1 -1
  78. package/dist/runners/opencode.js +68 -75
  79. package/dist/runners/opencode.js.map +1 -1
  80. package/dist/runners/process-control.d.ts +1 -1
  81. package/dist/runners/process-control.js +13 -11
  82. package/dist/runners/process-control.js.map +1 -1
  83. package/dist/runners/stream-json-parser.d.ts +1 -1
  84. package/dist/runners/stream-json-parser.js +70 -67
  85. package/dist/runners/stream-json-parser.js.map +1 -1
  86. package/dist/runners/stream-json-parser.replay.test.js +17 -17
  87. package/dist/runners/stream-json-parser.replay.test.js.map +1 -1
  88. package/dist/runners/stream-json-parser.test.js +76 -78
  89. package/dist/runners/stream-json-parser.test.js.map +1 -1
  90. package/dist/types.d.ts +4 -4
  91. package/dist/utils/auth-path-store.js +7 -7
  92. package/dist/utils/auth-path-store.test.js +24 -29
  93. package/dist/utils/auth-path-store.test.js.map +1 -1
  94. package/dist/utils/auto-update.d.ts +4 -4
  95. package/dist/utils/auto-update.js +30 -28
  96. package/dist/utils/auto-update.js.map +1 -1
  97. package/dist/utils/auto-update.test.js +30 -30
  98. package/dist/utils/convention-evaluator.d.ts +1 -1
  99. package/dist/utils/convention-evaluator.js +22 -21
  100. package/dist/utils/convention-evaluator.js.map +1 -1
  101. package/dist/utils/convention-evaluator.test.js +120 -110
  102. package/dist/utils/convention-evaluator.test.js.map +1 -1
  103. package/dist/utils/convention-sync.d.ts +3 -3
  104. package/dist/utils/convention-sync.js +86 -18
  105. package/dist/utils/convention-sync.js.map +1 -1
  106. package/dist/utils/convention-sync.test.js +126 -30
  107. package/dist/utils/convention-sync.test.js.map +1 -1
  108. package/dist/utils/git-worktree.js +20 -18
  109. package/dist/utils/git-worktree.js.map +1 -1
  110. package/dist/utils/git-worktree.test.js +98 -98
  111. package/dist/utils/git-worktree.test.js.map +1 -1
  112. package/dist/utils/origin-issue-safeguard.js +35 -30
  113. package/dist/utils/origin-issue-safeguard.js.map +1 -1
  114. package/dist/utils/power-save-blocker.d.ts +4 -4
  115. package/dist/utils/power-save-blocker.js +33 -33
  116. package/dist/utils/power-save-blocker.test.js +37 -37
  117. package/dist/utils/power-save-blocker.test.js.map +1 -1
  118. package/dist/utils/runner-cleanup.d.ts +3 -3
  119. package/dist/utils/runner-cleanup.js +13 -13
  120. package/dist/utils/runner-cleanup.test.js +35 -35
  121. package/dist/utils/runner-history.js +3 -3
  122. package/dist/utils/runner-history.test.js +17 -17
  123. package/package.json +9 -2
  124. package/readme.md +10 -10
@@ -1,25 +1,25 @@
1
- import assert from "node:assert/strict";
2
- import test, { mock } from "node:test";
3
- import { mkdtemp, rm, stat } from "node:fs/promises";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
- import { logger } from "../logger.js";
7
- import { createTriggerHandler } from "./trigger-handler.js";
1
+ import assert from 'node:assert/strict';
2
+ import test, { mock } from 'node:test';
3
+ import { mkdtemp, rm, stat } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { logger } from '../logger.js';
7
+ import { createTriggerHandler } from './trigger-handler.js';
8
8
  const trigger = {
9
- id: "trigger-1",
10
- prompt: "Implement feature",
11
- runnerType: "CODEX",
12
- model: "o4-mini",
9
+ id: 'trigger-1',
10
+ prompt: 'Implement feature',
11
+ runnerType: 'CODEX',
12
+ model: 'o4-mini',
13
13
  fastMode: false,
14
- status: "PENDING",
15
- agentConfigId: "agent-1",
14
+ status: 'PENDING',
15
+ agentConfigId: 'agent-1',
16
16
  startedAt: null,
17
17
  errorMessage: null,
18
18
  worktreeError: null,
19
19
  lastHeartbeatAt: null,
20
20
  conversationId: null,
21
- parentTriggerId: "parent-1",
22
- createdByMemberId: "member-1",
21
+ parentTriggerId: 'parent-1',
22
+ createdByMemberId: 'member-1',
23
23
  planMode: false,
24
24
  targetDaemonId: null,
25
25
  claimedByDaemonId: null,
@@ -27,527 +27,631 @@ const trigger = {
27
27
  baseBranch: null,
28
28
  worktreeId: null,
29
29
  worktreeStatus: null,
30
- createdAt: "2026-01-01T00:00:00.000Z",
31
- updatedAt: "2026-01-01T00:00:00.000Z"
30
+ createdAt: '2026-01-01T00:00:00.000Z',
31
+ updatedAt: '2026-01-01T00:00:00.000Z',
32
32
  };
33
33
  const runtime = {
34
- triggerId: "trigger-1",
35
- agentConfigId: "agent-1",
36
- authPath: "/auth/path",
37
- apiKey: "api-key",
38
- teamId: "team-1",
39
- projectId: "project-1",
40
- runnerPrompt: "API runner prompt\n- History path: {{AGENTRUNNER_CURRENT_HISTORY_PATH}}\n- Previous history path: {{AGENTRUNNER_PARENT_HISTORY_PATH}}",
34
+ triggerId: 'trigger-1',
35
+ agentConfigId: 'agent-1',
36
+ authPath: '/auth/path',
37
+ apiKey: 'api-key',
38
+ teamId: 'team-1',
39
+ projectId: 'project-1',
40
+ runnerPrompt: 'API runner prompt\n- History path: {{AGENTRUNNER_CURRENT_HISTORY_PATH}}\n- Previous history path: {{AGENTRUNNER_PARENT_HISTORY_PATH}}',
41
41
  parentHistoryMarkdown: null,
42
42
  useWorktree: false,
43
43
  baseBranch: null,
44
- worktreeId: null
44
+ worktreeId: null,
45
45
  };
46
46
  test.afterEach(() => {
47
47
  mock.restoreAll();
48
48
  });
49
- test("createTriggerHandler runs the runner, reports history, and marks success", async () => {
49
+ test('createTriggerHandler runs the runner, reports history, and marks success', async () => {
50
50
  const clientCalls = [];
51
51
  const logEntries = [];
52
52
  const discoveredAuthPaths = [];
53
53
  const runnerInputs = [];
54
54
  const client = {
55
55
  fetchTriggerRuntime: async (...args) => {
56
- clientCalls.push({ method: "fetchTriggerRuntime", args });
56
+ clientCalls.push({ method: 'fetchTriggerRuntime', args });
57
57
  return runtime;
58
58
  },
59
59
  isTriggerCancelRequested: async (...args) => {
60
- clientCalls.push({ method: "isTriggerCancelRequested", args });
60
+ clientCalls.push({ method: 'isTriggerCancelRequested', args });
61
61
  return false;
62
62
  },
63
63
  updateTriggerHistory: async (...args) => {
64
- clientCalls.push({ method: "updateTriggerHistory", args });
64
+ clientCalls.push({ method: 'updateTriggerHistory', args });
65
65
  },
66
66
  updateTriggerStatus: async (...args) => {
67
- clientCalls.push({ method: "updateTriggerStatus", args });
68
- }
67
+ clientCalls.push({ method: 'updateTriggerStatus', args });
68
+ },
69
69
  };
70
70
  const runner = {
71
71
  run: async (input) => {
72
72
  runnerInputs.push({ prompt: input.prompt, authPath: input.authPath });
73
- input.onStdoutChunk?.("stdout");
74
- input.onStderrChunk?.("stderr");
73
+ input.onStdoutChunk?.('stdout');
74
+ input.onStderrChunk?.('stderr');
75
75
  return { exitCode: 0 };
76
- }
76
+ },
77
77
  };
78
78
  const handler = createTriggerHandler({
79
79
  config: {
80
- daemonToken: "daemon-token",
81
- apiUrl: "https://api.example",
80
+ daemonToken: 'daemon-token',
81
+ apiUrl: 'https://api.example',
82
82
  pollingIntervalMs: 5000,
83
83
  timeoutMs: 1500,
84
84
  idleTimeoutMs: 500,
85
- runnerCmd: "opencode",
86
- preventSleepWhileBusy: false
85
+ runnerCmd: 'opencode',
86
+ preventSleepWhileBusy: false,
87
87
  },
88
88
  client: client,
89
89
  onAuthPathDiscovered: (authPath) => {
90
90
  discoveredAuthPaths.push(authPath);
91
- }
91
+ },
92
92
  }, {
93
93
  createRunnerFactory: () => () => runner,
94
94
  createLogReporter: () => ({
95
95
  start: () => {
96
- logEntries.push({ level: "START", message: "started" });
96
+ logEntries.push({ level: 'START', message: 'started' });
97
97
  },
98
98
  append: (level, message) => {
99
99
  logEntries.push({ level, message });
100
100
  },
101
101
  stop: async () => {
102
- logEntries.push({ level: "STOP", message: "stopped" });
103
- }
102
+ logEntries.push({ level: 'STOP', message: 'stopped' });
103
+ },
104
104
  }),
105
- readHistoryFile: async () => "### Summary\n- done\n",
105
+ readHistoryFile: async () => '### Summary\n- done\n',
106
106
  resolveRunnerHistoryPaths: () => ({
107
- currentHistoryPath: "/auth/path/.agentteams/runner/history/trigger-1.md",
108
- parentHistoryPath: "/auth/path/.agentteams/runner/history/parent-1.md"
109
- })
107
+ currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
108
+ parentHistoryPath: '/auth/path/.agentteams/runner/history/parent-1.md',
109
+ }),
110
110
  });
111
111
  await handler(trigger);
112
- assert.deepEqual(discoveredAuthPaths, ["/auth/path"]);
112
+ assert.deepEqual(discoveredAuthPaths, ['/auth/path']);
113
113
  assert.equal(runnerInputs.length, 1);
114
- assert.equal(runnerInputs[0]?.prompt, "API runner prompt\n- History path: /auth/path/.agentteams/runner/history/trigger-1.md\n- Previous history path: /auth/path/.agentteams/runner/history/parent-1.md");
115
- assert.equal(logEntries.some((entry) => entry.level === "INFO" && entry.message.includes("stdout")), true);
116
- assert.equal(logEntries.some((entry) => entry.level === "WARN" && entry.message.includes("stderr")), true);
117
- assert.deepEqual(clientCalls.map((entry) => entry.method), [
118
- "fetchTriggerRuntime",
119
- "isTriggerCancelRequested",
120
- "updateTriggerHistory",
121
- "updateTriggerStatus"
122
- ]);
123
- assert.deepEqual(clientCalls.at(-1)?.args, ["trigger-1", "DONE", undefined]);
114
+ assert.equal(runnerInputs[0]?.prompt, 'API runner prompt\n- History path: /auth/path/.agentteams/runner/history/trigger-1.md\n- Previous history path: /auth/path/.agentteams/runner/history/parent-1.md');
115
+ assert.equal(logEntries.some((entry) => entry.level === 'INFO' && entry.message.includes('stdout')), true);
116
+ assert.equal(logEntries.some((entry) => entry.level === 'WARN' && entry.message.includes('stderr')), true);
117
+ assert.deepEqual(clientCalls.map((entry) => entry.method), ['fetchTriggerRuntime', 'isTriggerCancelRequested', 'updateTriggerHistory', 'updateTriggerStatus']);
118
+ assert.deepEqual(clientCalls.at(-1)?.args, ['trigger-1', 'DONE', undefined]);
124
119
  });
125
- test("createTriggerHandler strips a UTF-8 BOM before reporting history to the database", async () => {
120
+ test('createTriggerHandler preserves the local history file and still marks success when history upload fails', async () => {
126
121
  const clientCalls = [];
122
+ const writeHistoryCalls = [];
127
123
  const client = {
128
124
  fetchTriggerRuntime: async () => runtime,
129
125
  isTriggerCancelRequested: async () => false,
130
126
  updateTriggerHistory: async (...args) => {
131
- clientCalls.push({ method: "updateTriggerHistory", args });
127
+ clientCalls.push({ method: 'updateTriggerHistory', args });
128
+ // 서버가 보고를 거부하는 상황(예: 공유 러너 404, 일시적 네트워크 오류)을 모사.
129
+ throw new Error('Failed to update trigger history (404)');
132
130
  },
133
131
  updateTriggerStatus: async (...args) => {
134
- clientCalls.push({ method: "updateTriggerStatus", args });
135
- }
132
+ clientCalls.push({ method: 'updateTriggerStatus', args });
133
+ },
136
134
  };
137
135
  const handler = createTriggerHandler({
138
136
  config: {
139
- daemonToken: "daemon-token",
140
- apiUrl: "https://api.example",
137
+ daemonToken: 'daemon-token',
138
+ apiUrl: 'https://api.example',
141
139
  pollingIntervalMs: 5000,
142
140
  timeoutMs: 1500,
143
141
  idleTimeoutMs: 500,
144
- runnerCmd: "opencode",
145
- preventSleepWhileBusy: false
142
+ runnerCmd: 'opencode',
143
+ preventSleepWhileBusy: false,
146
144
  },
147
- client: client
145
+ client: client,
148
146
  }, {
149
147
  createRunnerFactory: () => () => ({
150
- run: async () => ({ exitCode: 0 })
148
+ run: async () => ({ exitCode: 0, outputText: '{"type":"result"}' }),
151
149
  }),
152
150
  createLogReporter: () => ({
153
151
  start: () => undefined,
154
152
  append: () => undefined,
155
- stop: async () => undefined
153
+ stop: async () => undefined,
154
+ }),
155
+ // 러너가 정상 히스토리 파일을 작성한 상태(Questions 섹션 포함 → 정규화 없음).
156
+ readHistoryFile: async () => '### Summary\n- done\n\n### Questions for User\nNone',
157
+ writeHistoryFile: async (path, content) => {
158
+ writeHistoryCalls.push({ path, content });
159
+ },
160
+ resolveRunnerHistoryPaths: () => ({
161
+ currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
162
+ parentHistoryPath: null,
163
+ }),
164
+ });
165
+ await handler(trigger);
166
+ // 업로드는 1회 시도됐고(실패), 핵심: 좋은 히스토리 파일을 stdout 폴백으로 덮어쓰지 않는다.
167
+ assert.equal(clientCalls.filter((entry) => entry.method === 'updateTriggerHistory').length, 1);
168
+ assert.deepEqual(writeHistoryCalls, []);
169
+ // 업로드 실패가 러너 성공을 뒤집지 않고 DONE으로 보고된다(FAILED 아님).
170
+ const statusCall = clientCalls.find((entry) => entry.method === 'updateTriggerStatus');
171
+ assert.deepEqual(statusCall?.args, ['trigger-1', 'DONE', undefined]);
172
+ });
173
+ test('createTriggerHandler strips a UTF-8 BOM before reporting history to the database', async () => {
174
+ const clientCalls = [];
175
+ const logEntries = [];
176
+ const client = {
177
+ fetchTriggerRuntime: async () => runtime,
178
+ isTriggerCancelRequested: async () => false,
179
+ updateTriggerHistory: async (...args) => {
180
+ clientCalls.push({ method: 'updateTriggerHistory', args });
181
+ },
182
+ updateTriggerStatus: async (...args) => {
183
+ clientCalls.push({ method: 'updateTriggerStatus', args });
184
+ },
185
+ };
186
+ const handler = createTriggerHandler({
187
+ config: {
188
+ daemonToken: 'daemon-token',
189
+ apiUrl: 'https://api.example',
190
+ pollingIntervalMs: 5000,
191
+ timeoutMs: 1500,
192
+ idleTimeoutMs: 500,
193
+ runnerCmd: 'opencode',
194
+ preventSleepWhileBusy: false,
195
+ },
196
+ client: client,
197
+ }, {
198
+ createRunnerFactory: () => () => ({
199
+ run: async () => ({ exitCode: 0 }),
200
+ }),
201
+ createLogReporter: () => ({
202
+ start: () => undefined,
203
+ append: (level, message) => {
204
+ logEntries.push({ level, message });
205
+ },
206
+ stop: async () => undefined,
207
+ }),
208
+ readHistoryFile: async () => '\uFEFF### Summary\n- done\n',
209
+ resolveRunnerHistoryPaths: () => ({
210
+ currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
211
+ parentHistoryPath: '/auth/path/.agentteams/runner/history/parent-1.md',
212
+ }),
213
+ });
214
+ await handler(trigger);
215
+ // BOM stripped, and the missing Questions for User section is appended by normalization
216
+ assert.deepEqual(clientCalls.at(0)?.args, ['trigger-1', '### Summary\n- done\n\n### Questions for User\nNone']);
217
+ // observation signal: normalization is surfaced to the trigger log (web UI logs tab)
218
+ assert.ok(logEntries.some((entry) => entry.level === 'WARN' && entry.message.includes('Questions for User')));
219
+ });
220
+ test('createTriggerHandler keeps history unchanged when the Questions for User section exists', async () => {
221
+ const clientCalls = [];
222
+ const logEntries = [];
223
+ const client = {
224
+ fetchTriggerRuntime: async () => runtime,
225
+ isTriggerCancelRequested: async () => false,
226
+ updateTriggerHistory: async (...args) => {
227
+ clientCalls.push({ method: 'updateTriggerHistory', args });
228
+ },
229
+ updateTriggerStatus: async (...args) => {
230
+ clientCalls.push({ method: 'updateTriggerStatus', args });
231
+ },
232
+ };
233
+ const historyWithQuestions = '### Summary\n- done\n\n### Questions for User\n- Should we ship this now?';
234
+ const handler = createTriggerHandler({
235
+ config: {
236
+ daemonToken: 'daemon-token',
237
+ apiUrl: 'https://api.example',
238
+ pollingIntervalMs: 5000,
239
+ timeoutMs: 1500,
240
+ idleTimeoutMs: 500,
241
+ runnerCmd: 'opencode',
242
+ preventSleepWhileBusy: false,
243
+ },
244
+ client: client,
245
+ }, {
246
+ createRunnerFactory: () => () => ({
247
+ run: async () => ({ exitCode: 0 }),
248
+ }),
249
+ createLogReporter: () => ({
250
+ start: () => undefined,
251
+ append: (level, message) => {
252
+ logEntries.push({ level, message });
253
+ },
254
+ stop: async () => undefined,
156
255
  }),
157
- readHistoryFile: async () => "\uFEFF### Summary\n- done\n",
256
+ readHistoryFile: async () => historyWithQuestions,
158
257
  resolveRunnerHistoryPaths: () => ({
159
- currentHistoryPath: "/auth/path/.agentteams/runner/history/trigger-1.md",
160
- parentHistoryPath: "/auth/path/.agentteams/runner/history/parent-1.md"
161
- })
258
+ currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
259
+ parentHistoryPath: '/auth/path/.agentteams/runner/history/parent-1.md',
260
+ }),
162
261
  });
163
262
  await handler(trigger);
164
- assert.deepEqual(clientCalls.at(0)?.args, ["trigger-1", "### Summary\n- done"]);
263
+ assert.deepEqual(clientCalls.at(0)?.args, ['trigger-1', historyWithQuestions]);
264
+ // no normalization → no observation warning
265
+ assert.ok(!logEntries.some((entry) => entry.message.includes('Questions for User')));
165
266
  });
166
- test("createTriggerHandler restores parent history from server-side coaction content", async () => {
267
+ test('createTriggerHandler restores parent history from server-side coaction content', async () => {
167
268
  const runnerInputs = [];
168
269
  const writtenFiles = [];
169
270
  const client = {
170
271
  fetchTriggerRuntime: async () => ({
171
272
  ...runtime,
172
- parentHistoryMarkdown: "### Summary\n- restored from coaction\n"
273
+ parentHistoryMarkdown: '### Summary\n- restored from coaction\n',
173
274
  }),
174
275
  isTriggerCancelRequested: async () => false,
175
276
  updateTriggerHistory: async () => undefined,
176
- updateTriggerStatus: async () => undefined
277
+ updateTriggerStatus: async () => undefined,
177
278
  };
178
279
  const handler = createTriggerHandler({
179
280
  config: {
180
- daemonToken: "daemon-token",
181
- apiUrl: "https://api.example",
281
+ daemonToken: 'daemon-token',
282
+ apiUrl: 'https://api.example',
182
283
  pollingIntervalMs: 5000,
183
284
  timeoutMs: 1500,
184
285
  idleTimeoutMs: 500,
185
- runnerCmd: "opencode",
186
- preventSleepWhileBusy: false
286
+ runnerCmd: 'opencode',
287
+ preventSleepWhileBusy: false,
187
288
  },
188
- client: client
289
+ client: client,
189
290
  }, {
190
291
  createRunnerFactory: () => () => ({
191
292
  run: async (input) => {
192
293
  runnerInputs.push({ prompt: input.prompt, authPath: input.authPath });
193
294
  return { exitCode: 0 };
194
- }
295
+ },
195
296
  }),
196
297
  createLogReporter: () => ({
197
298
  start: () => undefined,
198
299
  append: () => undefined,
199
- stop: async () => undefined
300
+ stop: async () => undefined,
200
301
  }),
201
302
  readHistoryFile: async (path) => {
202
- if (String(path).endsWith("parent-1.md")) {
203
- throw new Error("ENOENT");
303
+ if (String(path).endsWith('parent-1.md')) {
304
+ throw new Error('ENOENT');
204
305
  }
205
- return "### Summary\n- current\n";
306
+ return '### Summary\n- current\n';
206
307
  },
207
308
  writeHistoryFile: async (path, content) => {
208
309
  writtenFiles.push({ path, content });
209
310
  },
210
311
  resolveRunnerHistoryPaths: () => ({
211
- currentHistoryPath: "/auth/path/.agentteams/runner/history/trigger-1.md",
212
- parentHistoryPath: "/auth/path/.agentteams/runner/history/parent-1.md"
213
- })
312
+ currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
313
+ parentHistoryPath: '/auth/path/.agentteams/runner/history/parent-1.md',
314
+ }),
214
315
  });
215
316
  await handler(trigger);
216
317
  assert.equal(runnerInputs.length, 1);
217
- assert.deepEqual(writtenFiles, [{
218
- path: "/auth/path/.agentteams/runner/history/parent-1.md",
219
- content: "### Summary\n- restored from coaction"
220
- }]);
318
+ assert.deepEqual(writtenFiles, [
319
+ {
320
+ path: '/auth/path/.agentteams/runner/history/parent-1.md',
321
+ content: '### Summary\n- restored from coaction',
322
+ },
323
+ ]);
221
324
  });
222
- test("createTriggerHandler strips a UTF-8 BOM before restoring parent history from the server", async () => {
325
+ test('createTriggerHandler strips a UTF-8 BOM before restoring parent history from the server', async () => {
223
326
  const writtenFiles = [];
224
327
  const client = {
225
328
  fetchTriggerRuntime: async () => ({
226
329
  ...runtime,
227
- parentHistoryMarkdown: "\uFEFF### Summary\n- restored from coaction\n"
330
+ parentHistoryMarkdown: '\uFEFF### Summary\n- restored from coaction\n',
228
331
  }),
229
332
  isTriggerCancelRequested: async () => false,
230
333
  updateTriggerHistory: async () => undefined,
231
- updateTriggerStatus: async () => undefined
334
+ updateTriggerStatus: async () => undefined,
232
335
  };
233
336
  const handler = createTriggerHandler({
234
337
  config: {
235
- daemonToken: "daemon-token",
236
- apiUrl: "https://api.example",
338
+ daemonToken: 'daemon-token',
339
+ apiUrl: 'https://api.example',
237
340
  pollingIntervalMs: 5000,
238
341
  timeoutMs: 1500,
239
342
  idleTimeoutMs: 500,
240
- runnerCmd: "opencode",
241
- preventSleepWhileBusy: false
343
+ runnerCmd: 'opencode',
344
+ preventSleepWhileBusy: false,
242
345
  },
243
- client: client
346
+ client: client,
244
347
  }, {
245
348
  createRunnerFactory: () => () => ({
246
- run: async () => ({ exitCode: 0 })
349
+ run: async () => ({ exitCode: 0 }),
247
350
  }),
248
351
  createLogReporter: () => ({
249
352
  start: () => undefined,
250
353
  append: () => undefined,
251
- stop: async () => undefined
354
+ stop: async () => undefined,
252
355
  }),
253
- readHistoryFile: async () => "### Summary\n- current\n",
356
+ readHistoryFile: async () => '### Summary\n- current\n',
254
357
  writeHistoryFile: async (path, content) => {
255
358
  writtenFiles.push({ path, content });
256
359
  },
257
360
  resolveRunnerHistoryPaths: () => ({
258
- currentHistoryPath: "/auth/path/.agentteams/runner/history/trigger-1.md",
259
- parentHistoryPath: "/auth/path/.agentteams/runner/history/parent-1.md"
260
- })
361
+ currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
362
+ parentHistoryPath: '/auth/path/.agentteams/runner/history/parent-1.md',
363
+ }),
261
364
  });
262
365
  await handler(trigger);
263
- assert.deepEqual(writtenFiles, [{
264
- path: "/auth/path/.agentteams/runner/history/parent-1.md",
265
- content: "### Summary\n- restored from coaction"
266
- }]);
366
+ assert.deepEqual(writtenFiles, [
367
+ {
368
+ path: '/auth/path/.agentteams/runner/history/parent-1.md',
369
+ content: '### Summary\n- restored from coaction',
370
+ },
371
+ ]);
267
372
  });
268
- test("createTriggerHandler overwrites existing parent history with server cumulative context", async () => {
373
+ test('createTriggerHandler overwrites existing parent history with server cumulative context', async () => {
269
374
  const writtenFiles = [];
270
375
  const client = {
271
376
  fetchTriggerRuntime: async () => ({
272
377
  ...runtime,
273
- parentHistoryMarkdown: "### Summary\n- cumulative from server\n"
378
+ parentHistoryMarkdown: '### Summary\n- cumulative from server\n',
274
379
  }),
275
380
  isTriggerCancelRequested: async () => false,
276
381
  updateTriggerHistory: async () => undefined,
277
- updateTriggerStatus: async () => undefined
382
+ updateTriggerStatus: async () => undefined,
278
383
  };
279
384
  const handler = createTriggerHandler({
280
385
  config: {
281
- daemonToken: "daemon-token",
282
- apiUrl: "https://api.example",
386
+ daemonToken: 'daemon-token',
387
+ apiUrl: 'https://api.example',
283
388
  pollingIntervalMs: 5000,
284
389
  timeoutMs: 1500,
285
390
  idleTimeoutMs: 500,
286
- runnerCmd: "opencode",
287
- preventSleepWhileBusy: false
391
+ runnerCmd: 'opencode',
392
+ preventSleepWhileBusy: false,
288
393
  },
289
- client: client
394
+ client: client,
290
395
  }, {
291
396
  createRunnerFactory: () => () => ({
292
- run: async () => ({ exitCode: 0 })
397
+ run: async () => ({ exitCode: 0 }),
293
398
  }),
294
399
  createLogReporter: () => ({
295
400
  start: () => undefined,
296
401
  append: () => undefined,
297
- stop: async () => undefined
402
+ stop: async () => undefined,
298
403
  }),
299
- readHistoryFile: async () => "### Summary\n- stale local parent history\n",
404
+ readHistoryFile: async () => '### Summary\n- stale local parent history\n',
300
405
  writeHistoryFile: async (path, content) => {
301
406
  writtenFiles.push({ path, content });
302
407
  },
303
408
  resolveRunnerHistoryPaths: () => ({
304
- currentHistoryPath: "/auth/path/.agentteams/runner/history/trigger-1.md",
305
- parentHistoryPath: "/auth/path/.agentteams/runner/history/parent-1.md"
306
- })
409
+ currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
410
+ parentHistoryPath: '/auth/path/.agentteams/runner/history/parent-1.md',
411
+ }),
307
412
  });
308
413
  await handler(trigger);
309
- assert.deepEqual(writtenFiles, [{
310
- path: "/auth/path/.agentteams/runner/history/parent-1.md",
311
- content: "### Summary\n- cumulative from server"
312
- }]);
414
+ assert.deepEqual(writtenFiles, [
415
+ {
416
+ path: '/auth/path/.agentteams/runner/history/parent-1.md',
417
+ content: '### Summary\n- cumulative from server',
418
+ },
419
+ ]);
313
420
  });
314
- test("createTriggerHandler reports runner failures and falls back to last output", async () => {
421
+ test('createTriggerHandler reports runner failures and falls back to last output', async () => {
315
422
  const clientCalls = [];
316
423
  const client = {
317
424
  fetchTriggerRuntime: async () => runtime,
318
425
  isTriggerCancelRequested: async () => false,
319
426
  updateTriggerHistory: async (...args) => {
320
- clientCalls.push({ method: "updateTriggerHistory", args });
427
+ clientCalls.push({ method: 'updateTriggerHistory', args });
321
428
  },
322
429
  updateTriggerStatus: async (...args) => {
323
- clientCalls.push({ method: "updateTriggerStatus", args });
324
- }
430
+ clientCalls.push({ method: 'updateTriggerStatus', args });
431
+ },
325
432
  };
326
433
  const handler = createTriggerHandler({
327
434
  config: {
328
- daemonToken: "daemon-token",
329
- apiUrl: "https://api.example",
435
+ daemonToken: 'daemon-token',
436
+ apiUrl: 'https://api.example',
330
437
  pollingIntervalMs: 5000,
331
438
  timeoutMs: 1500,
332
439
  idleTimeoutMs: 500,
333
- runnerCmd: "opencode",
334
- preventSleepWhileBusy: false
440
+ runnerCmd: 'opencode',
441
+ preventSleepWhileBusy: false,
335
442
  },
336
- client: client
443
+ client: client,
337
444
  }, {
338
445
  createRunnerFactory: () => () => ({
339
- run: async () => ({ exitCode: 1, lastOutput: "last output" })
446
+ run: async () => ({ exitCode: 1, lastOutput: 'last output' }),
340
447
  }),
341
448
  createLogReporter: () => ({
342
449
  start: () => undefined,
343
450
  append: () => undefined,
344
- stop: async () => undefined
451
+ stop: async () => undefined,
345
452
  }),
346
- readHistoryFile: async () => "",
453
+ readHistoryFile: async () => '',
347
454
  resolveRunnerHistoryPaths: () => ({
348
- currentHistoryPath: "/auth/path/.agentteams/runner/history/trigger-1.md",
349
- parentHistoryPath: null
350
- })
455
+ currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
456
+ parentHistoryPath: null,
457
+ }),
351
458
  });
352
459
  await handler({ ...trigger, parentTriggerId: null });
353
- assert.deepEqual(clientCalls.at(-1)?.args, ["trigger-1", "FAILED", "last output"]);
460
+ assert.deepEqual(clientCalls.at(-1)?.args, ['trigger-1', 'FAILED', 'last output']);
354
461
  });
355
- test("createTriggerHandler stores stdout as fallback history when the runner omits the history file", async () => {
462
+ test('createTriggerHandler stores stdout as fallback history when the runner omits the history file', async () => {
356
463
  const clientCalls = [];
357
464
  const writtenFiles = [];
358
465
  const client = {
359
466
  fetchTriggerRuntime: async () => runtime,
360
467
  isTriggerCancelRequested: async () => false,
361
468
  updateTriggerHistory: async (...args) => {
362
- clientCalls.push({ method: "updateTriggerHistory", args });
469
+ clientCalls.push({ method: 'updateTriggerHistory', args });
363
470
  },
364
471
  updateTriggerStatus: async (...args) => {
365
- clientCalls.push({ method: "updateTriggerStatus", args });
366
- }
472
+ clientCalls.push({ method: 'updateTriggerStatus', args });
473
+ },
367
474
  };
368
475
  const handler = createTriggerHandler({
369
476
  config: {
370
- daemonToken: "daemon-token",
371
- apiUrl: "https://api.example",
477
+ daemonToken: 'daemon-token',
478
+ apiUrl: 'https://api.example',
372
479
  pollingIntervalMs: 5000,
373
480
  timeoutMs: 1500,
374
481
  idleTimeoutMs: 500,
375
- runnerCmd: "opencode",
376
- preventSleepWhileBusy: false
482
+ runnerCmd: 'opencode',
483
+ preventSleepWhileBusy: false,
377
484
  },
378
- client: client
485
+ client: client,
379
486
  }, {
380
487
  createRunnerFactory: () => () => ({
381
488
  run: async () => ({
382
489
  exitCode: 0,
383
- outputText: "agentrunner version 0.0.11"
384
- })
490
+ outputText: 'agentrunner version 0.0.11',
491
+ }),
385
492
  }),
386
493
  createLogReporter: () => ({
387
494
  start: () => undefined,
388
495
  append: () => undefined,
389
- stop: async () => undefined
496
+ stop: async () => undefined,
390
497
  }),
391
498
  writeHistoryFile: async (path, content) => {
392
499
  writtenFiles.push({ path, content });
393
500
  },
394
501
  readHistoryFile: async () => {
395
- throw new Error("ENOENT");
502
+ throw new Error('ENOENT');
396
503
  },
397
504
  resolveRunnerHistoryPaths: () => ({
398
- currentHistoryPath: "/auth/path/.agentteams/runner/history/trigger-1.md",
399
- parentHistoryPath: null
400
- })
505
+ currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
506
+ parentHistoryPath: null,
507
+ }),
401
508
  });
402
509
  await handler({ ...trigger, parentTriggerId: null });
403
- assert.deepEqual(clientCalls.map((entry) => entry.method), [
404
- "updateTriggerHistory",
405
- "updateTriggerStatus"
406
- ]);
510
+ assert.deepEqual(clientCalls.map((entry) => entry.method), ['updateTriggerHistory', 'updateTriggerStatus']);
407
511
  assert.equal(writtenFiles.length, 1);
408
- assert.equal(writtenFiles[0]?.path, "/auth/path/.agentteams/runner/history/trigger-1.md");
512
+ assert.equal(writtenFiles[0]?.path, '/auth/path/.agentteams/runner/history/trigger-1.md');
409
513
  assert.match(String(clientCalls[0]?.args[1]), /Agent output \(history file not written\)/);
410
514
  assert.match(String(clientCalls[0]?.args[1]), /agentrunner version 0\.0\.11/);
411
- assert.deepEqual(clientCalls.at(-1)?.args, ["trigger-1", "DONE", undefined]);
515
+ assert.deepEqual(clientCalls.at(-1)?.args, ['trigger-1', 'DONE', undefined]);
412
516
  });
413
- test("createTriggerHandler truncates long agent output in fallback history", async () => {
517
+ test('createTriggerHandler truncates long agent output in fallback history', async () => {
414
518
  const clientCalls = [];
415
519
  const writtenFiles = [];
416
- const longOutput = "x".repeat(9000);
520
+ const longOutput = 'x'.repeat(9000);
417
521
  const client = {
418
522
  fetchTriggerRuntime: async () => runtime,
419
523
  isTriggerCancelRequested: async () => false,
420
524
  updateTriggerHistory: async (...args) => {
421
- clientCalls.push({ method: "updateTriggerHistory", args });
525
+ clientCalls.push({ method: 'updateTriggerHistory', args });
422
526
  },
423
527
  updateTriggerStatus: async (...args) => {
424
- clientCalls.push({ method: "updateTriggerStatus", args });
425
- }
528
+ clientCalls.push({ method: 'updateTriggerStatus', args });
529
+ },
426
530
  };
427
531
  const handler = createTriggerHandler({
428
532
  config: {
429
- daemonToken: "daemon-token",
430
- apiUrl: "https://api.example",
533
+ daemonToken: 'daemon-token',
534
+ apiUrl: 'https://api.example',
431
535
  pollingIntervalMs: 5000,
432
536
  timeoutMs: 1500,
433
537
  idleTimeoutMs: 500,
434
- runnerCmd: "opencode",
435
- preventSleepWhileBusy: false
538
+ runnerCmd: 'opencode',
539
+ preventSleepWhileBusy: false,
436
540
  },
437
- client: client
541
+ client: client,
438
542
  }, {
439
543
  createRunnerFactory: () => () => ({
440
544
  run: async () => ({
441
545
  exitCode: 0,
442
- outputText: longOutput
443
- })
546
+ outputText: longOutput,
547
+ }),
444
548
  }),
445
549
  createLogReporter: () => ({
446
550
  start: () => undefined,
447
551
  append: () => undefined,
448
- stop: async () => undefined
552
+ stop: async () => undefined,
449
553
  }),
450
554
  writeHistoryFile: async (path, content) => {
451
555
  writtenFiles.push({ path, content });
452
556
  },
453
557
  readHistoryFile: async () => {
454
- throw new Error("ENOENT");
558
+ throw new Error('ENOENT');
455
559
  },
456
560
  resolveRunnerHistoryPaths: () => ({
457
- currentHistoryPath: "/auth/path/.agentteams/runner/history/trigger-1.md",
458
- parentHistoryPath: null
459
- })
561
+ currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
562
+ parentHistoryPath: null,
563
+ }),
460
564
  });
461
565
  await handler({ ...trigger, parentTriggerId: null });
462
566
  const fallbackContent = String(clientCalls[0]?.args[1]);
463
567
  assert.match(fallbackContent, /\*\(truncated\)\*/);
464
- assert.ok(fallbackContent.length < 9000, "Fallback history should be truncated");
465
- assert.ok(fallbackContent.includes("x".repeat(8000)), "Fallback history should retain output up to the 8,000 character limit");
568
+ assert.ok(fallbackContent.length < 9000, 'Fallback history should be truncated');
569
+ assert.ok(fallbackContent.includes('x'.repeat(8000)), 'Fallback history should retain output up to the 8,000 character limit');
466
570
  });
467
- test("createTriggerHandler preserves fallback agent output within history limit", async () => {
571
+ test('createTriggerHandler preserves fallback agent output within history limit', async () => {
468
572
  const clientCalls = [];
469
573
  const writtenFiles = [];
470
- const output = "x".repeat(8000);
574
+ const output = 'x'.repeat(8000);
471
575
  const client = {
472
576
  fetchTriggerRuntime: async () => runtime,
473
577
  isTriggerCancelRequested: async () => false,
474
578
  updateTriggerHistory: async (...args) => {
475
- clientCalls.push({ method: "updateTriggerHistory", args });
579
+ clientCalls.push({ method: 'updateTriggerHistory', args });
476
580
  },
477
581
  updateTriggerStatus: async (...args) => {
478
- clientCalls.push({ method: "updateTriggerStatus", args });
479
- }
582
+ clientCalls.push({ method: 'updateTriggerStatus', args });
583
+ },
480
584
  };
481
585
  const handler = createTriggerHandler({
482
586
  config: {
483
- daemonToken: "daemon-token",
484
- apiUrl: "https://api.example",
587
+ daemonToken: 'daemon-token',
588
+ apiUrl: 'https://api.example',
485
589
  pollingIntervalMs: 5000,
486
590
  timeoutMs: 1500,
487
591
  idleTimeoutMs: 500,
488
- runnerCmd: "opencode",
489
- preventSleepWhileBusy: false
592
+ runnerCmd: 'opencode',
593
+ preventSleepWhileBusy: false,
490
594
  },
491
- client: client
595
+ client: client,
492
596
  }, {
493
597
  createRunnerFactory: () => () => ({
494
598
  run: async () => ({
495
599
  exitCode: 0,
496
- outputText: output
497
- })
600
+ outputText: output,
601
+ }),
498
602
  }),
499
603
  createLogReporter: () => ({
500
604
  start: () => undefined,
501
605
  append: () => undefined,
502
- stop: async () => undefined
606
+ stop: async () => undefined,
503
607
  }),
504
608
  writeHistoryFile: async (path, content) => {
505
609
  writtenFiles.push({ path, content });
506
610
  },
507
611
  readHistoryFile: async () => {
508
- throw new Error("ENOENT");
612
+ throw new Error('ENOENT');
509
613
  },
510
614
  resolveRunnerHistoryPaths: () => ({
511
- currentHistoryPath: "/auth/path/.agentteams/runner/history/trigger-1.md",
512
- parentHistoryPath: null
513
- })
615
+ currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
616
+ parentHistoryPath: null,
617
+ }),
514
618
  });
515
619
  await handler({ ...trigger, parentTriggerId: null });
516
620
  const fallbackContent = String(clientCalls[0]?.args[1]);
517
621
  assert.doesNotMatch(fallbackContent, /\*\(truncated\)\*/);
518
622
  assert.match(fallbackContent, new RegExp(`x{${output.length}}`));
519
623
  });
520
- test("createTriggerHandler cancels the runner when the server reports a cancel request", async () => {
624
+ test('createTriggerHandler cancels the runner when the server reports a cancel request', async () => {
521
625
  const clientCalls = [];
522
626
  const client = {
523
627
  fetchTriggerRuntime: async () => runtime,
524
628
  isTriggerCancelRequested: async (...args) => {
525
- clientCalls.push({ method: "isTriggerCancelRequested", args });
629
+ clientCalls.push({ method: 'isTriggerCancelRequested', args });
526
630
  return true;
527
631
  },
528
632
  updateTriggerHistory: async (...args) => {
529
- clientCalls.push({ method: "updateTriggerHistory", args });
633
+ clientCalls.push({ method: 'updateTriggerHistory', args });
530
634
  },
531
635
  updateTriggerStatus: async (...args) => {
532
- clientCalls.push({ method: "updateTriggerStatus", args });
533
- }
636
+ clientCalls.push({ method: 'updateTriggerStatus', args });
637
+ },
534
638
  };
535
639
  const handler = createTriggerHandler({
536
640
  config: {
537
- daemonToken: "daemon-token",
538
- apiUrl: "https://api.example",
641
+ daemonToken: 'daemon-token',
642
+ apiUrl: 'https://api.example',
539
643
  pollingIntervalMs: 5000,
540
644
  timeoutMs: 1500,
541
645
  idleTimeoutMs: 500,
542
- runnerCmd: "opencode",
543
- preventSleepWhileBusy: false
646
+ runnerCmd: 'opencode',
647
+ preventSleepWhileBusy: false,
544
648
  },
545
- client: client
649
+ client: client,
546
650
  }, {
547
651
  createRunnerFactory: () => () => ({
548
652
  run: async ({ signal }) => {
549
653
  await new Promise((resolve) => {
550
- signal?.addEventListener("abort", () => resolve(), { once: true });
654
+ signal?.addEventListener('abort', () => resolve(), { once: true });
551
655
  if (signal?.aborted) {
552
656
  resolve();
553
657
  }
@@ -555,70 +659,70 @@ test("createTriggerHandler cancels the runner when the server reports a cancel r
555
659
  return {
556
660
  exitCode: 1,
557
661
  cancelled: true,
558
- errorMessage: "Runner cancelled by user"
662
+ errorMessage: 'Runner cancelled by user',
559
663
  };
560
- }
664
+ },
561
665
  }),
562
666
  createLogReporter: () => ({
563
667
  start: () => undefined,
564
668
  append: () => undefined,
565
- stop: async () => undefined
669
+ stop: async () => undefined,
566
670
  }),
567
- readHistoryFile: async () => "",
671
+ readHistoryFile: async () => '',
568
672
  cancelPollIntervalMs: 1,
569
673
  resolveRunnerHistoryPaths: () => ({
570
- currentHistoryPath: "/auth/path/.agentteams/runner/history/trigger-1.md",
571
- parentHistoryPath: null
572
- })
674
+ currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
675
+ parentHistoryPath: null,
676
+ }),
573
677
  });
574
678
  await handler({ ...trigger, parentTriggerId: null });
575
- assert.deepEqual(clientCalls.at(-1)?.args, ["trigger-1", "CANCELLED", "Runner cancelled by user"]);
679
+ assert.deepEqual(clientCalls.at(-1)?.args, ['trigger-1', 'CANCELLED', 'Runner cancelled by user']);
576
680
  });
577
- test("createTriggerHandler marks the trigger as failed when runtime loading throws", async () => {
681
+ test('createTriggerHandler marks the trigger as failed when runtime loading throws', async () => {
578
682
  const errors = [];
579
- mock.method(logger, "error", (message, meta) => {
683
+ mock.method(logger, 'error', (message, meta) => {
580
684
  errors.push({ message, meta });
581
685
  });
582
686
  const clientCalls = [];
583
687
  const client = {
584
688
  fetchTriggerRuntime: async () => {
585
- throw new Error("runtime boom");
689
+ throw new Error('runtime boom');
586
690
  },
587
691
  isTriggerCancelRequested: async () => false,
588
692
  updateTriggerHistory: async (...args) => {
589
- clientCalls.push({ method: "updateTriggerHistory", args });
693
+ clientCalls.push({ method: 'updateTriggerHistory', args });
590
694
  },
591
695
  updateTriggerStatus: async (...args) => {
592
- clientCalls.push({ method: "updateTriggerStatus", args });
593
- }
696
+ clientCalls.push({ method: 'updateTriggerStatus', args });
697
+ },
594
698
  };
595
699
  const handler = createTriggerHandler({
596
700
  config: {
597
- daemonToken: "daemon-token",
598
- apiUrl: "https://api.example",
701
+ daemonToken: 'daemon-token',
702
+ apiUrl: 'https://api.example',
599
703
  pollingIntervalMs: 5000,
600
704
  timeoutMs: 1500,
601
705
  idleTimeoutMs: 500,
602
- runnerCmd: "opencode",
603
- preventSleepWhileBusy: false
706
+ runnerCmd: 'opencode',
707
+ preventSleepWhileBusy: false,
604
708
  },
605
- client: client
709
+ client: client,
606
710
  }, {
607
711
  createLogReporter: () => ({
608
712
  start: () => undefined,
609
713
  append: () => undefined,
610
- stop: async () => undefined
611
- })
714
+ stop: async () => undefined,
715
+ }),
612
716
  });
613
717
  await handler({ ...trigger, parentTriggerId: null });
614
- assert.deepEqual(clientCalls.at(-1)?.args, ["trigger-1", "FAILED", "runtime boom"]);
615
- assert.equal(errors.some((entry) => entry.message === "Trigger handling failed"), true);
718
+ assert.deepEqual(clientCalls.at(-1)?.args, ['trigger-1', 'FAILED', 'runtime boom']);
719
+ assert.equal(errors.some((entry) => entry.message === 'Trigger handling failed'), true);
616
720
  });
617
721
  // ---------------------------------------------------------------------------
618
722
  // Pre-execution hook tests
619
723
  // ---------------------------------------------------------------------------
620
724
  const withTempDir = async (run) => {
621
- const dir = await mkdtemp(join(tmpdir(), "trigger-handler-hook-test-"));
725
+ const dir = await mkdtemp(join(tmpdir(), 'trigger-handler-hook-test-'));
622
726
  try {
623
727
  await run(dir);
624
728
  }
@@ -626,14 +730,14 @@ const withTempDir = async (run) => {
626
730
  await rm(dir, { recursive: true, force: true });
627
731
  }
628
732
  };
629
- test("createTriggerHandler passes the API-provided runner prompt unchanged", async () => {
733
+ test('createTriggerHandler passes the API-provided runner prompt unchanged', async () => {
630
734
  await withTempDir(async (dir) => {
631
735
  const runnerInputs = [];
632
736
  const client = {
633
737
  fetchTriggerRuntime: async () => ({
634
738
  ...runtime,
635
739
  authPath: dir,
636
- runnerPrompt: "Prompt generated by API\n\nDo exactly this.",
740
+ runnerPrompt: 'Prompt generated by API\n\nDo exactly this.',
637
741
  }),
638
742
  isTriggerCancelRequested: async () => false,
639
743
  updateTriggerHistory: async () => undefined,
@@ -641,12 +745,12 @@ test("createTriggerHandler passes the API-provided runner prompt unchanged", asy
641
745
  };
642
746
  const handler = createTriggerHandler({
643
747
  config: {
644
- daemonToken: "daemon-token",
645
- apiUrl: "https://api.example",
748
+ daemonToken: 'daemon-token',
749
+ apiUrl: 'https://api.example',
646
750
  pollingIntervalMs: 5000,
647
751
  timeoutMs: 1500,
648
752
  idleTimeoutMs: 500,
649
- runnerCmd: "opencode",
753
+ runnerCmd: 'opencode',
650
754
  preventSleepWhileBusy: false,
651
755
  },
652
756
  client: client,
@@ -662,18 +766,18 @@ test("createTriggerHandler passes the API-provided runner prompt unchanged", asy
662
766
  append: () => undefined,
663
767
  stop: async () => undefined,
664
768
  }),
665
- readHistoryFile: async () => "### Summary\n- done\n",
769
+ readHistoryFile: async () => '### Summary\n- done\n',
666
770
  resolveRunnerHistoryPaths: () => ({
667
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
771
+ currentHistoryPath: join(dir, '.agentteams/runner/history/trigger-1.md'),
668
772
  parentHistoryPath: null,
669
773
  }),
670
774
  });
671
775
  await handler({ ...trigger, parentTriggerId: null });
672
776
  assert.equal(runnerInputs.length, 1);
673
- assert.equal(runnerInputs[0]?.prompt, "Prompt generated by API\n\nDo exactly this.");
777
+ assert.equal(runnerInputs[0]?.prompt, 'Prompt generated by API\n\nDo exactly this.');
674
778
  });
675
779
  });
676
- test("createTriggerHandler downloads attachments into the runner workspace and injects local paths", async () => {
780
+ test('createTriggerHandler downloads attachments into the runner workspace and injects local paths', async () => {
677
781
  await withTempDir(async (dir) => {
678
782
  const runnerInputs = [];
679
783
  const downloadedUrls = [];
@@ -681,15 +785,17 @@ test("createTriggerHandler downloads attachments into the runner workspace and i
681
785
  fetchTriggerRuntime: async () => ({
682
786
  ...runtime,
683
787
  authPath: dir,
684
- runnerPrompt: "Use the attached file.",
685
- attachments: [{
686
- id: "attachment-1",
687
- originalName: "../notes file.md",
688
- mimeType: "text/markdown",
788
+ runnerPrompt: 'Use the attached file.',
789
+ attachments: [
790
+ {
791
+ id: 'attachment-1',
792
+ originalName: '../notes file.md',
793
+ mimeType: 'text/markdown',
689
794
  size: 12,
690
- downloadUrl: "https://storage.example/attachment-1",
795
+ downloadUrl: 'https://storage.example/attachment-1',
691
796
  expiresInSeconds: 300,
692
- }],
797
+ },
798
+ ],
693
799
  }),
694
800
  isTriggerCancelRequested: async () => false,
695
801
  updateTriggerHistory: async () => undefined,
@@ -697,12 +803,12 @@ test("createTriggerHandler downloads attachments into the runner workspace and i
697
803
  };
698
804
  const handler = createTriggerHandler({
699
805
  config: {
700
- daemonToken: "daemon-token",
701
- apiUrl: "https://api.example",
806
+ daemonToken: 'daemon-token',
807
+ apiUrl: 'https://api.example',
702
808
  pollingIntervalMs: 5000,
703
809
  timeoutMs: 1500,
704
810
  idleTimeoutMs: 500,
705
- runnerCmd: "opencode",
811
+ runnerCmd: 'opencode',
706
812
  preventSleepWhileBusy: false,
707
813
  },
708
814
  client: client,
@@ -720,18 +826,18 @@ test("createTriggerHandler downloads attachments into the runner workspace and i
720
826
  }),
721
827
  fetchAttachmentFile: async (downloadUrl) => {
722
828
  downloadedUrls.push(downloadUrl);
723
- return new TextEncoder().encode("hello world\n");
829
+ return new TextEncoder().encode('hello world\n');
724
830
  },
725
- readHistoryFile: async () => "### Summary\n- done\n",
831
+ readHistoryFile: async () => '### Summary\n- done\n',
726
832
  resolveRunnerHistoryPaths: () => ({
727
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
833
+ currentHistoryPath: join(dir, '.agentteams/runner/history/trigger-1.md'),
728
834
  parentHistoryPath: null,
729
835
  }),
730
836
  });
731
837
  await handler({ ...trigger, parentTriggerId: null });
732
- assert.deepEqual(downloadedUrls, ["https://storage.example/attachment-1"]);
838
+ assert.deepEqual(downloadedUrls, ['https://storage.example/attachment-1']);
733
839
  assert.equal(runnerInputs.length, 1);
734
- const prompt = runnerInputs[0]?.prompt ?? "";
840
+ const prompt = runnerInputs[0]?.prompt ?? '';
735
841
  assert.match(prompt, /## Attached Files/);
736
842
  assert.match(prompt, /MIME type: text\/markdown/);
737
843
  assert.match(prompt, /Size: 12 bytes/);
@@ -739,26 +845,28 @@ test("createTriggerHandler downloads attachments into the runner workspace and i
739
845
  assert.doesNotMatch(prompt, /https:\/\/storage\.example/);
740
846
  const localPathMatch = prompt.match(/Local path: (.+01-attachme-notes-file\.md)$/m);
741
847
  assert.ok(localPathMatch?.[1]);
742
- assert.equal(localPathMatch[1].startsWith(join(dir, ".agentteams", "runner", "attachments", "trigger-1")), true);
743
- const attachmentDir = join(dir, ".agentteams", "runner", "attachments", "trigger-1");
848
+ assert.equal(localPathMatch[1].startsWith(join(dir, '.agentteams', 'runner', 'attachments', 'trigger-1')), true);
849
+ const attachmentDir = join(dir, '.agentteams', 'runner', 'attachments', 'trigger-1');
744
850
  await assert.rejects(stat(attachmentDir), /ENOENT/);
745
851
  });
746
852
  });
747
- test("createTriggerHandler removes the attachment directory after runner failure", async () => {
853
+ test('createTriggerHandler removes the attachment directory after runner failure', async () => {
748
854
  await withTempDir(async (dir) => {
749
855
  const client = {
750
856
  fetchTriggerRuntime: async () => ({
751
857
  ...runtime,
752
858
  authPath: dir,
753
- runnerPrompt: "Use the attached file.",
754
- attachments: [{
755
- id: "attachment-1",
756
- originalName: "notes.md",
757
- mimeType: "text/markdown",
859
+ runnerPrompt: 'Use the attached file.',
860
+ attachments: [
861
+ {
862
+ id: 'attachment-1',
863
+ originalName: 'notes.md',
864
+ mimeType: 'text/markdown',
758
865
  size: 12,
759
- downloadUrl: "https://storage.example/attachment-1",
866
+ downloadUrl: 'https://storage.example/attachment-1',
760
867
  expiresInSeconds: 300,
761
- }],
868
+ },
869
+ ],
762
870
  }),
763
871
  isTriggerCancelRequested: async () => false,
764
872
  updateTriggerHistory: async () => undefined,
@@ -766,19 +874,19 @@ test("createTriggerHandler removes the attachment directory after runner failure
766
874
  };
767
875
  const handler = createTriggerHandler({
768
876
  config: {
769
- daemonToken: "daemon-token",
770
- apiUrl: "https://api.example",
877
+ daemonToken: 'daemon-token',
878
+ apiUrl: 'https://api.example',
771
879
  pollingIntervalMs: 5000,
772
880
  timeoutMs: 1500,
773
881
  idleTimeoutMs: 500,
774
- runnerCmd: "opencode",
882
+ runnerCmd: 'opencode',
775
883
  preventSleepWhileBusy: false,
776
884
  },
777
885
  client: client,
778
886
  }, {
779
887
  createRunnerFactory: () => () => ({
780
888
  run: async () => {
781
- throw new Error("runner crashed");
889
+ throw new Error('runner crashed');
782
890
  },
783
891
  }),
784
892
  createLogReporter: () => ({
@@ -786,34 +894,36 @@ test("createTriggerHandler removes the attachment directory after runner failure
786
894
  append: () => undefined,
787
895
  stop: async () => undefined,
788
896
  }),
789
- fetchAttachmentFile: async () => new TextEncoder().encode("hello world\n"),
790
- readHistoryFile: async () => "",
897
+ fetchAttachmentFile: async () => new TextEncoder().encode('hello world\n'),
898
+ readHistoryFile: async () => '',
791
899
  resolveRunnerHistoryPaths: () => ({
792
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
900
+ currentHistoryPath: join(dir, '.agentteams/runner/history/trigger-1.md'),
793
901
  parentHistoryPath: null,
794
902
  }),
795
903
  });
796
904
  await handler({ ...trigger, parentTriggerId: null });
797
- const attachmentDir = join(dir, ".agentteams", "runner", "attachments", "trigger-1");
905
+ const attachmentDir = join(dir, '.agentteams', 'runner', 'attachments', 'trigger-1');
798
906
  await assert.rejects(stat(attachmentDir), /ENOENT/);
799
907
  });
800
908
  });
801
- test("createTriggerHandler logs but does not throw when attachment cleanup fails", async () => {
909
+ test('createTriggerHandler logs but does not throw when attachment cleanup fails', async () => {
802
910
  await withTempDir(async (dir) => {
803
911
  const cleanupCalls = [];
804
912
  const client = {
805
913
  fetchTriggerRuntime: async () => ({
806
914
  ...runtime,
807
915
  authPath: dir,
808
- runnerPrompt: "Use the attached file.",
809
- attachments: [{
810
- id: "attachment-1",
811
- originalName: "notes.md",
812
- mimeType: "text/markdown",
916
+ runnerPrompt: 'Use the attached file.',
917
+ attachments: [
918
+ {
919
+ id: 'attachment-1',
920
+ originalName: 'notes.md',
921
+ mimeType: 'text/markdown',
813
922
  size: 12,
814
- downloadUrl: "https://storage.example/attachment-1",
923
+ downloadUrl: 'https://storage.example/attachment-1',
815
924
  expiresInSeconds: 300,
816
- }],
925
+ },
926
+ ],
817
927
  }),
818
928
  isTriggerCancelRequested: async () => false,
819
929
  updateTriggerHistory: async () => undefined,
@@ -821,12 +931,12 @@ test("createTriggerHandler logs but does not throw when attachment cleanup fails
821
931
  };
822
932
  const handler = createTriggerHandler({
823
933
  config: {
824
- daemonToken: "daemon-token",
825
- apiUrl: "https://api.example",
934
+ daemonToken: 'daemon-token',
935
+ apiUrl: 'https://api.example',
826
936
  pollingIntervalMs: 5000,
827
937
  timeoutMs: 1500,
828
938
  idleTimeoutMs: 500,
829
- runnerCmd: "opencode",
939
+ runnerCmd: 'opencode',
830
940
  preventSleepWhileBusy: false,
831
941
  },
832
942
  client: client,
@@ -839,53 +949,55 @@ test("createTriggerHandler logs but does not throw when attachment cleanup fails
839
949
  append: () => undefined,
840
950
  stop: async () => undefined,
841
951
  }),
842
- fetchAttachmentFile: async () => new TextEncoder().encode("hello world\n"),
952
+ fetchAttachmentFile: async () => new TextEncoder().encode('hello world\n'),
843
953
  removeAttachmentDirectory: async (path) => {
844
954
  cleanupCalls.push(path);
845
- throw new Error("cleanup boom");
955
+ throw new Error('cleanup boom');
846
956
  },
847
- readHistoryFile: async () => "### Summary\n- done\n",
957
+ readHistoryFile: async () => '### Summary\n- done\n',
848
958
  resolveRunnerHistoryPaths: () => ({
849
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
959
+ currentHistoryPath: join(dir, '.agentteams/runner/history/trigger-1.md'),
850
960
  parentHistoryPath: null,
851
961
  }),
852
962
  });
853
963
  await handler({ ...trigger, parentTriggerId: null });
854
- assert.deepEqual(cleanupCalls, [join(dir, ".agentteams", "runner", "attachments", "trigger-1")]);
964
+ assert.deepEqual(cleanupCalls, [join(dir, '.agentteams', 'runner', 'attachments', 'trigger-1')]);
855
965
  });
856
966
  });
857
- test("createTriggerHandler fails before runner execution when attachments have no runner workspace", async () => {
967
+ test('createTriggerHandler fails before runner execution when attachments have no runner workspace', async () => {
858
968
  const clientCalls = [];
859
969
  let runnerCalled = false;
860
970
  const client = {
861
971
  fetchTriggerRuntime: async () => ({
862
972
  ...runtime,
863
973
  authPath: null,
864
- attachments: [{
865
- id: "attachment-1",
866
- originalName: "notes.md",
867
- mimeType: "text/markdown",
974
+ attachments: [
975
+ {
976
+ id: 'attachment-1',
977
+ originalName: 'notes.md',
978
+ mimeType: 'text/markdown',
868
979
  size: 12,
869
- downloadUrl: "https://storage.example/attachment-1",
980
+ downloadUrl: 'https://storage.example/attachment-1',
870
981
  expiresInSeconds: 300,
871
- }],
982
+ },
983
+ ],
872
984
  }),
873
985
  isTriggerCancelRequested: async () => false,
874
986
  updateTriggerHistory: async (...args) => {
875
- clientCalls.push({ method: "updateTriggerHistory", args });
987
+ clientCalls.push({ method: 'updateTriggerHistory', args });
876
988
  },
877
989
  updateTriggerStatus: async (...args) => {
878
- clientCalls.push({ method: "updateTriggerStatus", args });
990
+ clientCalls.push({ method: 'updateTriggerStatus', args });
879
991
  },
880
992
  };
881
993
  const handler = createTriggerHandler({
882
994
  config: {
883
- daemonToken: "daemon-token",
884
- apiUrl: "https://api.example",
995
+ daemonToken: 'daemon-token',
996
+ apiUrl: 'https://api.example',
885
997
  pollingIntervalMs: 5000,
886
998
  timeoutMs: 1500,
887
999
  idleTimeoutMs: 500,
888
- runnerCmd: "opencode",
1000
+ runnerCmd: 'opencode',
889
1001
  preventSleepWhileBusy: false,
890
1002
  },
891
1003
  client: client,
@@ -906,28 +1018,30 @@ test("createTriggerHandler fails before runner execution when attachments have n
906
1018
  await handler({ ...trigger, parentTriggerId: null });
907
1019
  assert.equal(runnerCalled, false);
908
1020
  assert.deepEqual(clientCalls.at(-1)?.args, [
909
- "trigger-1",
910
- "FAILED",
911
- "Cannot deliver attachments because runner workspace path is not configured."
1021
+ 'trigger-1',
1022
+ 'FAILED',
1023
+ 'Cannot deliver attachments because runner workspace path is not configured.',
912
1024
  ]);
913
1025
  });
914
- test("createTriggerHandler does not append history or convention text to the API prompt", async () => {
1026
+ test('createTriggerHandler does not append history or convention text to the API prompt', async () => {
915
1027
  await withTempDir(async (dir) => {
916
1028
  const runnerInputs = [];
917
- const conventions = [{
918
- id: "conv-feature",
919
- filePath: ".agentteams/rules/feature.md",
920
- trigger: "task:FEATURE",
921
- title: "Feature Convention",
922
- description: "Rules for feature tasks",
923
- }];
1029
+ const conventions = [
1030
+ {
1031
+ id: 'conv-feature',
1032
+ filePath: '.agentteams/rules/feature.md',
1033
+ trigger: 'task:FEATURE',
1034
+ title: 'Feature Convention',
1035
+ description: 'Rules for feature tasks',
1036
+ },
1037
+ ];
924
1038
  const client = {
925
1039
  fetchTriggerRuntime: async () => ({
926
1040
  ...runtime,
927
1041
  authPath: dir,
928
1042
  conventions,
929
- planType: "BUG_FIX",
930
- runnerPrompt: "Only the API prompt",
1043
+ planType: 'BUG_FIX',
1044
+ runnerPrompt: 'Only the API prompt',
931
1045
  }),
932
1046
  isTriggerCancelRequested: async () => false,
933
1047
  updateTriggerHistory: async () => undefined,
@@ -935,12 +1049,12 @@ test("createTriggerHandler does not append history or convention text to the API
935
1049
  };
936
1050
  const handler = createTriggerHandler({
937
1051
  config: {
938
- daemonToken: "daemon-token",
939
- apiUrl: "https://api.example",
1052
+ daemonToken: 'daemon-token',
1053
+ apiUrl: 'https://api.example',
940
1054
  pollingIntervalMs: 5000,
941
1055
  timeoutMs: 1500,
942
1056
  idleTimeoutMs: 500,
943
- runnerCmd: "opencode",
1057
+ runnerCmd: 'opencode',
944
1058
  preventSleepWhileBusy: false,
945
1059
  },
946
1060
  client: client,
@@ -956,16 +1070,16 @@ test("createTriggerHandler does not append history or convention text to the API
956
1070
  append: () => undefined,
957
1071
  stop: async () => undefined,
958
1072
  }),
959
- readHistoryFile: async () => "### Summary\n- done\n",
1073
+ readHistoryFile: async () => '### Summary\n- done\n',
960
1074
  resolveRunnerHistoryPaths: () => ({
961
- currentHistoryPath: join(dir, ".agentteams/runner/history/trigger-1.md"),
1075
+ currentHistoryPath: join(dir, '.agentteams/runner/history/trigger-1.md'),
962
1076
  parentHistoryPath: null,
963
1077
  }),
964
1078
  });
965
1079
  await handler({ ...trigger, parentTriggerId: null });
966
1080
  assert.equal(runnerInputs.length, 1);
967
- assert.equal(runnerInputs[0]?.prompt, "Only the API prompt");
968
- assert.doesNotMatch(runnerInputs[0]?.prompt ?? "", /Context-Matched Conventions \(AUTO-LOADED\)/);
1081
+ assert.equal(runnerInputs[0]?.prompt, 'Only the API prompt');
1082
+ assert.doesNotMatch(runnerInputs[0]?.prompt ?? '', /Context-Matched Conventions \(AUTO-LOADED\)/);
969
1083
  });
970
1084
  });
971
1085
  //# sourceMappingURL=trigger-handler.test.js.map