@agentteams/runner 0.0.84 → 0.0.85-dev.211
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-client.d.ts +2 -2
- package/dist/api-client.js +66 -66
- package/dist/api-client.js.map +1 -1
- package/dist/api-client.test.js +63 -54
- package/dist/api-client.test.js.map +1 -1
- package/dist/autostart.js +81 -81
- package/dist/autostart.js.map +1 -1
- package/dist/autostart.test.js +35 -35
- package/dist/autostart.test.js.map +1 -1
- package/dist/commands/cleanup.js +4 -4
- package/dist/commands/init.js +14 -14
- package/dist/commands/restart.js +3 -3
- package/dist/commands/start.js +10 -10
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/status.js +7 -7
- package/dist/commands/stop.js +7 -7
- package/dist/commands/uninstall.js +6 -6
- package/dist/commands/update.d.ts +4 -4
- package/dist/commands/update.js +20 -22
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +38 -42
- package/dist/commands/update.test.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +12 -12
- package/dist/config.test.js +61 -61
- package/dist/config.test.js.map +1 -1
- package/dist/daemon-control.d.ts +5 -5
- package/dist/daemon-control.js +21 -22
- package/dist/daemon-control.js.map +1 -1
- package/dist/daemon-control.test.js +29 -29
- package/dist/executable.d.ts +3 -3
- package/dist/executable.js +46 -49
- package/dist/executable.js.map +1 -1
- package/dist/executable.test.js +62 -62
- package/dist/executable.test.js.map +1 -1
- package/dist/handlers/trigger-handler.d.ts +6 -6
- package/dist/handlers/trigger-handler.js +172 -134
- package/dist/handlers/trigger-handler.js.map +1 -1
- package/dist/handlers/trigger-handler.test.js +442 -328
- package/dist/handlers/trigger-handler.test.js.map +1 -1
- package/dist/index.js +25 -25
- package/dist/logger.js +1 -1
- package/dist/pid.js +6 -6
- package/dist/poller.d.ts +7 -7
- package/dist/poller.js +55 -54
- package/dist/poller.js.map +1 -1
- package/dist/poller.test.js +264 -180
- package/dist/poller.test.js.map +1 -1
- package/dist/runners/amp.d.ts +1 -1
- package/dist/runners/amp.js +74 -81
- package/dist/runners/amp.js.map +1 -1
- package/dist/runners/antigravity.d.ts +3 -3
- package/dist/runners/antigravity.js +116 -127
- package/dist/runners/antigravity.js.map +1 -1
- package/dist/runners/antigravity.replay.test.js +25 -25
- package/dist/runners/antigravity.replay.test.js.map +1 -1
- package/dist/runners/antigravity.test.js +98 -98
- package/dist/runners/antigravity.test.js.map +1 -1
- package/dist/runners/claude-code.d.ts +1 -1
- package/dist/runners/claude-code.js +85 -84
- package/dist/runners/claude-code.js.map +1 -1
- package/dist/runners/claude-code.test.js +37 -40
- package/dist/runners/claude-code.test.js.map +1 -1
- package/dist/runners/codex.d.ts +1 -1
- package/dist/runners/codex.js +87 -96
- package/dist/runners/codex.js.map +1 -1
- package/dist/runners/codex.test.js +68 -68
- package/dist/runners/codex.test.js.map +1 -1
- package/dist/runners/index.d.ts +1 -1
- package/dist/runners/index.js +10 -10
- package/dist/runners/index.test.js +16 -16
- package/dist/runners/log-reporter.d.ts +2 -2
- package/dist/runners/log-reporter.js +12 -10
- package/dist/runners/log-reporter.js.map +1 -1
- package/dist/runners/log-reporter.test.js +70 -70
- package/dist/runners/log-reporter.test.js.map +1 -1
- package/dist/runners/opencode.d.ts +1 -1
- package/dist/runners/opencode.js +68 -75
- package/dist/runners/opencode.js.map +1 -1
- package/dist/runners/process-control.d.ts +1 -1
- package/dist/runners/process-control.js +13 -11
- package/dist/runners/process-control.js.map +1 -1
- package/dist/runners/stream-json-parser.d.ts +1 -1
- package/dist/runners/stream-json-parser.js +70 -67
- package/dist/runners/stream-json-parser.js.map +1 -1
- package/dist/runners/stream-json-parser.replay.test.js +17 -17
- package/dist/runners/stream-json-parser.replay.test.js.map +1 -1
- package/dist/runners/stream-json-parser.test.js +76 -78
- package/dist/runners/stream-json-parser.test.js.map +1 -1
- package/dist/types.d.ts +4 -4
- package/dist/utils/auth-path-store.js +7 -7
- package/dist/utils/auth-path-store.test.js +24 -29
- package/dist/utils/auth-path-store.test.js.map +1 -1
- package/dist/utils/auto-update.d.ts +4 -4
- package/dist/utils/auto-update.js +30 -28
- package/dist/utils/auto-update.js.map +1 -1
- package/dist/utils/auto-update.test.js +30 -30
- package/dist/utils/convention-evaluator.d.ts +1 -1
- package/dist/utils/convention-evaluator.js +22 -21
- package/dist/utils/convention-evaluator.js.map +1 -1
- package/dist/utils/convention-evaluator.test.js +120 -110
- package/dist/utils/convention-evaluator.test.js.map +1 -1
- package/dist/utils/convention-sync.d.ts +3 -3
- package/dist/utils/convention-sync.js +86 -18
- package/dist/utils/convention-sync.js.map +1 -1
- package/dist/utils/convention-sync.test.js +126 -30
- package/dist/utils/convention-sync.test.js.map +1 -1
- package/dist/utils/git-worktree.js +20 -18
- package/dist/utils/git-worktree.js.map +1 -1
- package/dist/utils/git-worktree.test.js +98 -98
- package/dist/utils/git-worktree.test.js.map +1 -1
- package/dist/utils/origin-issue-safeguard.js +35 -30
- package/dist/utils/origin-issue-safeguard.js.map +1 -1
- package/dist/utils/power-save-blocker.d.ts +4 -4
- package/dist/utils/power-save-blocker.js +33 -33
- package/dist/utils/power-save-blocker.test.js +37 -37
- package/dist/utils/power-save-blocker.test.js.map +1 -1
- package/dist/utils/runner-cleanup.d.ts +3 -3
- package/dist/utils/runner-cleanup.js +13 -13
- package/dist/utils/runner-cleanup.test.js +35 -35
- package/dist/utils/runner-history.js +3 -3
- package/dist/utils/runner-history.test.js +17 -17
- package/package.json +9 -2
- package/readme.md +10 -10
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import assert from
|
|
2
|
-
import test, { mock } from
|
|
3
|
-
import { mkdtemp, rm, stat } from
|
|
4
|
-
import { tmpdir } from
|
|
5
|
-
import { join } from
|
|
6
|
-
import { logger } from
|
|
7
|
-
import { createTriggerHandler } from
|
|
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:
|
|
10
|
-
prompt:
|
|
11
|
-
runnerType:
|
|
12
|
-
model:
|
|
9
|
+
id: 'trigger-1',
|
|
10
|
+
prompt: 'Implement feature',
|
|
11
|
+
runnerType: 'CODEX',
|
|
12
|
+
model: 'o4-mini',
|
|
13
13
|
fastMode: false,
|
|
14
|
-
status:
|
|
15
|
-
agentConfigId:
|
|
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:
|
|
22
|
-
createdByMemberId:
|
|
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:
|
|
31
|
-
updatedAt:
|
|
30
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
31
|
+
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
32
32
|
};
|
|
33
33
|
const runtime = {
|
|
34
|
-
triggerId:
|
|
35
|
-
agentConfigId:
|
|
36
|
-
authPath:
|
|
37
|
-
apiKey:
|
|
38
|
-
teamId:
|
|
39
|
-
projectId:
|
|
40
|
-
runnerPrompt:
|
|
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(
|
|
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:
|
|
56
|
+
clientCalls.push({ method: 'fetchTriggerRuntime', args });
|
|
57
57
|
return runtime;
|
|
58
58
|
},
|
|
59
59
|
isTriggerCancelRequested: async (...args) => {
|
|
60
|
-
clientCalls.push({ method:
|
|
60
|
+
clientCalls.push({ method: 'isTriggerCancelRequested', args });
|
|
61
61
|
return false;
|
|
62
62
|
},
|
|
63
63
|
updateTriggerHistory: async (...args) => {
|
|
64
|
-
clientCalls.push({ method:
|
|
64
|
+
clientCalls.push({ method: 'updateTriggerHistory', args });
|
|
65
65
|
},
|
|
66
66
|
updateTriggerStatus: async (...args) => {
|
|
67
|
-
clientCalls.push({ method:
|
|
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?.(
|
|
74
|
-
input.onStderrChunk?.(
|
|
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:
|
|
81
|
-
apiUrl:
|
|
80
|
+
daemonToken: 'daemon-token',
|
|
81
|
+
apiUrl: 'https://api.example',
|
|
82
82
|
pollingIntervalMs: 5000,
|
|
83
83
|
timeoutMs: 1500,
|
|
84
84
|
idleTimeoutMs: 500,
|
|
85
|
-
runnerCmd:
|
|
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:
|
|
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:
|
|
103
|
-
}
|
|
102
|
+
logEntries.push({ level: 'STOP', message: 'stopped' });
|
|
103
|
+
},
|
|
104
104
|
}),
|
|
105
|
-
readHistoryFile: async () =>
|
|
105
|
+
readHistoryFile: async () => '### Summary\n- done\n',
|
|
106
106
|
resolveRunnerHistoryPaths: () => ({
|
|
107
|
-
currentHistoryPath:
|
|
108
|
-
parentHistoryPath:
|
|
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, [
|
|
112
|
+
assert.deepEqual(discoveredAuthPaths, ['/auth/path']);
|
|
113
113
|
assert.equal(runnerInputs.length, 1);
|
|
114
|
-
assert.equal(runnerInputs[0]?.prompt,
|
|
115
|
-
assert.equal(logEntries.some((entry) => entry.level ===
|
|
116
|
-
assert.equal(logEntries.some((entry) => entry.level ===
|
|
117
|
-
assert.deepEqual(clientCalls.map((entry) => entry.method), [
|
|
118
|
-
|
|
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(
|
|
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:
|
|
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:
|
|
135
|
-
}
|
|
132
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
133
|
+
},
|
|
136
134
|
};
|
|
137
135
|
const handler = createTriggerHandler({
|
|
138
136
|
config: {
|
|
139
|
-
daemonToken:
|
|
140
|
-
apiUrl:
|
|
137
|
+
daemonToken: 'daemon-token',
|
|
138
|
+
apiUrl: 'https://api.example',
|
|
141
139
|
pollingIntervalMs: 5000,
|
|
142
140
|
timeoutMs: 1500,
|
|
143
141
|
idleTimeoutMs: 500,
|
|
144
|
-
runnerCmd:
|
|
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 () =>
|
|
256
|
+
readHistoryFile: async () => historyWithQuestions,
|
|
158
257
|
resolveRunnerHistoryPaths: () => ({
|
|
159
|
-
currentHistoryPath:
|
|
160
|
-
parentHistoryPath:
|
|
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, [
|
|
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(
|
|
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:
|
|
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:
|
|
181
|
-
apiUrl:
|
|
281
|
+
daemonToken: 'daemon-token',
|
|
282
|
+
apiUrl: 'https://api.example',
|
|
182
283
|
pollingIntervalMs: 5000,
|
|
183
284
|
timeoutMs: 1500,
|
|
184
285
|
idleTimeoutMs: 500,
|
|
185
|
-
runnerCmd:
|
|
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(
|
|
203
|
-
throw new Error(
|
|
303
|
+
if (String(path).endsWith('parent-1.md')) {
|
|
304
|
+
throw new Error('ENOENT');
|
|
204
305
|
}
|
|
205
|
-
return
|
|
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:
|
|
212
|
-
parentHistoryPath:
|
|
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
|
-
|
|
219
|
-
|
|
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(
|
|
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:
|
|
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:
|
|
236
|
-
apiUrl:
|
|
338
|
+
daemonToken: 'daemon-token',
|
|
339
|
+
apiUrl: 'https://api.example',
|
|
237
340
|
pollingIntervalMs: 5000,
|
|
238
341
|
timeoutMs: 1500,
|
|
239
342
|
idleTimeoutMs: 500,
|
|
240
|
-
runnerCmd:
|
|
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 () =>
|
|
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:
|
|
259
|
-
parentHistoryPath:
|
|
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
|
-
|
|
265
|
-
|
|
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(
|
|
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:
|
|
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:
|
|
282
|
-
apiUrl:
|
|
386
|
+
daemonToken: 'daemon-token',
|
|
387
|
+
apiUrl: 'https://api.example',
|
|
283
388
|
pollingIntervalMs: 5000,
|
|
284
389
|
timeoutMs: 1500,
|
|
285
390
|
idleTimeoutMs: 500,
|
|
286
|
-
runnerCmd:
|
|
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 () =>
|
|
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:
|
|
305
|
-
parentHistoryPath:
|
|
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
|
-
|
|
311
|
-
|
|
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(
|
|
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:
|
|
427
|
+
clientCalls.push({ method: 'updateTriggerHistory', args });
|
|
321
428
|
},
|
|
322
429
|
updateTriggerStatus: async (...args) => {
|
|
323
|
-
clientCalls.push({ method:
|
|
324
|
-
}
|
|
430
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
431
|
+
},
|
|
325
432
|
};
|
|
326
433
|
const handler = createTriggerHandler({
|
|
327
434
|
config: {
|
|
328
|
-
daemonToken:
|
|
329
|
-
apiUrl:
|
|
435
|
+
daemonToken: 'daemon-token',
|
|
436
|
+
apiUrl: 'https://api.example',
|
|
330
437
|
pollingIntervalMs: 5000,
|
|
331
438
|
timeoutMs: 1500,
|
|
332
439
|
idleTimeoutMs: 500,
|
|
333
|
-
runnerCmd:
|
|
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:
|
|
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:
|
|
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, [
|
|
460
|
+
assert.deepEqual(clientCalls.at(-1)?.args, ['trigger-1', 'FAILED', 'last output']);
|
|
354
461
|
});
|
|
355
|
-
test(
|
|
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:
|
|
469
|
+
clientCalls.push({ method: 'updateTriggerHistory', args });
|
|
363
470
|
},
|
|
364
471
|
updateTriggerStatus: async (...args) => {
|
|
365
|
-
clientCalls.push({ method:
|
|
366
|
-
}
|
|
472
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
473
|
+
},
|
|
367
474
|
};
|
|
368
475
|
const handler = createTriggerHandler({
|
|
369
476
|
config: {
|
|
370
|
-
daemonToken:
|
|
371
|
-
apiUrl:
|
|
477
|
+
daemonToken: 'daemon-token',
|
|
478
|
+
apiUrl: 'https://api.example',
|
|
372
479
|
pollingIntervalMs: 5000,
|
|
373
480
|
timeoutMs: 1500,
|
|
374
481
|
idleTimeoutMs: 500,
|
|
375
|
-
runnerCmd:
|
|
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:
|
|
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(
|
|
502
|
+
throw new Error('ENOENT');
|
|
396
503
|
},
|
|
397
504
|
resolveRunnerHistoryPaths: () => ({
|
|
398
|
-
currentHistoryPath:
|
|
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,
|
|
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, [
|
|
515
|
+
assert.deepEqual(clientCalls.at(-1)?.args, ['trigger-1', 'DONE', undefined]);
|
|
412
516
|
});
|
|
413
|
-
test(
|
|
517
|
+
test('createTriggerHandler truncates long agent output in fallback history', async () => {
|
|
414
518
|
const clientCalls = [];
|
|
415
519
|
const writtenFiles = [];
|
|
416
|
-
const longOutput =
|
|
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:
|
|
525
|
+
clientCalls.push({ method: 'updateTriggerHistory', args });
|
|
422
526
|
},
|
|
423
527
|
updateTriggerStatus: async (...args) => {
|
|
424
|
-
clientCalls.push({ method:
|
|
425
|
-
}
|
|
528
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
529
|
+
},
|
|
426
530
|
};
|
|
427
531
|
const handler = createTriggerHandler({
|
|
428
532
|
config: {
|
|
429
|
-
daemonToken:
|
|
430
|
-
apiUrl:
|
|
533
|
+
daemonToken: 'daemon-token',
|
|
534
|
+
apiUrl: 'https://api.example',
|
|
431
535
|
pollingIntervalMs: 5000,
|
|
432
536
|
timeoutMs: 1500,
|
|
433
537
|
idleTimeoutMs: 500,
|
|
434
|
-
runnerCmd:
|
|
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(
|
|
558
|
+
throw new Error('ENOENT');
|
|
455
559
|
},
|
|
456
560
|
resolveRunnerHistoryPaths: () => ({
|
|
457
|
-
currentHistoryPath:
|
|
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,
|
|
465
|
-
assert.ok(fallbackContent.includes(
|
|
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(
|
|
571
|
+
test('createTriggerHandler preserves fallback agent output within history limit', async () => {
|
|
468
572
|
const clientCalls = [];
|
|
469
573
|
const writtenFiles = [];
|
|
470
|
-
const output =
|
|
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:
|
|
579
|
+
clientCalls.push({ method: 'updateTriggerHistory', args });
|
|
476
580
|
},
|
|
477
581
|
updateTriggerStatus: async (...args) => {
|
|
478
|
-
clientCalls.push({ method:
|
|
479
|
-
}
|
|
582
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
583
|
+
},
|
|
480
584
|
};
|
|
481
585
|
const handler = createTriggerHandler({
|
|
482
586
|
config: {
|
|
483
|
-
daemonToken:
|
|
484
|
-
apiUrl:
|
|
587
|
+
daemonToken: 'daemon-token',
|
|
588
|
+
apiUrl: 'https://api.example',
|
|
485
589
|
pollingIntervalMs: 5000,
|
|
486
590
|
timeoutMs: 1500,
|
|
487
591
|
idleTimeoutMs: 500,
|
|
488
|
-
runnerCmd:
|
|
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(
|
|
612
|
+
throw new Error('ENOENT');
|
|
509
613
|
},
|
|
510
614
|
resolveRunnerHistoryPaths: () => ({
|
|
511
|
-
currentHistoryPath:
|
|
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(
|
|
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:
|
|
629
|
+
clientCalls.push({ method: 'isTriggerCancelRequested', args });
|
|
526
630
|
return true;
|
|
527
631
|
},
|
|
528
632
|
updateTriggerHistory: async (...args) => {
|
|
529
|
-
clientCalls.push({ method:
|
|
633
|
+
clientCalls.push({ method: 'updateTriggerHistory', args });
|
|
530
634
|
},
|
|
531
635
|
updateTriggerStatus: async (...args) => {
|
|
532
|
-
clientCalls.push({ method:
|
|
533
|
-
}
|
|
636
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
637
|
+
},
|
|
534
638
|
};
|
|
535
639
|
const handler = createTriggerHandler({
|
|
536
640
|
config: {
|
|
537
|
-
daemonToken:
|
|
538
|
-
apiUrl:
|
|
641
|
+
daemonToken: 'daemon-token',
|
|
642
|
+
apiUrl: 'https://api.example',
|
|
539
643
|
pollingIntervalMs: 5000,
|
|
540
644
|
timeoutMs: 1500,
|
|
541
645
|
idleTimeoutMs: 500,
|
|
542
|
-
runnerCmd:
|
|
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(
|
|
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:
|
|
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:
|
|
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, [
|
|
679
|
+
assert.deepEqual(clientCalls.at(-1)?.args, ['trigger-1', 'CANCELLED', 'Runner cancelled by user']);
|
|
576
680
|
});
|
|
577
|
-
test(
|
|
681
|
+
test('createTriggerHandler marks the trigger as failed when runtime loading throws', async () => {
|
|
578
682
|
const errors = [];
|
|
579
|
-
mock.method(logger,
|
|
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(
|
|
689
|
+
throw new Error('runtime boom');
|
|
586
690
|
},
|
|
587
691
|
isTriggerCancelRequested: async () => false,
|
|
588
692
|
updateTriggerHistory: async (...args) => {
|
|
589
|
-
clientCalls.push({ method:
|
|
693
|
+
clientCalls.push({ method: 'updateTriggerHistory', args });
|
|
590
694
|
},
|
|
591
695
|
updateTriggerStatus: async (...args) => {
|
|
592
|
-
clientCalls.push({ method:
|
|
593
|
-
}
|
|
696
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
697
|
+
},
|
|
594
698
|
};
|
|
595
699
|
const handler = createTriggerHandler({
|
|
596
700
|
config: {
|
|
597
|
-
daemonToken:
|
|
598
|
-
apiUrl:
|
|
701
|
+
daemonToken: 'daemon-token',
|
|
702
|
+
apiUrl: 'https://api.example',
|
|
599
703
|
pollingIntervalMs: 5000,
|
|
600
704
|
timeoutMs: 1500,
|
|
601
705
|
idleTimeoutMs: 500,
|
|
602
|
-
runnerCmd:
|
|
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, [
|
|
615
|
-
assert.equal(errors.some((entry) => entry.message ===
|
|
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(),
|
|
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(
|
|
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:
|
|
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:
|
|
645
|
-
apiUrl:
|
|
748
|
+
daemonToken: 'daemon-token',
|
|
749
|
+
apiUrl: 'https://api.example',
|
|
646
750
|
pollingIntervalMs: 5000,
|
|
647
751
|
timeoutMs: 1500,
|
|
648
752
|
idleTimeoutMs: 500,
|
|
649
|
-
runnerCmd:
|
|
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 () =>
|
|
769
|
+
readHistoryFile: async () => '### Summary\n- done\n',
|
|
666
770
|
resolveRunnerHistoryPaths: () => ({
|
|
667
|
-
currentHistoryPath: join(dir,
|
|
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,
|
|
777
|
+
assert.equal(runnerInputs[0]?.prompt, 'Prompt generated by API\n\nDo exactly this.');
|
|
674
778
|
});
|
|
675
779
|
});
|
|
676
|
-
test(
|
|
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:
|
|
685
|
-
attachments: [
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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:
|
|
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:
|
|
701
|
-
apiUrl:
|
|
806
|
+
daemonToken: 'daemon-token',
|
|
807
|
+
apiUrl: 'https://api.example',
|
|
702
808
|
pollingIntervalMs: 5000,
|
|
703
809
|
timeoutMs: 1500,
|
|
704
810
|
idleTimeoutMs: 500,
|
|
705
|
-
runnerCmd:
|
|
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(
|
|
829
|
+
return new TextEncoder().encode('hello world\n');
|
|
724
830
|
},
|
|
725
|
-
readHistoryFile: async () =>
|
|
831
|
+
readHistoryFile: async () => '### Summary\n- done\n',
|
|
726
832
|
resolveRunnerHistoryPaths: () => ({
|
|
727
|
-
currentHistoryPath: join(dir,
|
|
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, [
|
|
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,
|
|
743
|
-
const attachmentDir = join(dir,
|
|
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(
|
|
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:
|
|
754
|
-
attachments: [
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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:
|
|
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:
|
|
770
|
-
apiUrl:
|
|
877
|
+
daemonToken: 'daemon-token',
|
|
878
|
+
apiUrl: 'https://api.example',
|
|
771
879
|
pollingIntervalMs: 5000,
|
|
772
880
|
timeoutMs: 1500,
|
|
773
881
|
idleTimeoutMs: 500,
|
|
774
|
-
runnerCmd:
|
|
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(
|
|
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(
|
|
790
|
-
readHistoryFile: async () =>
|
|
897
|
+
fetchAttachmentFile: async () => new TextEncoder().encode('hello world\n'),
|
|
898
|
+
readHistoryFile: async () => '',
|
|
791
899
|
resolveRunnerHistoryPaths: () => ({
|
|
792
|
-
currentHistoryPath: join(dir,
|
|
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,
|
|
905
|
+
const attachmentDir = join(dir, '.agentteams', 'runner', 'attachments', 'trigger-1');
|
|
798
906
|
await assert.rejects(stat(attachmentDir), /ENOENT/);
|
|
799
907
|
});
|
|
800
908
|
});
|
|
801
|
-
test(
|
|
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:
|
|
809
|
-
attachments: [
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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:
|
|
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:
|
|
825
|
-
apiUrl:
|
|
934
|
+
daemonToken: 'daemon-token',
|
|
935
|
+
apiUrl: 'https://api.example',
|
|
826
936
|
pollingIntervalMs: 5000,
|
|
827
937
|
timeoutMs: 1500,
|
|
828
938
|
idleTimeoutMs: 500,
|
|
829
|
-
runnerCmd:
|
|
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(
|
|
952
|
+
fetchAttachmentFile: async () => new TextEncoder().encode('hello world\n'),
|
|
843
953
|
removeAttachmentDirectory: async (path) => {
|
|
844
954
|
cleanupCalls.push(path);
|
|
845
|
-
throw new Error(
|
|
955
|
+
throw new Error('cleanup boom');
|
|
846
956
|
},
|
|
847
|
-
readHistoryFile: async () =>
|
|
957
|
+
readHistoryFile: async () => '### Summary\n- done\n',
|
|
848
958
|
resolveRunnerHistoryPaths: () => ({
|
|
849
|
-
currentHistoryPath: join(dir,
|
|
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,
|
|
964
|
+
assert.deepEqual(cleanupCalls, [join(dir, '.agentteams', 'runner', 'attachments', 'trigger-1')]);
|
|
855
965
|
});
|
|
856
966
|
});
|
|
857
|
-
test(
|
|
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
|
-
|
|
866
|
-
|
|
867
|
-
|
|
974
|
+
attachments: [
|
|
975
|
+
{
|
|
976
|
+
id: 'attachment-1',
|
|
977
|
+
originalName: 'notes.md',
|
|
978
|
+
mimeType: 'text/markdown',
|
|
868
979
|
size: 12,
|
|
869
|
-
downloadUrl:
|
|
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:
|
|
987
|
+
clientCalls.push({ method: 'updateTriggerHistory', args });
|
|
876
988
|
},
|
|
877
989
|
updateTriggerStatus: async (...args) => {
|
|
878
|
-
clientCalls.push({ method:
|
|
990
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
879
991
|
},
|
|
880
992
|
};
|
|
881
993
|
const handler = createTriggerHandler({
|
|
882
994
|
config: {
|
|
883
|
-
daemonToken:
|
|
884
|
-
apiUrl:
|
|
995
|
+
daemonToken: 'daemon-token',
|
|
996
|
+
apiUrl: 'https://api.example',
|
|
885
997
|
pollingIntervalMs: 5000,
|
|
886
998
|
timeoutMs: 1500,
|
|
887
999
|
idleTimeoutMs: 500,
|
|
888
|
-
runnerCmd:
|
|
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
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1021
|
+
'trigger-1',
|
|
1022
|
+
'FAILED',
|
|
1023
|
+
'Cannot deliver attachments because runner workspace path is not configured.',
|
|
912
1024
|
]);
|
|
913
1025
|
});
|
|
914
|
-
test(
|
|
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
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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:
|
|
930
|
-
runnerPrompt:
|
|
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:
|
|
939
|
-
apiUrl:
|
|
1052
|
+
daemonToken: 'daemon-token',
|
|
1053
|
+
apiUrl: 'https://api.example',
|
|
940
1054
|
pollingIntervalMs: 5000,
|
|
941
1055
|
timeoutMs: 1500,
|
|
942
1056
|
idleTimeoutMs: 500,
|
|
943
|
-
runnerCmd:
|
|
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 () =>
|
|
1073
|
+
readHistoryFile: async () => '### Summary\n- done\n',
|
|
960
1074
|
resolveRunnerHistoryPaths: () => ({
|
|
961
|
-
currentHistoryPath: join(dir,
|
|
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,
|
|
968
|
-
assert.doesNotMatch(runnerInputs[0]?.prompt ??
|
|
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
|