@agentteams/runner 0.0.108 → 0.0.110

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.
Files changed (74) hide show
  1. package/dist/api-client.js +6 -0
  2. package/dist/api-client.js.map +1 -1
  3. package/dist/api-client.test.js +21 -12
  4. package/dist/api-client.test.js.map +1 -1
  5. package/dist/handlers/trigger-handler.js +24 -4
  6. package/dist/handlers/trigger-handler.js.map +1 -1
  7. package/dist/handlers/trigger-handler.test.js +296 -13
  8. package/dist/handlers/trigger-handler.test.js.map +1 -1
  9. package/dist/runners/amp.js +2 -2
  10. package/dist/runners/amp.js.map +1 -1
  11. package/dist/runners/antigravity.d.ts +5 -2
  12. package/dist/runners/antigravity.js +30 -16
  13. package/dist/runners/antigravity.js.map +1 -1
  14. package/dist/runners/antigravity.test.js +9 -8
  15. package/dist/runners/antigravity.test.js.map +1 -1
  16. package/dist/runners/claude-code.js +2 -2
  17. package/dist/runners/claude-code.js.map +1 -1
  18. package/dist/runners/codex-json-parser.d.ts +17 -0
  19. package/dist/runners/codex-json-parser.js +207 -0
  20. package/dist/runners/codex-json-parser.js.map +1 -0
  21. package/dist/runners/codex-json-parser.test.d.ts +1 -0
  22. package/dist/runners/codex-json-parser.test.js +77 -0
  23. package/dist/runners/codex-json-parser.test.js.map +1 -0
  24. package/dist/runners/codex.js +25 -13
  25. package/dist/runners/codex.js.map +1 -1
  26. package/dist/runners/codex.test.js +6 -0
  27. package/dist/runners/codex.test.js.map +1 -1
  28. package/dist/runners/copilot-cli.js +22 -10
  29. package/dist/runners/copilot-cli.js.map +1 -1
  30. package/dist/runners/copilot-cli.test.js +20 -4
  31. package/dist/runners/copilot-cli.test.js.map +1 -1
  32. package/dist/runners/copilot-json-parser.d.ts +18 -0
  33. package/dist/runners/copilot-json-parser.js +154 -0
  34. package/dist/runners/copilot-json-parser.js.map +1 -0
  35. package/dist/runners/copilot-json-parser.test.d.ts +1 -0
  36. package/dist/runners/copilot-json-parser.test.js +59 -0
  37. package/dist/runners/copilot-json-parser.test.js.map +1 -0
  38. package/dist/runners/cursor-cli.js +2 -2
  39. package/dist/runners/cursor-cli.js.map +1 -1
  40. package/dist/runners/kimi-cli.js +1 -1
  41. package/dist/runners/kimi-cli.js.map +1 -1
  42. package/dist/runners/log-reporter.d.ts +2 -2
  43. package/dist/runners/log-reporter.js +19 -14
  44. package/dist/runners/log-reporter.js.map +1 -1
  45. package/dist/runners/log-reporter.test.js +49 -12
  46. package/dist/runners/log-reporter.test.js.map +1 -1
  47. package/dist/runners/opencode-json-parser.js +12 -4
  48. package/dist/runners/opencode-json-parser.js.map +1 -1
  49. package/dist/runners/opencode-json-parser.test.js +11 -6
  50. package/dist/runners/opencode-json-parser.test.js.map +1 -1
  51. package/dist/runners/opencode.js +2 -2
  52. package/dist/runners/opencode.js.map +1 -1
  53. package/dist/runners/stream-json-parser.d.ts +3 -0
  54. package/dist/runners/stream-json-parser.js +34 -10
  55. package/dist/runners/stream-json-parser.js.map +1 -1
  56. package/dist/runners/stream-json-parser.test.js +21 -10
  57. package/dist/runners/stream-json-parser.test.js.map +1 -1
  58. package/dist/runners/types.d.ts +3 -2
  59. package/dist/types.d.ts +3 -0
  60. package/dist/utils/machine-id.d.ts +32 -0
  61. package/dist/utils/machine-id.js +88 -0
  62. package/dist/utils/machine-id.js.map +1 -0
  63. package/dist/utils/machine-id.test.d.ts +1 -0
  64. package/dist/utils/machine-id.test.js +97 -0
  65. package/dist/utils/machine-id.test.js.map +1 -0
  66. package/dist/utils/working-directory.d.ts +27 -0
  67. package/dist/utils/working-directory.js +96 -0
  68. package/dist/utils/working-directory.js.map +1 -0
  69. package/dist/utils/working-directory.test.d.ts +1 -0
  70. package/dist/utils/working-directory.test.js +145 -0
  71. package/dist/utils/working-directory.test.js.map +1 -0
  72. package/native/bin/win32-x64/agentrunner-launcher.exe +0 -0
  73. package/native/bin/win32-x64/manifest.json +2 -2
  74. package/package.json +1 -1
@@ -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';
@@ -75,8 +75,8 @@ test('createTriggerHandler runs the runner, reports history, and marks success',
75
75
  const runner = {
76
76
  run: async (input) => {
77
77
  runnerInputs.push({ prompt: input.prompt, authPath: input.authPath });
78
- input.onStdoutChunk?.('stdout');
79
- input.onStderrChunk?.('stderr');
78
+ input.onStdoutChunk?.('stdout', 'TEXT');
79
+ input.onStderrChunk?.('stderr', 'STDERR');
80
80
  return { exitCode: 0 };
81
81
  },
82
82
  };
@@ -96,13 +96,14 @@ 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: () => {
102
103
  logEntries.push({ level: 'START', message: 'started' });
103
104
  },
104
- append: (level, message) => {
105
- logEntries.push({ level, message });
105
+ append: (level, message, category, toolName) => {
106
+ logEntries.push({ level, message, category, toolName });
106
107
  },
107
108
  stop: async () => {
108
109
  logEntries.push({ level: 'STOP', message: 'stopped' });
@@ -118,8 +119,8 @@ test('createTriggerHandler runs the runner, reports history, and marks success',
118
119
  assert.deepEqual(discoveredAuthPaths, ['/auth/path']);
119
120
  assert.equal(runnerInputs.length, 1);
120
121
  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');
121
- assert.equal(logEntries.some((entry) => entry.level === 'INFO' && entry.message.includes('stdout')), true);
122
- assert.equal(logEntries.some((entry) => entry.level === 'WARN' && entry.message.includes('stderr')), true);
122
+ assert.equal(logEntries.some((entry) => entry.level === 'INFO' && entry.category === 'TEXT' && entry.message.includes('stdout')), true);
123
+ assert.equal(logEntries.some((entry) => entry.level === 'WARN' && entry.category === 'STDERR' && entry.message.includes('stderr')), true);
123
124
  assert.deepEqual(clientCalls.map((entry) => entry.method), ['fetchTriggerRuntime', 'isTriggerCancelRequested', 'updateTriggerHistory', 'updateTriggerStatus']);
124
125
  assert.deepEqual(clientCalls.at(-1)?.args, ['trigger-1', 'DONE', undefined]);
125
126
  });
@@ -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 attachments have no runner workspace', async () => {
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
- assert.deepEqual(clientCalls.at(-1)?.args, [
1840
- 'trigger-1',
1841
- 'FAILED',
1842
- 'Cannot deliver attachments because runner workspace path is not configured.',
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