@agentteams/runner 0.0.84-dev.208 → 0.0.85

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