@agentteams/runner 0.0.109 → 0.0.111
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.js +6 -0
- package/dist/api-client.js.map +1 -1
- package/dist/api-client.test.js +21 -12
- package/dist/api-client.test.js.map +1 -1
- package/dist/handlers/trigger-handler.js +20 -0
- package/dist/handlers/trigger-handler.js.map +1 -1
- package/dist/handlers/trigger-handler.test.js +290 -7
- package/dist/handlers/trigger-handler.test.js.map +1 -1
- package/dist/utils/machine-id.d.ts +32 -0
- package/dist/utils/machine-id.js +88 -0
- package/dist/utils/machine-id.js.map +1 -0
- package/dist/utils/machine-id.test.d.ts +1 -0
- package/dist/utils/machine-id.test.js +97 -0
- package/dist/utils/machine-id.test.js.map +1 -0
- package/dist/utils/working-directory.d.ts +27 -0
- package/dist/utils/working-directory.js +96 -0
- package/dist/utils/working-directory.js.map +1 -0
- package/dist/utils/working-directory.test.d.ts +1 -0
- package/dist/utils/working-directory.test.js +145 -0
- package/dist/utils/working-directory.test.js.map +1 -0
- package/native/bin/win32-x64/agentrunner-launcher.exe +0 -0
- package/native/bin/win32-x64/manifest.json +2 -2
- package/package.json +3 -2
|
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
|
|
|
2
2
|
import test, { mock } from 'node:test';
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
4
|
import { existsSync } from 'node:fs';
|
|
5
|
-
import { mkdtemp, rm, stat } from 'node:fs/promises';
|
|
5
|
+
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';
|
|
@@ -96,6 +96,7 @@ test('createTriggerHandler runs the runner, reports history, and marks success',
|
|
|
96
96
|
discoveredAuthPaths.push(authPath);
|
|
97
97
|
},
|
|
98
98
|
}, {
|
|
99
|
+
pathExists: () => true,
|
|
99
100
|
createRunnerFactory: () => () => runner,
|
|
100
101
|
createLogReporter: () => ({
|
|
101
102
|
start: () => {
|
|
@@ -325,6 +326,7 @@ test('createTriggerHandler preserves the local history file and still marks succ
|
|
|
325
326
|
},
|
|
326
327
|
client: client,
|
|
327
328
|
}, {
|
|
329
|
+
pathExists: () => true,
|
|
328
330
|
createRunnerFactory: () => () => ({
|
|
329
331
|
run: async () => ({ exitCode: 0, outputText: '{"type":"result"}' }),
|
|
330
332
|
}),
|
|
@@ -377,6 +379,7 @@ test('createTriggerHandler flags NEEDS_REVIEW when the runner exits 0 without wr
|
|
|
377
379
|
},
|
|
378
380
|
client: client,
|
|
379
381
|
}, {
|
|
382
|
+
pathExists: () => true,
|
|
380
383
|
createRunnerFactory: () => () => ({
|
|
381
384
|
run: async () => ({ exitCode: 0, outputText: 'The final summary.' }),
|
|
382
385
|
}),
|
|
@@ -433,6 +436,7 @@ test('createTriggerHandler strips a UTF-8 BOM before reporting history to the da
|
|
|
433
436
|
},
|
|
434
437
|
client: client,
|
|
435
438
|
}, {
|
|
439
|
+
pathExists: () => true,
|
|
436
440
|
createRunnerFactory: () => () => ({
|
|
437
441
|
run: async () => ({ exitCode: 0 }),
|
|
438
442
|
}),
|
|
@@ -482,6 +486,7 @@ test('createTriggerHandler keeps history unchanged when the Questions for User s
|
|
|
482
486
|
},
|
|
483
487
|
client: client,
|
|
484
488
|
}, {
|
|
489
|
+
pathExists: () => true,
|
|
485
490
|
createRunnerFactory: () => () => ({
|
|
486
491
|
run: async () => ({ exitCode: 0 }),
|
|
487
492
|
}),
|
|
@@ -528,6 +533,7 @@ test('createTriggerHandler restores parent history from server-side coaction con
|
|
|
528
533
|
},
|
|
529
534
|
client: client,
|
|
530
535
|
}, {
|
|
536
|
+
pathExists: () => true,
|
|
531
537
|
createRunnerFactory: () => () => ({
|
|
532
538
|
run: async (input) => {
|
|
533
539
|
runnerInputs.push({ prompt: input.prompt, authPath: input.authPath });
|
|
@@ -586,6 +592,7 @@ test('createTriggerHandler strips a UTF-8 BOM before restoring parent history fr
|
|
|
586
592
|
},
|
|
587
593
|
client: client,
|
|
588
594
|
}, {
|
|
595
|
+
pathExists: () => true,
|
|
589
596
|
createRunnerFactory: () => () => ({
|
|
590
597
|
run: async () => ({ exitCode: 0 }),
|
|
591
598
|
}),
|
|
@@ -635,6 +642,7 @@ test('createTriggerHandler overwrites existing parent history with server cumula
|
|
|
635
642
|
},
|
|
636
643
|
client: client,
|
|
637
644
|
}, {
|
|
645
|
+
pathExists: () => true,
|
|
638
646
|
createRunnerFactory: () => () => ({
|
|
639
647
|
run: async () => ({ exitCode: 0 }),
|
|
640
648
|
}),
|
|
@@ -685,6 +693,7 @@ test('createTriggerHandler reports runner failures and falls back to last output
|
|
|
685
693
|
},
|
|
686
694
|
client: client,
|
|
687
695
|
}, {
|
|
696
|
+
pathExists: () => true,
|
|
688
697
|
createRunnerFactory: () => () => ({
|
|
689
698
|
run: async () => ({ exitCode: 1, lastOutput: 'last output' }),
|
|
690
699
|
}),
|
|
@@ -727,6 +736,7 @@ test('createTriggerHandler reports the explicit runner error before last output'
|
|
|
727
736
|
},
|
|
728
737
|
client: client,
|
|
729
738
|
}, {
|
|
739
|
+
pathExists: () => true,
|
|
730
740
|
createRunnerFactory: () => () => ({
|
|
731
741
|
run: async () => ({
|
|
732
742
|
exitCode: 1,
|
|
@@ -786,6 +796,7 @@ test('createTriggerHandler fails without running the runner when worktree creati
|
|
|
786
796
|
},
|
|
787
797
|
client: client,
|
|
788
798
|
}, {
|
|
799
|
+
pathExists: () => true,
|
|
789
800
|
createRunnerFactory: () => () => ({
|
|
790
801
|
run: async (input) => {
|
|
791
802
|
runnerInputs.push({ authPath: input.authPath });
|
|
@@ -851,6 +862,7 @@ test('createTriggerHandler fails without running the runner when worktree authPa
|
|
|
851
862
|
},
|
|
852
863
|
client: client,
|
|
853
864
|
}, {
|
|
865
|
+
pathExists: () => true,
|
|
854
866
|
createRunnerFactory: () => () => ({
|
|
855
867
|
run: async (input) => {
|
|
856
868
|
runnerInputs.push({ authPath: input.authPath });
|
|
@@ -918,6 +930,7 @@ test('createTriggerHandler resolves a member repo via remoteUrl when worktree au
|
|
|
918
930
|
discoveredAuthPaths.push(authPath);
|
|
919
931
|
},
|
|
920
932
|
}, {
|
|
933
|
+
pathExists: () => true,
|
|
921
934
|
createRunnerFactory: () => () => ({
|
|
922
935
|
run: async (input) => {
|
|
923
936
|
runnerInputs.push({ authPath: input.authPath });
|
|
@@ -996,6 +1009,7 @@ test('createTriggerHandler fails without running the runner when member repo res
|
|
|
996
1009
|
},
|
|
997
1010
|
client: client,
|
|
998
1011
|
}, {
|
|
1012
|
+
pathExists: () => true,
|
|
999
1013
|
createRunnerFactory: () => () => ({
|
|
1000
1014
|
run: async (input) => {
|
|
1001
1015
|
runnerInputs.push({ authPath: input.authPath });
|
|
@@ -1075,6 +1089,7 @@ test('worktrees created via member repo resolution are removed by the poller lif
|
|
|
1075
1089
|
discoveredAuthPaths.push(authPath);
|
|
1076
1090
|
},
|
|
1077
1091
|
}, {
|
|
1092
|
+
pathExists: () => true,
|
|
1078
1093
|
createRunnerFactory: () => () => ({
|
|
1079
1094
|
run: async () => ({ exitCode: 0 }),
|
|
1080
1095
|
}),
|
|
@@ -1195,6 +1210,7 @@ test('createTriggerHandler downgrades an idle-timeout to NEEDS_REVIEW when a his
|
|
|
1195
1210
|
},
|
|
1196
1211
|
client: client,
|
|
1197
1212
|
}, {
|
|
1213
|
+
pathExists: () => true,
|
|
1198
1214
|
createRunnerFactory: () => () => ({
|
|
1199
1215
|
run: async () => ({
|
|
1200
1216
|
exitCode: 1,
|
|
@@ -1244,6 +1260,7 @@ test('createTriggerHandler keeps an idle-timeout as FAILED when no history file
|
|
|
1244
1260
|
},
|
|
1245
1261
|
client: client,
|
|
1246
1262
|
}, {
|
|
1263
|
+
pathExists: () => true,
|
|
1247
1264
|
createRunnerFactory: () => () => ({
|
|
1248
1265
|
// idle 타임아웃이지만 히스토리 파일도, stdout 폴백도 없으면 작업 완료를 보장할 수 없어 FAILED 유지.
|
|
1249
1266
|
run: async () => ({
|
|
@@ -1293,6 +1310,7 @@ test('createTriggerHandler stores stdout as fallback history when the runner omi
|
|
|
1293
1310
|
},
|
|
1294
1311
|
client: client,
|
|
1295
1312
|
}, {
|
|
1313
|
+
pathExists: () => true,
|
|
1296
1314
|
createRunnerFactory: () => () => ({
|
|
1297
1315
|
run: async () => ({
|
|
1298
1316
|
exitCode: 0,
|
|
@@ -1351,6 +1369,7 @@ test('createTriggerHandler truncates long agent output in fallback history', asy
|
|
|
1351
1369
|
},
|
|
1352
1370
|
client: client,
|
|
1353
1371
|
}, {
|
|
1372
|
+
pathExists: () => true,
|
|
1354
1373
|
createRunnerFactory: () => () => ({
|
|
1355
1374
|
run: async () => ({
|
|
1356
1375
|
exitCode: 0,
|
|
@@ -1406,6 +1425,7 @@ test('createTriggerHandler preserves fallback agent output within history limit'
|
|
|
1406
1425
|
},
|
|
1407
1426
|
client: client,
|
|
1408
1427
|
}, {
|
|
1428
|
+
pathExists: () => true,
|
|
1409
1429
|
createRunnerFactory: () => () => ({
|
|
1410
1430
|
run: async () => ({
|
|
1411
1431
|
exitCode: 0,
|
|
@@ -1461,6 +1481,7 @@ test('createTriggerHandler cancels the runner when the server reports a cancel r
|
|
|
1461
1481
|
},
|
|
1462
1482
|
client: client,
|
|
1463
1483
|
}, {
|
|
1484
|
+
pathExists: () => true,
|
|
1464
1485
|
createRunnerFactory: () => () => ({
|
|
1465
1486
|
run: async ({ signal }) => {
|
|
1466
1487
|
await new Promise((resolve) => {
|
|
@@ -1537,6 +1558,10 @@ test('createTriggerHandler marks the trigger as failed when runtime loading thro
|
|
|
1537
1558
|
// ---------------------------------------------------------------------------
|
|
1538
1559
|
const withTempDir = async (run) => {
|
|
1539
1560
|
const dir = await mkdtemp(join(tmpdir(), 'trigger-handler-hook-test-'));
|
|
1561
|
+
// 실행 전 작업 디렉터리 검증(checkRunnerWorkingDirectory)이 프로젝트 마커를 요구하므로,
|
|
1562
|
+
// 임시 워크스페이스도 실제 러너 워크스페이스와 같은 마커(트리거와 같은 projectId)를 갖춘다.
|
|
1563
|
+
await mkdir(join(dir, '.agentteams'), { recursive: true });
|
|
1564
|
+
await writeFile(join(dir, '.agentteams', 'config.json'), `${JSON.stringify({ projectId: runtime.projectId })}\n`, 'utf8');
|
|
1540
1565
|
try {
|
|
1541
1566
|
await run(dir);
|
|
1542
1567
|
}
|
|
@@ -1782,7 +1807,7 @@ test('createTriggerHandler logs but does not throw when attachment cleanup fails
|
|
|
1782
1807
|
assert.deepEqual(cleanupCalls, [join(dir, '.agentteams', 'runner', 'attachments', 'trigger-1')]);
|
|
1783
1808
|
});
|
|
1784
1809
|
});
|
|
1785
|
-
test('createTriggerHandler fails before runner execution when
|
|
1810
|
+
test('createTriggerHandler fails before runner execution when the runner workspace is not configured', async () => {
|
|
1786
1811
|
const clientCalls = [];
|
|
1787
1812
|
let runnerCalled = false;
|
|
1788
1813
|
const client = {
|
|
@@ -1836,11 +1861,11 @@ test('createTriggerHandler fails before runner execution when attachments have n
|
|
|
1836
1861
|
});
|
|
1837
1862
|
await handler({ ...trigger, parentTriggerId: null });
|
|
1838
1863
|
assert.equal(runnerCalled, false);
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1864
|
+
const [triggerId, status, message] = (clientCalls.at(-1)?.args ?? []);
|
|
1865
|
+
assert.equal(triggerId, 'trigger-1');
|
|
1866
|
+
assert.equal(status, 'FAILED');
|
|
1867
|
+
// 워크스페이스 경로가 없는 실행은 첨부 전달 단계보다 앞선 작업 디렉터리 검증에서 끝난다.
|
|
1868
|
+
assert.match(message, /Runner workspace path is not configured/);
|
|
1844
1869
|
});
|
|
1845
1870
|
test('createTriggerHandler does not append history or convention text to the API prompt', async () => {
|
|
1846
1871
|
await withTempDir(async (dir) => {
|
|
@@ -1924,6 +1949,7 @@ test('createTriggerHandler surfaces a user-visible warning when the runner ignor
|
|
|
1924
1949
|
},
|
|
1925
1950
|
client: client,
|
|
1926
1951
|
}, {
|
|
1952
|
+
pathExists: () => true,
|
|
1927
1953
|
createRunnerFactory: () => () => ({
|
|
1928
1954
|
run: async (input) => {
|
|
1929
1955
|
runnerInputs.push({ model: input.model, fastMode: input.fastMode });
|
|
@@ -1973,6 +1999,7 @@ test('createTriggerHandler does not warn when the runner supports the requested
|
|
|
1973
1999
|
},
|
|
1974
2000
|
client: client,
|
|
1975
2001
|
}, {
|
|
2002
|
+
pathExists: () => true,
|
|
1976
2003
|
createRunnerFactory: () => () => ({
|
|
1977
2004
|
run: async () => ({ exitCode: 0 }),
|
|
1978
2005
|
}),
|
|
@@ -1993,4 +2020,260 @@ test('createTriggerHandler does not warn when the runner supports the requested
|
|
|
1993
2020
|
const unsupportedWarnings = logEntries.filter((entry) => entry.level === 'WARN' && /is not supported by runner/.test(entry.message));
|
|
1994
2021
|
assert.deepEqual(unsupportedWarnings, []);
|
|
1995
2022
|
});
|
|
2023
|
+
// ---------------------------------------------------------------------------
|
|
2024
|
+
// Working directory pre-flight tests
|
|
2025
|
+
// ---------------------------------------------------------------------------
|
|
2026
|
+
test('createTriggerHandler fails without creating the workspace when authPath is missing on this runner', async () => {
|
|
2027
|
+
await withTempDir(async (dir) => {
|
|
2028
|
+
const missingAuthPath = join(dir, 'not-on-this-machine');
|
|
2029
|
+
const clientCalls = [];
|
|
2030
|
+
const logEntries = [];
|
|
2031
|
+
let runnerCalled = false;
|
|
2032
|
+
const client = {
|
|
2033
|
+
fetchTriggerRuntime: async () => ({ ...runtime, authPath: missingAuthPath }),
|
|
2034
|
+
isTriggerCancelRequested: async () => false,
|
|
2035
|
+
updateTriggerHistory: async (...args) => {
|
|
2036
|
+
clientCalls.push({ method: 'updateTriggerHistory', args });
|
|
2037
|
+
},
|
|
2038
|
+
updateTriggerStatus: async (...args) => {
|
|
2039
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
2040
|
+
},
|
|
2041
|
+
};
|
|
2042
|
+
const handler = createTriggerHandler({
|
|
2043
|
+
config: {
|
|
2044
|
+
daemonToken: 'daemon-token',
|
|
2045
|
+
apiUrl: 'https://api.example',
|
|
2046
|
+
pollingIntervalMs: 5000,
|
|
2047
|
+
maxPollingIntervalMs: 120_000,
|
|
2048
|
+
timeoutMs: 1500,
|
|
2049
|
+
idleTimeoutMs: 500,
|
|
2050
|
+
runnerCmd: 'opencode',
|
|
2051
|
+
preventSleepWhileBusy: false,
|
|
2052
|
+
},
|
|
2053
|
+
client: client,
|
|
2054
|
+
}, {
|
|
2055
|
+
createRunnerFactory: () => () => ({
|
|
2056
|
+
run: async () => {
|
|
2057
|
+
runnerCalled = true;
|
|
2058
|
+
return { exitCode: 0 };
|
|
2059
|
+
},
|
|
2060
|
+
}),
|
|
2061
|
+
createLogReporter: () => ({
|
|
2062
|
+
start: () => undefined,
|
|
2063
|
+
append: (level, message) => {
|
|
2064
|
+
logEntries.push({ level, message });
|
|
2065
|
+
},
|
|
2066
|
+
stop: async () => undefined,
|
|
2067
|
+
}),
|
|
2068
|
+
readHistoryFile: async () => '',
|
|
2069
|
+
});
|
|
2070
|
+
await handler({ ...trigger, parentTriggerId: null });
|
|
2071
|
+
assert.equal(runnerCalled, false);
|
|
2072
|
+
assert.equal(existsSync(missingAuthPath), false);
|
|
2073
|
+
const statusCall = clientCalls.filter((call) => call.method === 'updateTriggerStatus').at(-1);
|
|
2074
|
+
assert.equal(statusCall?.args[1], 'FAILED');
|
|
2075
|
+
assert.match(String(statusCall?.args[2]), /does not exist on this runner/);
|
|
2076
|
+
assert.match(String(statusCall?.args[2]), /machine where this agent was registered/);
|
|
2077
|
+
assert.equal(logEntries.some((entry) => entry.level === 'ERROR' && /does not exist on this runner/.test(entry.message)), true);
|
|
2078
|
+
});
|
|
2079
|
+
});
|
|
2080
|
+
test('createTriggerHandler fails when the workspace exists but is neither a project nor a git repository', async () => {
|
|
2081
|
+
await withTempDir(async (dir) => {
|
|
2082
|
+
const plainDir = join(dir, 'plain');
|
|
2083
|
+
await mkdir(plainDir);
|
|
2084
|
+
const clientCalls = [];
|
|
2085
|
+
let runnerCalled = false;
|
|
2086
|
+
const client = {
|
|
2087
|
+
fetchTriggerRuntime: async () => ({ ...runtime, authPath: plainDir }),
|
|
2088
|
+
isTriggerCancelRequested: async () => false,
|
|
2089
|
+
updateTriggerHistory: async () => undefined,
|
|
2090
|
+
updateTriggerStatus: async (...args) => {
|
|
2091
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
2092
|
+
},
|
|
2093
|
+
};
|
|
2094
|
+
const handler = createTriggerHandler({
|
|
2095
|
+
config: {
|
|
2096
|
+
daemonToken: 'daemon-token',
|
|
2097
|
+
apiUrl: 'https://api.example',
|
|
2098
|
+
pollingIntervalMs: 5000,
|
|
2099
|
+
maxPollingIntervalMs: 120_000,
|
|
2100
|
+
timeoutMs: 1500,
|
|
2101
|
+
idleTimeoutMs: 500,
|
|
2102
|
+
runnerCmd: 'opencode',
|
|
2103
|
+
preventSleepWhileBusy: false,
|
|
2104
|
+
},
|
|
2105
|
+
client: client,
|
|
2106
|
+
}, {
|
|
2107
|
+
createRunnerFactory: () => () => ({
|
|
2108
|
+
run: async () => {
|
|
2109
|
+
runnerCalled = true;
|
|
2110
|
+
return { exitCode: 0 };
|
|
2111
|
+
},
|
|
2112
|
+
}),
|
|
2113
|
+
createLogReporter: () => ({
|
|
2114
|
+
start: () => undefined,
|
|
2115
|
+
append: () => undefined,
|
|
2116
|
+
stop: async () => undefined,
|
|
2117
|
+
}),
|
|
2118
|
+
readHistoryFile: async () => '',
|
|
2119
|
+
});
|
|
2120
|
+
await handler({ ...trigger, parentTriggerId: null });
|
|
2121
|
+
assert.equal(runnerCalled, false);
|
|
2122
|
+
assert.equal(existsSync(join(plainDir, '.agentteams')), false);
|
|
2123
|
+
assert.equal(clientCalls.at(-1)?.args[1], 'FAILED');
|
|
2124
|
+
assert.match(String(clientCalls.at(-1)?.args[2]), /is neither a git repository nor an AgentTeams project/);
|
|
2125
|
+
});
|
|
2126
|
+
});
|
|
2127
|
+
test('createTriggerHandler runs normally when the workspace carries the project marker', async () => {
|
|
2128
|
+
await withTempDir(async (dir) => {
|
|
2129
|
+
const runnerInputs = [];
|
|
2130
|
+
const clientCalls = [];
|
|
2131
|
+
const client = {
|
|
2132
|
+
fetchTriggerRuntime: async () => ({ ...runtime, authPath: dir }),
|
|
2133
|
+
isTriggerCancelRequested: async () => false,
|
|
2134
|
+
updateTriggerHistory: async () => undefined,
|
|
2135
|
+
updateTriggerStatus: async (...args) => {
|
|
2136
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
2137
|
+
},
|
|
2138
|
+
};
|
|
2139
|
+
const handler = createTriggerHandler({
|
|
2140
|
+
config: {
|
|
2141
|
+
daemonToken: 'daemon-token',
|
|
2142
|
+
apiUrl: 'https://api.example',
|
|
2143
|
+
pollingIntervalMs: 5000,
|
|
2144
|
+
maxPollingIntervalMs: 120_000,
|
|
2145
|
+
timeoutMs: 1500,
|
|
2146
|
+
idleTimeoutMs: 500,
|
|
2147
|
+
runnerCmd: 'opencode',
|
|
2148
|
+
preventSleepWhileBusy: false,
|
|
2149
|
+
},
|
|
2150
|
+
client: client,
|
|
2151
|
+
}, {
|
|
2152
|
+
createRunnerFactory: () => () => ({
|
|
2153
|
+
run: async (input) => {
|
|
2154
|
+
runnerInputs.push({ authPath: input.authPath });
|
|
2155
|
+
return { exitCode: 0 };
|
|
2156
|
+
},
|
|
2157
|
+
}),
|
|
2158
|
+
createLogReporter: () => ({
|
|
2159
|
+
start: () => undefined,
|
|
2160
|
+
append: () => undefined,
|
|
2161
|
+
stop: async () => undefined,
|
|
2162
|
+
}),
|
|
2163
|
+
readHistoryFile: async () => '### Summary\n- done\n',
|
|
2164
|
+
resolveRunnerHistoryPaths: () => ({
|
|
2165
|
+
currentHistoryPath: join(dir, '.agentteams/runner/history/trigger-1.md'),
|
|
2166
|
+
parentHistoryPath: null,
|
|
2167
|
+
}),
|
|
2168
|
+
});
|
|
2169
|
+
await handler({ ...trigger, parentTriggerId: null });
|
|
2170
|
+
assert.deepEqual(runnerInputs, [{ authPath: dir }]);
|
|
2171
|
+
assert.equal(clientCalls.at(-1)?.args[1], 'DONE');
|
|
2172
|
+
});
|
|
2173
|
+
});
|
|
2174
|
+
test('createTriggerHandler fails when the workspace marker belongs to another project', async () => {
|
|
2175
|
+
await withTempDir(async (dir) => {
|
|
2176
|
+
// 같은 사용자명·같은 디렉터리 배치를 쓰는 머신에서는 다른 머신의 authPath가 "존재하는 어떤
|
|
2177
|
+
// 프로젝트"에 그대로 맞아떨어진다. 마커의 projectId까지 대조해야 조용한 오실행이 사라진다.
|
|
2178
|
+
const otherProjectDir = join(dir, 'other-project');
|
|
2179
|
+
await mkdir(join(otherProjectDir, '.agentteams'), { recursive: true });
|
|
2180
|
+
await writeFile(join(otherProjectDir, '.agentteams', 'config.json'), `${JSON.stringify({ projectId: 'project-other' })}\n`, 'utf8');
|
|
2181
|
+
const clientCalls = [];
|
|
2182
|
+
let runnerCalled = false;
|
|
2183
|
+
const client = {
|
|
2184
|
+
fetchTriggerRuntime: async () => ({ ...runtime, authPath: otherProjectDir }),
|
|
2185
|
+
isTriggerCancelRequested: async () => false,
|
|
2186
|
+
updateTriggerHistory: async () => undefined,
|
|
2187
|
+
updateTriggerStatus: async (...args) => {
|
|
2188
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
2189
|
+
},
|
|
2190
|
+
};
|
|
2191
|
+
const handler = createTriggerHandler({
|
|
2192
|
+
config: {
|
|
2193
|
+
daemonToken: 'daemon-token',
|
|
2194
|
+
apiUrl: 'https://api.example',
|
|
2195
|
+
pollingIntervalMs: 5000,
|
|
2196
|
+
maxPollingIntervalMs: 120_000,
|
|
2197
|
+
timeoutMs: 1500,
|
|
2198
|
+
idleTimeoutMs: 500,
|
|
2199
|
+
runnerCmd: 'opencode',
|
|
2200
|
+
preventSleepWhileBusy: false,
|
|
2201
|
+
},
|
|
2202
|
+
client: client,
|
|
2203
|
+
}, {
|
|
2204
|
+
isGitRepo: () => true,
|
|
2205
|
+
createRunnerFactory: () => () => ({
|
|
2206
|
+
run: async () => {
|
|
2207
|
+
runnerCalled = true;
|
|
2208
|
+
return { exitCode: 0 };
|
|
2209
|
+
},
|
|
2210
|
+
}),
|
|
2211
|
+
createLogReporter: () => ({
|
|
2212
|
+
start: () => undefined,
|
|
2213
|
+
append: () => undefined,
|
|
2214
|
+
stop: async () => undefined,
|
|
2215
|
+
}),
|
|
2216
|
+
readHistoryFile: async () => '',
|
|
2217
|
+
});
|
|
2218
|
+
await handler({ ...trigger, parentTriggerId: null });
|
|
2219
|
+
assert.equal(runnerCalled, false);
|
|
2220
|
+
assert.equal(clientCalls.at(-1)?.args[1], 'FAILED');
|
|
2221
|
+
assert.match(String(clientCalls.at(-1)?.args[2]), /belongs to a different AgentTeams project/);
|
|
2222
|
+
});
|
|
2223
|
+
});
|
|
2224
|
+
test('createTriggerHandler skips the pre-flight for worktree runs that already validate their path', async () => {
|
|
2225
|
+
await withTempDir(async (dir) => {
|
|
2226
|
+
// RunnerBox 경로는 authPath가 아니라 생성된 워크트리에서 실행되므로, 마커가 없는 git 저장소
|
|
2227
|
+
// 루트여도 기존 동작(worktree 생성 후 실행)이 유지되어야 한다.
|
|
2228
|
+
const repoDir = join(dir, 'repo');
|
|
2229
|
+
await mkdir(repoDir);
|
|
2230
|
+
const worktreePath = join(dir, 'worktrees', 'wt-1');
|
|
2231
|
+
const runnerInputs = [];
|
|
2232
|
+
const clientCalls = [];
|
|
2233
|
+
const client = {
|
|
2234
|
+
fetchTriggerRuntime: async () => ({ ...runtime, authPath: repoDir, useWorktree: true, worktreeId: 'wt-1' }),
|
|
2235
|
+
isTriggerCancelRequested: async () => false,
|
|
2236
|
+
updateTriggerHistory: async () => undefined,
|
|
2237
|
+
reportWorktreeStatus: async () => undefined,
|
|
2238
|
+
updateTriggerStatus: async (...args) => {
|
|
2239
|
+
clientCalls.push({ method: 'updateTriggerStatus', args });
|
|
2240
|
+
},
|
|
2241
|
+
};
|
|
2242
|
+
const handler = createTriggerHandler({
|
|
2243
|
+
config: {
|
|
2244
|
+
daemonToken: 'daemon-token',
|
|
2245
|
+
apiUrl: 'https://api.example',
|
|
2246
|
+
pollingIntervalMs: 5000,
|
|
2247
|
+
maxPollingIntervalMs: 120_000,
|
|
2248
|
+
timeoutMs: 1500,
|
|
2249
|
+
idleTimeoutMs: 500,
|
|
2250
|
+
runnerCmd: 'opencode',
|
|
2251
|
+
preventSleepWhileBusy: false,
|
|
2252
|
+
},
|
|
2253
|
+
client: client,
|
|
2254
|
+
}, {
|
|
2255
|
+
isGitRepo: () => true,
|
|
2256
|
+
createWorktree: () => worktreePath,
|
|
2257
|
+
createRunnerFactory: () => () => ({
|
|
2258
|
+
run: async (input) => {
|
|
2259
|
+
runnerInputs.push({ authPath: input.authPath });
|
|
2260
|
+
return { exitCode: 0 };
|
|
2261
|
+
},
|
|
2262
|
+
}),
|
|
2263
|
+
createLogReporter: () => ({
|
|
2264
|
+
start: () => undefined,
|
|
2265
|
+
append: () => undefined,
|
|
2266
|
+
stop: async () => undefined,
|
|
2267
|
+
}),
|
|
2268
|
+
readHistoryFile: async () => '### Summary\n- done\n',
|
|
2269
|
+
resolveRunnerHistoryPaths: () => ({
|
|
2270
|
+
currentHistoryPath: join(worktreePath, '.agentteams/runner/history/trigger-1.md'),
|
|
2271
|
+
parentHistoryPath: null,
|
|
2272
|
+
}),
|
|
2273
|
+
});
|
|
2274
|
+
await handler({ ...trigger, useWorktree: true, worktreeId: 'wt-1', parentTriggerId: null });
|
|
2275
|
+
assert.deepEqual(runnerInputs, [{ authPath: worktreePath }]);
|
|
2276
|
+
assert.equal(clientCalls.at(-1)?.args[1], 'DONE');
|
|
2277
|
+
});
|
|
2278
|
+
});
|
|
1996
2279
|
//# sourceMappingURL=trigger-handler.test.js.map
|