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