@agentteams/runner 0.0.122 → 0.0.124
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.test.js +40 -0
- package/dist/api-client.test.js.map +1 -1
- package/dist/config.js +14 -5
- package/dist/config.js.map +1 -1
- package/dist/config.test.js +31 -2
- package/dist/config.test.js.map +1 -1
- package/dist/handlers/trigger-handler.d.ts +16 -0
- package/dist/handlers/trigger-handler.js +51 -3
- package/dist/handlers/trigger-handler.js.map +1 -1
- package/dist/handlers/trigger-handler.test.js +250 -1
- package/dist/handlers/trigger-handler.test.js.map +1 -1
- package/dist/poller.test.js +6 -1
- package/dist/poller.test.js.map +1 -1
- package/dist/runners/amp.js +1 -0
- package/dist/runners/amp.js.map +1 -1
- package/dist/runners/antigravity.js +1 -0
- package/dist/runners/antigravity.js.map +1 -1
- package/dist/runners/capabilities.d.ts +3 -0
- package/dist/runners/capabilities.js +14 -2
- package/dist/runners/capabilities.js.map +1 -1
- package/dist/runners/capabilities.test.js +12 -3
- package/dist/runners/capabilities.test.js.map +1 -1
- package/dist/runners/claude-code.js +1 -0
- package/dist/runners/claude-code.js.map +1 -1
- package/dist/runners/codex.js +1 -0
- package/dist/runners/codex.js.map +1 -1
- package/dist/runners/copilot-cli.js +1 -0
- package/dist/runners/copilot-cli.js.map +1 -1
- package/dist/runners/cursor-cli.js +1 -0
- package/dist/runners/cursor-cli.js.map +1 -1
- package/dist/runners/failure-message.d.ts +10 -0
- package/dist/runners/failure-message.js +46 -0
- package/dist/runners/failure-message.js.map +1 -1
- package/dist/runners/failure-message.test.js +106 -1
- package/dist/runners/failure-message.test.js.map +1 -1
- package/dist/runners/grok-build.js +1 -0
- package/dist/runners/grok-build.js.map +1 -1
- package/dist/runners/kimi-cli.js +1 -0
- package/dist/runners/kimi-cli.js.map +1 -1
- package/dist/runners/kiro-cli.js +1 -0
- package/dist/runners/kiro-cli.js.map +1 -1
- package/dist/runners/omp.js +1 -0
- package/dist/runners/omp.js.map +1 -1
- package/dist/runners/opencode.js +1 -0
- package/dist/runners/opencode.js.map +1 -1
- package/dist/runners/types.d.ts +1 -0
- package/dist/types.d.ts +3 -0
- package/dist/utils/model-enumerator.d.ts +14 -0
- package/dist/utils/model-enumerator.js +52 -0
- package/dist/utils/model-enumerator.js.map +1 -1
- package/dist/utils/model-enumerator.test.js +47 -1
- package/dist/utils/model-enumerator.test.js.map +1 -1
- package/native/bin/win32-x64/agentrunner-launcher.exe +0 -0
- package/native/bin/win32-x64/manifest.json +2 -2
- package/package.json +1 -1
- package/readme.md +12 -12
|
@@ -6,9 +6,40 @@ import { mkdir, mkdtemp, rm, stat, writeFile } from 'node:fs/promises';
|
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { logger } from '../logger.js';
|
|
9
|
-
import { createTriggerHandler } from './trigger-handler.js';
|
|
9
|
+
import { createTriggerHandler, selectIdleTimeoutMs } from './trigger-handler.js';
|
|
10
10
|
import { startPolling } from '../poller.js';
|
|
11
11
|
import { computeLocalKey } from '../utils/worktree-discovery.js';
|
|
12
|
+
test('selectIdleTimeoutMs prefers the trigger value over every fallback', () => {
|
|
13
|
+
assert.deepEqual(selectIdleTimeoutMs({
|
|
14
|
+
triggerIdleTimeoutMs: 120_000,
|
|
15
|
+
configuredIdleTimeoutMs: 300_000,
|
|
16
|
+
isConfiguredIdleTimeoutExplicit: true,
|
|
17
|
+
runnerDefaultIdleTimeoutMs: 600_000,
|
|
18
|
+
}), { idleTimeoutMs: 120_000, source: 'trigger' });
|
|
19
|
+
});
|
|
20
|
+
test('selectIdleTimeoutMs prefers an explicitly configured env value over the runner default', () => {
|
|
21
|
+
assert.deepEqual(selectIdleTimeoutMs({
|
|
22
|
+
triggerIdleTimeoutMs: null,
|
|
23
|
+
configuredIdleTimeoutMs: 300_000,
|
|
24
|
+
isConfiguredIdleTimeoutExplicit: true,
|
|
25
|
+
runnerDefaultIdleTimeoutMs: 600_000,
|
|
26
|
+
}), { idleTimeoutMs: 300_000, source: 'env' });
|
|
27
|
+
});
|
|
28
|
+
test('selectIdleTimeoutMs uses the runner default when env is not explicitly configured', () => {
|
|
29
|
+
assert.deepEqual(selectIdleTimeoutMs({
|
|
30
|
+
triggerIdleTimeoutMs: null,
|
|
31
|
+
configuredIdleTimeoutMs: 1_800_000,
|
|
32
|
+
isConfiguredIdleTimeoutExplicit: false,
|
|
33
|
+
runnerDefaultIdleTimeoutMs: 2_700_000,
|
|
34
|
+
}), { idleTimeoutMs: 2_700_000, source: 'runnerDefault' });
|
|
35
|
+
});
|
|
36
|
+
test('selectIdleTimeoutMs falls back to the global default when no higher-priority value exists', () => {
|
|
37
|
+
assert.deepEqual(selectIdleTimeoutMs({
|
|
38
|
+
triggerIdleTimeoutMs: null,
|
|
39
|
+
configuredIdleTimeoutMs: 1_800_000,
|
|
40
|
+
isConfiguredIdleTimeoutExplicit: false,
|
|
41
|
+
}), { idleTimeoutMs: 1_800_000, source: 'globalDefault' });
|
|
42
|
+
});
|
|
12
43
|
const trigger = {
|
|
13
44
|
id: 'trigger-1',
|
|
14
45
|
prompt: 'Implement feature',
|
|
@@ -16,6 +47,7 @@ const trigger = {
|
|
|
16
47
|
model: 'o4-mini',
|
|
17
48
|
fastMode: false,
|
|
18
49
|
effort: null,
|
|
50
|
+
idleTimeoutMs: null,
|
|
19
51
|
status: 'PENDING',
|
|
20
52
|
agentConfigId: 'agent-1',
|
|
21
53
|
startedAt: null,
|
|
@@ -51,6 +83,63 @@ const runtime = {
|
|
|
51
83
|
test.afterEach(() => {
|
|
52
84
|
mock.restoreAll();
|
|
53
85
|
});
|
|
86
|
+
// 트리거 값이 없고 엔진별 기본값도 없는 기존 시나리오는 config 값을 그대로 쓰던 동작을 유지해야 한다.
|
|
87
|
+
test('createTriggerHandler passes the resolved idle timeout to the runner and logs its source', async () => {
|
|
88
|
+
const runnerIdleTimeouts = [];
|
|
89
|
+
const idleTimeoutLogs = [];
|
|
90
|
+
mock.method(logger, 'info', (message, meta) => {
|
|
91
|
+
if (message === 'Resolved runner idle timeout' && meta) {
|
|
92
|
+
idleTimeoutLogs.push(meta);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
const client = {
|
|
96
|
+
fetchTriggerRuntime: async () => runtime,
|
|
97
|
+
isTriggerCancelRequested: async () => false,
|
|
98
|
+
updateTriggerHistory: async () => { },
|
|
99
|
+
updateTriggerStatus: async () => { },
|
|
100
|
+
};
|
|
101
|
+
const runner = {
|
|
102
|
+
run: async (input) => {
|
|
103
|
+
runnerIdleTimeouts.push(input.idleTimeoutMs);
|
|
104
|
+
return { exitCode: 0 };
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
const handler = createTriggerHandler({
|
|
108
|
+
config: {
|
|
109
|
+
daemonToken: 'daemon-token',
|
|
110
|
+
apiUrl: 'https://api.example',
|
|
111
|
+
pollingIntervalMs: 5000,
|
|
112
|
+
maxPollingIntervalMs: 120_000,
|
|
113
|
+
timeoutMs: 1500,
|
|
114
|
+
idleTimeoutMs: 1_800_000,
|
|
115
|
+
isConfiguredIdleTimeoutExplicit: false,
|
|
116
|
+
runnerCmd: 'opencode',
|
|
117
|
+
preventSleepWhileBusy: false,
|
|
118
|
+
},
|
|
119
|
+
client: client,
|
|
120
|
+
}, {
|
|
121
|
+
pathExists: () => true,
|
|
122
|
+
createRunnerFactory: () => () => runner,
|
|
123
|
+
createLogReporter: () => ({ start: () => { }, append: () => { }, stop: async () => { } }),
|
|
124
|
+
readHistoryFile: async () => '### Summary\n- done\n',
|
|
125
|
+
resolveRunnerHistoryPaths: () => ({
|
|
126
|
+
currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
|
|
127
|
+
parentHistoryPath: null,
|
|
128
|
+
}),
|
|
129
|
+
});
|
|
130
|
+
// CODEX는 엔진별 기본값이 없으므로 전역 기본값으로 떨어진다.
|
|
131
|
+
await handler({ ...trigger, idleTimeoutMs: null });
|
|
132
|
+
// CLAUDE_CODE는 엔진별 기본값을 가지므로 그 축에서 값이 온다.
|
|
133
|
+
await handler({ ...trigger, runnerType: 'CLAUDE_CODE', idleTimeoutMs: null });
|
|
134
|
+
// 태스크 라벨이 실린 실행은 다른 어떤 축보다 앞선다.
|
|
135
|
+
await handler({ ...trigger, runnerType: 'CLAUDE_CODE', idleTimeoutMs: 5_400_000 });
|
|
136
|
+
assert.deepEqual(runnerIdleTimeouts, [1_800_000, 1_800_000, 5_400_000]);
|
|
137
|
+
assert.deepEqual(idleTimeoutLogs.map((meta) => ({ source: meta.source, idleTimeoutMs: meta.idleTimeoutMs })), [
|
|
138
|
+
{ source: 'globalDefault', idleTimeoutMs: 1_800_000 },
|
|
139
|
+
{ source: 'runnerDefault', idleTimeoutMs: 1_800_000 },
|
|
140
|
+
{ source: 'trigger', idleTimeoutMs: 5_400_000 },
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
54
143
|
test('createTriggerHandler runs the runner, reports history, and marks success', async () => {
|
|
55
144
|
const clientCalls = [];
|
|
56
145
|
const logEntries = [];
|
|
@@ -842,6 +931,166 @@ test('createTriggerHandler reports the explicit runner error before last output'
|
|
|
842
931
|
await handler({ ...trigger, parentTriggerId: null });
|
|
843
932
|
assert.deepEqual(clientCalls.at(-1)?.args, ['trigger-1', 'FAILED', 'specific runner error']);
|
|
844
933
|
});
|
|
934
|
+
test('createTriggerHandler: 파싱된 RESULT 실패 원문이 stderr 마지막 줄보다 우선한다', async () => {
|
|
935
|
+
const clientCalls = [];
|
|
936
|
+
const client = {
|
|
937
|
+
fetchTriggerRuntime: async () => runtime,
|
|
938
|
+
isTriggerCancelRequested: async () => false,
|
|
939
|
+
updateTriggerHistory: async (...args) => {
|
|
940
|
+
clientCalls.push({ method: 'updateTriggerHistory', args });
|
|
941
|
+
},
|
|
942
|
+
updateTriggerStatus: async (...args) => {
|
|
943
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
944
|
+
},
|
|
945
|
+
};
|
|
946
|
+
const handler = createTriggerHandler({
|
|
947
|
+
config: {
|
|
948
|
+
daemonToken: 'daemon-token',
|
|
949
|
+
apiUrl: 'https://api.example',
|
|
950
|
+
pollingIntervalMs: 5000,
|
|
951
|
+
maxPollingIntervalMs: 120_000,
|
|
952
|
+
timeoutMs: 1500,
|
|
953
|
+
idleTimeoutMs: 500,
|
|
954
|
+
runnerCmd: 'opencode',
|
|
955
|
+
preventSleepWhileBusy: false,
|
|
956
|
+
},
|
|
957
|
+
client: client,
|
|
958
|
+
}, {
|
|
959
|
+
pathExists: () => true,
|
|
960
|
+
createRunnerFactory: () => () => ({
|
|
961
|
+
run: async (opts) => {
|
|
962
|
+
// 구조화 로그 파서가 만든 RESULT 청크. 마지막 원인 있는 값이 사유로 승격돼야 한다.
|
|
963
|
+
opts.onStdoutChunk?.('[Result] Failed: first failure', 'RESULT');
|
|
964
|
+
opts.onStdoutChunk?.('[Result] Completed in 3s', 'RESULT');
|
|
965
|
+
opts.onStdoutChunk?.("[Result] Failed: The 'gpt-5' model is not supported", 'RESULT');
|
|
966
|
+
return {
|
|
967
|
+
exitCode: 1,
|
|
968
|
+
lastOutput: 'general output',
|
|
969
|
+
errorMessage: 'Reading additional input from stdin...',
|
|
970
|
+
};
|
|
971
|
+
},
|
|
972
|
+
}),
|
|
973
|
+
createLogReporter: () => ({
|
|
974
|
+
start: () => undefined,
|
|
975
|
+
append: () => undefined,
|
|
976
|
+
stop: async () => undefined,
|
|
977
|
+
}),
|
|
978
|
+
readHistoryFile: async () => '',
|
|
979
|
+
resolveRunnerHistoryPaths: () => ({
|
|
980
|
+
currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
|
|
981
|
+
parentHistoryPath: null,
|
|
982
|
+
}),
|
|
983
|
+
});
|
|
984
|
+
await handler({ ...trigger, parentTriggerId: null });
|
|
985
|
+
assert.deepEqual(clientCalls.at(-1)?.args, [
|
|
986
|
+
'trigger-1',
|
|
987
|
+
'FAILED',
|
|
988
|
+
"[Result] Failed: The 'gpt-5' model is not supported",
|
|
989
|
+
]);
|
|
990
|
+
});
|
|
991
|
+
test('createTriggerHandler: 원인 없는 RESULT 마커면 stderr 마지막 줄을 유지한다', async () => {
|
|
992
|
+
const clientCalls = [];
|
|
993
|
+
const client = {
|
|
994
|
+
fetchTriggerRuntime: async () => runtime,
|
|
995
|
+
isTriggerCancelRequested: async () => false,
|
|
996
|
+
updateTriggerHistory: async (...args) => {
|
|
997
|
+
clientCalls.push({ method: 'updateTriggerHistory', args });
|
|
998
|
+
},
|
|
999
|
+
updateTriggerStatus: async (...args) => {
|
|
1000
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
1001
|
+
},
|
|
1002
|
+
};
|
|
1003
|
+
const handler = createTriggerHandler({
|
|
1004
|
+
config: {
|
|
1005
|
+
daemonToken: 'daemon-token',
|
|
1006
|
+
apiUrl: 'https://api.example',
|
|
1007
|
+
pollingIntervalMs: 5000,
|
|
1008
|
+
maxPollingIntervalMs: 120_000,
|
|
1009
|
+
timeoutMs: 1500,
|
|
1010
|
+
idleTimeoutMs: 500,
|
|
1011
|
+
runnerCmd: 'opencode',
|
|
1012
|
+
preventSleepWhileBusy: false,
|
|
1013
|
+
},
|
|
1014
|
+
client: client,
|
|
1015
|
+
}, {
|
|
1016
|
+
pathExists: () => true,
|
|
1017
|
+
createRunnerFactory: () => () => ({
|
|
1018
|
+
run: async (opts) => {
|
|
1019
|
+
opts.onStdoutChunk?.('[Result] Failed (12s, 3 files changed)', 'RESULT');
|
|
1020
|
+
return {
|
|
1021
|
+
exitCode: 1,
|
|
1022
|
+
lastOutput: 'general output',
|
|
1023
|
+
errorMessage: 'Reading additional input from stdin...',
|
|
1024
|
+
};
|
|
1025
|
+
},
|
|
1026
|
+
}),
|
|
1027
|
+
createLogReporter: () => ({
|
|
1028
|
+
start: () => undefined,
|
|
1029
|
+
append: () => undefined,
|
|
1030
|
+
stop: async () => undefined,
|
|
1031
|
+
}),
|
|
1032
|
+
readHistoryFile: async () => '',
|
|
1033
|
+
resolveRunnerHistoryPaths: () => ({
|
|
1034
|
+
currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
|
|
1035
|
+
parentHistoryPath: null,
|
|
1036
|
+
}),
|
|
1037
|
+
});
|
|
1038
|
+
await handler({ ...trigger, parentTriggerId: null });
|
|
1039
|
+
assert.deepEqual(clientCalls.at(-1)?.args, ['trigger-1', 'FAILED', 'Reading additional input from stdin...']);
|
|
1040
|
+
});
|
|
1041
|
+
test('createTriggerHandler: fail-safe 타임아웃 문구는 그 전에 나온 RESULT 원문으로 덮이지 않는다', async () => {
|
|
1042
|
+
const clientCalls = [];
|
|
1043
|
+
const client = {
|
|
1044
|
+
fetchTriggerRuntime: async () => runtime,
|
|
1045
|
+
isTriggerCancelRequested: async () => false,
|
|
1046
|
+
updateTriggerHistory: async (...args) => {
|
|
1047
|
+
clientCalls.push({ method: 'updateTriggerHistory', args });
|
|
1048
|
+
},
|
|
1049
|
+
updateTriggerStatus: async (...args) => {
|
|
1050
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
1051
|
+
},
|
|
1052
|
+
};
|
|
1053
|
+
const handler = createTriggerHandler({
|
|
1054
|
+
config: {
|
|
1055
|
+
daemonToken: 'daemon-token',
|
|
1056
|
+
apiUrl: 'https://api.example',
|
|
1057
|
+
pollingIntervalMs: 5000,
|
|
1058
|
+
maxPollingIntervalMs: 120_000,
|
|
1059
|
+
timeoutMs: 1500,
|
|
1060
|
+
idleTimeoutMs: 500,
|
|
1061
|
+
runnerCmd: 'opencode',
|
|
1062
|
+
preventSleepWhileBusy: false,
|
|
1063
|
+
},
|
|
1064
|
+
client: client,
|
|
1065
|
+
}, {
|
|
1066
|
+
pathExists: () => true,
|
|
1067
|
+
createRunnerFactory: () => () => ({
|
|
1068
|
+
run: async (opts) => {
|
|
1069
|
+
// 원인 있는 RESULT를 한 번 낸 뒤 프로세스가 걸려 fail-safe 워치독에 죽은 경우.
|
|
1070
|
+
// 러너는 `idleTimedOut`을 세우지 않고 `timedOut`만 true로 돌려준다.
|
|
1071
|
+
opts.onStdoutChunk?.("[Result] Failed: The 'gpt-5' model is not supported", 'RESULT');
|
|
1072
|
+
return {
|
|
1073
|
+
exitCode: 1,
|
|
1074
|
+
timedOut: true,
|
|
1075
|
+
lastOutput: 'general output',
|
|
1076
|
+
errorMessage: 'Runner fail-safe timed out after 6h',
|
|
1077
|
+
};
|
|
1078
|
+
},
|
|
1079
|
+
}),
|
|
1080
|
+
createLogReporter: () => ({
|
|
1081
|
+
start: () => undefined,
|
|
1082
|
+
append: () => undefined,
|
|
1083
|
+
stop: async () => undefined,
|
|
1084
|
+
}),
|
|
1085
|
+
readHistoryFile: async () => '',
|
|
1086
|
+
resolveRunnerHistoryPaths: () => ({
|
|
1087
|
+
currentHistoryPath: '/auth/path/.agentteams/runner/history/trigger-1.md',
|
|
1088
|
+
parentHistoryPath: null,
|
|
1089
|
+
}),
|
|
1090
|
+
});
|
|
1091
|
+
await handler({ ...trigger, parentTriggerId: null });
|
|
1092
|
+
assert.deepEqual(clientCalls.at(-1)?.args, ['trigger-1', 'FAILED', 'Runner fail-safe timed out after 6h']);
|
|
1093
|
+
});
|
|
845
1094
|
test('createTriggerHandler fails without running the runner when worktree creation fails', async () => {
|
|
846
1095
|
const clientCalls = [];
|
|
847
1096
|
const runnerInputs = [];
|