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