@ai-devkit/agent-manager 0.28.1 → 0.30.0

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 (219) hide show
  1. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +1 -342
  2. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -1
  3. package/dist/__tests__/capacity/codex.test.js +343 -0
  4. package/dist/__tests__/capacity/codex.test.js.map +1 -0
  5. package/dist/__tests__/capacity/index.test.js +50 -0
  6. package/dist/__tests__/capacity/index.test.js.map +1 -0
  7. package/dist/__tests__/database/DurableAgentsDatabase.test.js +31 -3
  8. package/dist/__tests__/database/DurableAgentsDatabase.test.js.map +1 -1
  9. package/dist/__tests__/durable/CodexCliProbe.test.js +129 -0
  10. package/dist/__tests__/durable/CodexCliProbe.test.js.map +1 -0
  11. package/dist/__tests__/durable/CodexDurableAgentRepository.test.js +84 -0
  12. package/dist/__tests__/durable/CodexDurableAgentRepository.test.js.map +1 -0
  13. package/dist/__tests__/durable/CodexPrintAgent.integration.test.js +126 -0
  14. package/dist/__tests__/durable/CodexPrintAgent.integration.test.js.map +1 -0
  15. package/dist/__tests__/durable/CodexPrintAgentService.test.js +271 -0
  16. package/dist/__tests__/durable/CodexPrintAgentService.test.js.map +1 -0
  17. package/dist/__tests__/durable/CodexPrintRunner.test.js +393 -0
  18. package/dist/__tests__/durable/CodexPrintRunner.test.js.map +1 -0
  19. package/dist/__tests__/print/ClaudeCliProbe.test.js +3 -7
  20. package/dist/__tests__/print/ClaudeCliProbe.test.js.map +1 -1
  21. package/dist/__tests__/print/ClaudePrintAgent.integration.test.js +33 -27
  22. package/dist/__tests__/print/ClaudePrintAgent.integration.test.js.map +1 -1
  23. package/dist/__tests__/print/ClaudePrintAgentService.test.js +65 -0
  24. package/dist/__tests__/print/ClaudePrintAgentService.test.js.map +1 -1
  25. package/dist/__tests__/print/ClaudePrintRunner.test.js +4 -10
  26. package/dist/__tests__/print/ClaudePrintRunner.test.js.map +1 -1
  27. package/dist/__tests__/providers/claude/ClaudeAgentMapper.test.js +79 -0
  28. package/dist/__tests__/providers/claude/ClaudeAgentMapper.test.js.map +1 -0
  29. package/dist/__tests__/providers/claude/ClaudeSessionLocator.test.js +436 -0
  30. package/dist/__tests__/providers/claude/ClaudeSessionLocator.test.js.map +1 -0
  31. package/dist/__tests__/providers/pi/durable/PiCliProbe.test.js +76 -0
  32. package/dist/__tests__/providers/pi/durable/PiCliProbe.test.js.map +1 -0
  33. package/dist/__tests__/providers/pi/durable/PiDurableAgentRepository.test.js +43 -0
  34. package/dist/__tests__/providers/pi/durable/PiDurableAgentRepository.test.js.map +1 -0
  35. package/dist/__tests__/providers/pi/durable/PiPrintAgentService.test.js +202 -0
  36. package/dist/__tests__/providers/pi/durable/PiPrintAgentService.test.js.map +1 -0
  37. package/dist/__tests__/providers/pi/durable/PiPrintProtocol.test.js +119 -0
  38. package/dist/__tests__/providers/pi/durable/PiPrintProtocol.test.js.map +1 -0
  39. package/dist/__tests__/providers/pi/durable/PiPrintRunner.test.js +386 -0
  40. package/dist/__tests__/providers/pi/durable/PiPrintRunner.test.js.map +1 -0
  41. package/dist/__tests__/providers/pi/durable/PiStreamParser.test.js +34 -0
  42. package/dist/__tests__/providers/pi/durable/PiStreamParser.test.js.map +1 -0
  43. package/dist/__tests__/utils/ClaudeSessionParser.test.js +2 -2
  44. package/dist/__tests__/utils/ClaudeSessionParser.test.js.map +1 -1
  45. package/dist/adapters/index.d.ts +1 -1
  46. package/dist/adapters/index.d.ts.map +1 -1
  47. package/dist/adapters/index.js +1 -1
  48. package/dist/adapters/index.js.map +1 -1
  49. package/dist/capacity/codex.d.ts +36 -0
  50. package/dist/capacity/codex.d.ts.map +1 -0
  51. package/dist/capacity/codex.js +317 -0
  52. package/dist/capacity/codex.js.map +1 -0
  53. package/dist/capacity/index.d.ts +11 -0
  54. package/dist/capacity/index.d.ts.map +1 -0
  55. package/dist/capacity/index.js +49 -0
  56. package/dist/capacity/index.js.map +1 -0
  57. package/dist/capacity/types.d.ts +17 -0
  58. package/dist/capacity/types.d.ts.map +1 -0
  59. package/dist/capacity/types.js +3 -0
  60. package/dist/capacity/types.js.map +1 -0
  61. package/dist/database/migrations/004_codex_durable_sessions.sql +1 -0
  62. package/dist/durable/DurableAgent.d.ts +23 -3
  63. package/dist/durable/DurableAgent.d.ts.map +1 -1
  64. package/dist/durable/DurableAgent.js +12 -0
  65. package/dist/durable/DurableAgent.js.map +1 -1
  66. package/dist/durable/DurableAgentRepository.d.ts +6 -7
  67. package/dist/durable/DurableAgentRepository.d.ts.map +1 -1
  68. package/dist/durable/DurableAgentRepository.js +73 -42
  69. package/dist/durable/DurableAgentRepository.js.map +1 -1
  70. package/dist/durable/process.d.ts +8 -0
  71. package/dist/durable/process.d.ts.map +1 -0
  72. package/dist/durable/process.js +40 -0
  73. package/dist/durable/process.js.map +1 -0
  74. package/dist/durable/run.d.ts +32 -0
  75. package/dist/durable/run.d.ts.map +1 -0
  76. package/dist/durable/run.js +46 -0
  77. package/dist/durable/run.js.map +1 -0
  78. package/dist/durable/utils.d.ts +19 -0
  79. package/dist/durable/utils.d.ts.map +1 -0
  80. package/dist/durable/utils.js +52 -0
  81. package/dist/durable/utils.js.map +1 -0
  82. package/dist/index.d.ts +19 -13
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +10 -8
  85. package/dist/index.js.map +1 -1
  86. package/dist/providers/claude/ClaudeAgentMapper.d.ts +21 -0
  87. package/dist/providers/claude/ClaudeAgentMapper.d.ts.map +1 -0
  88. package/dist/providers/claude/ClaudeAgentMapper.js +42 -0
  89. package/dist/providers/claude/ClaudeAgentMapper.js.map +1 -0
  90. package/dist/providers/claude/ClaudeCodeAdapter.d.ts +29 -0
  91. package/dist/providers/claude/ClaudeCodeAdapter.d.ts.map +1 -0
  92. package/dist/providers/claude/ClaudeCodeAdapter.js +129 -0
  93. package/dist/providers/claude/ClaudeCodeAdapter.js.map +1 -0
  94. package/dist/{adapters/ClaudeCodeAdapter.d.ts → providers/claude/ClaudeSessionLocator.d.ts} +68 -52
  95. package/dist/providers/claude/ClaudeSessionLocator.d.ts.map +1 -0
  96. package/dist/{adapters/ClaudeCodeAdapter.js → providers/claude/ClaudeSessionLocator.js} +71 -189
  97. package/dist/providers/claude/ClaudeSessionLocator.js.map +1 -0
  98. package/dist/{utils → providers/claude}/ClaudeSessionParser.d.ts +2 -2
  99. package/dist/providers/claude/ClaudeSessionParser.d.ts.map +1 -0
  100. package/dist/{utils → providers/claude}/ClaudeSessionParser.js +1 -1
  101. package/dist/providers/claude/ClaudeSessionParser.js.map +1 -0
  102. package/dist/providers/claude/durable/ClaudeCliProbe.d.ts.map +1 -0
  103. package/dist/{durable → providers/claude/durable}/ClaudeCliProbe.js +4 -9
  104. package/dist/providers/claude/durable/ClaudeCliProbe.js.map +1 -0
  105. package/dist/{durable → providers/claude/durable}/ClaudePrintAgentService.d.ts +4 -9
  106. package/dist/providers/claude/durable/ClaudePrintAgentService.d.ts.map +1 -0
  107. package/dist/providers/claude/durable/ClaudePrintAgentService.js +45 -0
  108. package/dist/providers/claude/durable/ClaudePrintAgentService.js.map +1 -0
  109. package/dist/{durable → providers/claude/durable}/ClaudePrintRunner.d.ts +3 -3
  110. package/dist/providers/claude/durable/ClaudePrintRunner.d.ts.map +1 -0
  111. package/dist/providers/claude/durable/ClaudePrintRunner.js +75 -0
  112. package/dist/providers/claude/durable/ClaudePrintRunner.js.map +1 -0
  113. package/dist/providers/claude/durable/ClaudeStreamParser.d.ts +17 -0
  114. package/dist/providers/claude/durable/ClaudeStreamParser.d.ts.map +1 -0
  115. package/dist/providers/claude/durable/ClaudeStreamParser.js +69 -0
  116. package/dist/providers/claude/durable/ClaudeStreamParser.js.map +1 -0
  117. package/dist/providers/codex/durable/CodexCliProbe.d.ts +20 -0
  118. package/dist/providers/codex/durable/CodexCliProbe.d.ts.map +1 -0
  119. package/dist/providers/codex/durable/CodexCliProbe.js +61 -0
  120. package/dist/providers/codex/durable/CodexCliProbe.js.map +1 -0
  121. package/dist/providers/codex/durable/CodexPrintAgentService.d.ts +40 -0
  122. package/dist/providers/codex/durable/CodexPrintAgentService.d.ts.map +1 -0
  123. package/dist/providers/codex/durable/CodexPrintAgentService.js +50 -0
  124. package/dist/providers/codex/durable/CodexPrintAgentService.js.map +1 -0
  125. package/dist/providers/codex/durable/CodexPrintRunner.d.ts +34 -0
  126. package/dist/providers/codex/durable/CodexPrintRunner.d.ts.map +1 -0
  127. package/dist/providers/codex/durable/CodexPrintRunner.js +78 -0
  128. package/dist/providers/codex/durable/CodexPrintRunner.js.map +1 -0
  129. package/dist/providers/codex/durable/CodexStreamParser.d.ts +21 -0
  130. package/dist/providers/codex/durable/CodexStreamParser.d.ts.map +1 -0
  131. package/dist/providers/codex/durable/CodexStreamParser.js +86 -0
  132. package/dist/providers/codex/durable/CodexStreamParser.js.map +1 -0
  133. package/dist/providers/pi/durable/PiCliProbe.d.ts +20 -0
  134. package/dist/providers/pi/durable/PiCliProbe.d.ts.map +1 -0
  135. package/dist/providers/pi/durable/PiCliProbe.js +51 -0
  136. package/dist/providers/pi/durable/PiCliProbe.js.map +1 -0
  137. package/dist/providers/pi/durable/PiPrintAgentService.d.ts +39 -0
  138. package/dist/providers/pi/durable/PiPrintAgentService.d.ts.map +1 -0
  139. package/dist/providers/pi/durable/PiPrintAgentService.js +47 -0
  140. package/dist/providers/pi/durable/PiPrintAgentService.js.map +1 -0
  141. package/dist/providers/pi/durable/PiPrintProtocol.d.ts +4 -0
  142. package/dist/providers/pi/durable/PiPrintProtocol.d.ts.map +1 -0
  143. package/dist/providers/pi/durable/PiPrintProtocol.js +44 -0
  144. package/dist/providers/pi/durable/PiPrintProtocol.js.map +1 -0
  145. package/dist/providers/pi/durable/PiPrintRunner.d.ts +32 -0
  146. package/dist/providers/pi/durable/PiPrintRunner.d.ts.map +1 -0
  147. package/dist/providers/pi/durable/PiPrintRunner.js +61 -0
  148. package/dist/providers/pi/durable/PiPrintRunner.js.map +1 -0
  149. package/dist/providers/pi/durable/PiStreamParser.d.ts +21 -0
  150. package/dist/providers/pi/durable/PiStreamParser.d.ts.map +1 -0
  151. package/dist/providers/pi/durable/PiStreamParser.js +71 -0
  152. package/dist/providers/pi/durable/PiStreamParser.js.map +1 -0
  153. package/package.json +1 -1
  154. package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +1 -307
  155. package/src/__tests__/capacity/codex.test.ts +184 -0
  156. package/src/__tests__/capacity/index.test.ts +38 -0
  157. package/src/__tests__/database/DurableAgentsDatabase.test.ts +28 -3
  158. package/src/__tests__/durable/CodexCliProbe.test.ts +63 -0
  159. package/src/__tests__/durable/CodexDurableAgentRepository.test.ts +54 -0
  160. package/src/__tests__/durable/CodexPrintAgent.integration.test.ts +79 -0
  161. package/src/__tests__/durable/CodexPrintAgentService.test.ts +139 -0
  162. package/src/__tests__/durable/CodexPrintRunner.test.ts +189 -0
  163. package/src/__tests__/fixtures/fake-codex.cjs +45 -0
  164. package/src/__tests__/print/ClaudeCliProbe.test.ts +3 -7
  165. package/src/__tests__/print/ClaudePrintAgent.integration.test.ts +19 -22
  166. package/src/__tests__/print/ClaudePrintAgentService.test.ts +33 -1
  167. package/src/__tests__/print/ClaudePrintRunner.test.ts +4 -10
  168. package/src/__tests__/providers/claude/ClaudeAgentMapper.test.ts +84 -0
  169. package/src/__tests__/providers/claude/ClaudeSessionLocator.test.ts +411 -0
  170. package/src/__tests__/providers/pi/durable/PiCliProbe.test.ts +38 -0
  171. package/src/__tests__/providers/pi/durable/PiDurableAgentRepository.test.ts +32 -0
  172. package/src/__tests__/providers/pi/durable/PiPrintAgentService.test.ts +69 -0
  173. package/src/__tests__/providers/pi/durable/PiPrintProtocol.test.ts +33 -0
  174. package/src/__tests__/providers/pi/durable/PiPrintRunner.test.ts +107 -0
  175. package/src/__tests__/providers/pi/durable/PiStreamParser.test.ts +24 -0
  176. package/src/__tests__/utils/ClaudeSessionParser.test.ts +2 -2
  177. package/src/adapters/index.ts +1 -1
  178. package/src/capacity/codex.ts +320 -0
  179. package/src/capacity/index.ts +57 -0
  180. package/src/capacity/types.ts +18 -0
  181. package/src/database/migrations/004_codex_durable_sessions.sql +1 -0
  182. package/src/durable/DurableAgent.ts +51 -3
  183. package/src/durable/DurableAgentRepository.ts +60 -31
  184. package/src/durable/process.ts +28 -0
  185. package/src/durable/run.ts +74 -0
  186. package/src/durable/utils.ts +61 -0
  187. package/src/index.ts +37 -15
  188. package/src/providers/claude/ClaudeAgentMapper.ts +62 -0
  189. package/src/providers/claude/ClaudeCodeAdapter.ts +160 -0
  190. package/src/{adapters/ClaudeCodeAdapter.ts → providers/claude/ClaudeSessionLocator.ts} +97 -239
  191. package/src/{utils → providers/claude}/ClaudeSessionParser.ts +2 -2
  192. package/src/{durable → providers/claude/durable}/ClaudeCliProbe.ts +4 -10
  193. package/src/providers/claude/durable/ClaudePrintAgentService.ts +66 -0
  194. package/src/providers/claude/durable/ClaudePrintRunner.ts +92 -0
  195. package/src/providers/claude/durable/ClaudeStreamParser.ts +79 -0
  196. package/src/providers/codex/durable/CodexCliProbe.ts +60 -0
  197. package/src/providers/codex/durable/CodexPrintAgentService.ts +65 -0
  198. package/src/providers/codex/durable/CodexPrintRunner.ts +94 -0
  199. package/src/providers/codex/durable/CodexStreamParser.ts +104 -0
  200. package/src/providers/pi/durable/PiCliProbe.ts +48 -0
  201. package/src/providers/pi/durable/PiPrintAgentService.ts +69 -0
  202. package/src/providers/pi/durable/PiPrintProtocol.ts +39 -0
  203. package/src/providers/pi/durable/PiPrintRunner.ts +88 -0
  204. package/src/providers/pi/durable/PiStreamParser.ts +84 -0
  205. package/dist/adapters/ClaudeCodeAdapter.d.ts.map +0 -1
  206. package/dist/adapters/ClaudeCodeAdapter.js.map +0 -1
  207. package/dist/durable/ClaudeCliProbe.d.ts.map +0 -1
  208. package/dist/durable/ClaudeCliProbe.js.map +0 -1
  209. package/dist/durable/ClaudePrintAgentService.d.ts.map +0 -1
  210. package/dist/durable/ClaudePrintAgentService.js +0 -66
  211. package/dist/durable/ClaudePrintAgentService.js.map +0 -1
  212. package/dist/durable/ClaudePrintRunner.d.ts.map +0 -1
  213. package/dist/durable/ClaudePrintRunner.js +0 -128
  214. package/dist/durable/ClaudePrintRunner.js.map +0 -1
  215. package/dist/utils/ClaudeSessionParser.d.ts.map +0 -1
  216. package/dist/utils/ClaudeSessionParser.js.map +0 -1
  217. package/src/durable/ClaudePrintAgentService.ts +0 -94
  218. package/src/durable/ClaudePrintRunner.ts +0 -139
  219. /package/dist/{durable → providers/claude/durable}/ClaudeCliProbe.d.ts +0 -0
@@ -2,7 +2,7 @@
2
2
  * Tests for ClaudeCodeAdapter
3
3
  */ import * as fs from 'fs';
4
4
  import * as path from 'path';
5
- import { ClaudeCodeAdapter } from '../../adapters/ClaudeCodeAdapter.js';
5
+ import { ClaudeCodeAdapter } from '../../providers/claude/ClaudeCodeAdapter.js';
6
6
  import { AgentStatus } from '../../adapters/AgentAdapter.js';
7
7
  import { listAgentProcesses, enrichProcesses, captureProcessSnapshot } from '../../utils/process.js';
8
8
  import { batchGetSessionFileBirthtimes } from '../../utils/session.js';
@@ -910,148 +910,6 @@ describe('ClaudeCodeAdapter', ()=>{
910
910
  });
911
911
  });
912
912
  });
913
- describe('discoverSessions', ()=>{
914
- let tmpDir;
915
- beforeEach(()=>{
916
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-test-'));
917
- adapter.projectsDir = path.join(tmpDir, 'projects');
918
- });
919
- afterEach(()=>{
920
- fs.rmSync(tmpDir, {
921
- recursive: true,
922
- force: true
923
- });
924
- });
925
- it('should return empty when projects dir does not exist', ()=>{
926
- adapter.projectsDir = path.join(tmpDir, 'nonexistent');
927
- const discoverSessions = adapter.discoverSessions.bind(adapter);
928
- const result = discoverSessions([
929
- {
930
- pid: 1,
931
- command: 'claude',
932
- cwd: '/test',
933
- tty: ''
934
- }
935
- ]);
936
- expect(result).toEqual([]);
937
- });
938
- it('should scan only directories matching process CWDs', ()=>{
939
- const projectsDir = path.join(tmpDir, 'projects');
940
- adapter.projectsDir = projectsDir;
941
- const discoverSessions = adapter.discoverSessions.bind(adapter);
942
- // /my/project → -my-project (encoded dir)
943
- const encodedDir = path.join(projectsDir, '-my-project');
944
- fs.mkdirSync(encodedDir, {
945
- recursive: true
946
- });
947
- // Also create another dir that should NOT be scanned
948
- const otherDir = path.join(projectsDir, '-other-project');
949
- fs.mkdirSync(otherDir, {
950
- recursive: true
951
- });
952
- const mockFiles = [
953
- {
954
- sessionId: 's1',
955
- filePath: path.join(encodedDir, 's1.jsonl'),
956
- projectDir: encodedDir,
957
- birthtimeMs: 1710800324000,
958
- resolvedCwd: ''
959
- }
960
- ];
961
- mockedBatchGetSessionFileBirthtimes.mockReturnValue(mockFiles);
962
- const processes = [
963
- {
964
- pid: 1,
965
- command: 'claude',
966
- cwd: '/my/project',
967
- tty: ''
968
- }
969
- ];
970
- const result = discoverSessions(processes);
971
- expect(result).toHaveLength(1);
972
- expect(result[0].resolvedCwd).toBe('/my/project');
973
- // batchGetSessionFileBirthtimes called once with all dirs
974
- expect(mockedBatchGetSessionFileBirthtimes).toHaveBeenCalledTimes(1);
975
- expect(mockedBatchGetSessionFileBirthtimes).toHaveBeenCalledWith([
976
- encodedDir
977
- ]);
978
- });
979
- it('should return empty when encoded dir does not exist', ()=>{
980
- const projectsDir = path.join(tmpDir, 'projects');
981
- fs.mkdirSync(projectsDir, {
982
- recursive: true
983
- });
984
- adapter.projectsDir = projectsDir;
985
- const discoverSessions = adapter.discoverSessions.bind(adapter);
986
- // Process CWD /test encodes to -test, but that dir doesn't exist
987
- const result = discoverSessions([
988
- {
989
- pid: 1,
990
- command: 'claude',
991
- cwd: '/test',
992
- tty: ''
993
- }
994
- ]);
995
- expect(result).toEqual([]);
996
- expect(mockedBatchGetSessionFileBirthtimes).not.toHaveBeenCalled();
997
- });
998
- it('should deduplicate when multiple processes share same CWD', ()=>{
999
- const projectsDir = path.join(tmpDir, 'projects');
1000
- adapter.projectsDir = projectsDir;
1001
- const discoverSessions = adapter.discoverSessions.bind(adapter);
1002
- const encodedDir = path.join(projectsDir, '-my-project');
1003
- fs.mkdirSync(encodedDir, {
1004
- recursive: true
1005
- });
1006
- mockedBatchGetSessionFileBirthtimes.mockReturnValue([
1007
- {
1008
- sessionId: 's1',
1009
- filePath: path.join(encodedDir, 's1.jsonl'),
1010
- projectDir: encodedDir,
1011
- birthtimeMs: 1710800324000,
1012
- resolvedCwd: ''
1013
- }
1014
- ]);
1015
- const processes = [
1016
- {
1017
- pid: 1,
1018
- command: 'claude',
1019
- cwd: '/my/project',
1020
- tty: ''
1021
- },
1022
- {
1023
- pid: 2,
1024
- command: 'claude',
1025
- cwd: '/my/project',
1026
- tty: ''
1027
- }
1028
- ];
1029
- const result = discoverSessions(processes);
1030
- // Should only call batch once with deduplicated dir
1031
- expect(mockedBatchGetSessionFileBirthtimes).toHaveBeenCalledTimes(1);
1032
- expect(mockedBatchGetSessionFileBirthtimes).toHaveBeenCalledWith([
1033
- encodedDir
1034
- ]);
1035
- expect(result).toHaveLength(1);
1036
- });
1037
- it('should skip processes with empty cwd', ()=>{
1038
- const projectsDir = path.join(tmpDir, 'projects');
1039
- fs.mkdirSync(projectsDir, {
1040
- recursive: true
1041
- });
1042
- adapter.projectsDir = projectsDir;
1043
- const discoverSessions = adapter.discoverSessions.bind(adapter);
1044
- const result = discoverSessions([
1045
- {
1046
- pid: 1,
1047
- command: 'claude',
1048
- cwd: '',
1049
- tty: ''
1050
- }
1051
- ]);
1052
- expect(result).toEqual([]);
1053
- });
1054
- });
1055
913
  describe('helper methods', ()=>{
1056
914
  describe('determineStatus', ()=>{
1057
915
  it('should return "unknown" for sessions with no last entry type', ()=>{
@@ -1234,205 +1092,6 @@ describe('ClaudeCodeAdapter', ()=>{
1234
1092
  force: true
1235
1093
  });
1236
1094
  });
1237
- describe('tryPidFileMatching', ()=>{
1238
- let sessionsDir;
1239
- let projectsDir;
1240
- beforeEach(()=>{
1241
- sessionsDir = path.join(tmpDir, 'sessions');
1242
- projectsDir = path.join(tmpDir, 'projects');
1243
- fs.mkdirSync(sessionsDir, {
1244
- recursive: true
1245
- });
1246
- adapter.sessionsDir = sessionsDir;
1247
- adapter.projectsDir = projectsDir;
1248
- });
1249
- const makeProc = (pid, cwd = '/project/test', startTime)=>({
1250
- pid,
1251
- command: 'claude',
1252
- cwd,
1253
- tty: 'ttys001',
1254
- startTime
1255
- });
1256
- const writePidFile = (pid, sessionId, cwd, startedAt)=>{
1257
- fs.writeFileSync(path.join(sessionsDir, `${pid}.json`), JSON.stringify({
1258
- pid,
1259
- sessionId,
1260
- cwd,
1261
- startedAt,
1262
- kind: 'interactive',
1263
- entrypoint: 'cli'
1264
- }));
1265
- };
1266
- const writeJsonl = (cwd, sessionId)=>{
1267
- const encoded = cwd.replace(/[^a-zA-Z0-9]/g, '-');
1268
- const projDir = path.join(projectsDir, encoded);
1269
- fs.mkdirSync(projDir, {
1270
- recursive: true
1271
- });
1272
- const filePath = path.join(projDir, `${sessionId}.jsonl`);
1273
- fs.writeFileSync(filePath, JSON.stringify({
1274
- type: 'assistant',
1275
- timestamp: new Date().toISOString()
1276
- }));
1277
- return filePath;
1278
- };
1279
- it('should return direct match when PID file and JSONL both exist within time tolerance', ()=>{
1280
- const startTime = new Date();
1281
- const proc = makeProc(1001, '/project/test', startTime);
1282
- writePidFile(1001, 'session-abc', '/project/test', startTime.getTime());
1283
- writeJsonl('/project/test', 'session-abc');
1284
- const tryMatch = adapter.tryPidFileMatching.bind(adapter);
1285
- const { direct, fallback } = tryMatch([
1286
- proc
1287
- ]);
1288
- expect(direct).toHaveLength(1);
1289
- expect(fallback).toHaveLength(0);
1290
- expect(direct[0].sessionFile.sessionId).toBe('session-abc');
1291
- expect(direct[0].sessionFile.resolvedCwd).toBe('/project/test');
1292
- expect(direct[0].process.pid).toBe(1001);
1293
- });
1294
- it('should fall back when PID file exists but JSONL is missing', ()=>{
1295
- const startTime = new Date();
1296
- const proc = makeProc(1002, '/project/test', startTime);
1297
- writePidFile(1002, 'nonexistent-session', '/project/test', startTime.getTime());
1298
- // No JSONL file written
1299
- const tryMatch = adapter.tryPidFileMatching.bind(adapter);
1300
- const { direct, fallback } = tryMatch([
1301
- proc
1302
- ]);
1303
- expect(direct).toHaveLength(0);
1304
- expect(fallback).toHaveLength(1);
1305
- expect(fallback[0].pid).toBe(1002);
1306
- });
1307
- it('should fall back when startedAt is stale (>60s from proc.startTime)', ()=>{
1308
- const startTime = new Date();
1309
- const staleTime = startTime.getTime() - 90_000; // 90 seconds earlier
1310
- const proc = makeProc(1003, '/project/test', startTime);
1311
- writePidFile(1003, 'stale-session', '/project/test', staleTime);
1312
- writeJsonl('/project/test', 'stale-session');
1313
- const tryMatch = adapter.tryPidFileMatching.bind(adapter);
1314
- const { direct, fallback } = tryMatch([
1315
- proc
1316
- ]);
1317
- expect(direct).toHaveLength(0);
1318
- expect(fallback).toHaveLength(1);
1319
- });
1320
- it('should accept PID file when startedAt is within 60s tolerance', ()=>{
1321
- const startTime = new Date();
1322
- const closeTime = startTime.getTime() - 30_000; // 30 seconds earlier — within tolerance
1323
- const proc = makeProc(1004, '/project/test', startTime);
1324
- writePidFile(1004, 'close-session', '/project/test', closeTime);
1325
- writeJsonl('/project/test', 'close-session');
1326
- const tryMatch = adapter.tryPidFileMatching.bind(adapter);
1327
- const { direct, fallback } = tryMatch([
1328
- proc
1329
- ]);
1330
- expect(direct).toHaveLength(1);
1331
- expect(fallback).toHaveLength(0);
1332
- });
1333
- it('should fall back when PID file is absent', ()=>{
1334
- const proc = makeProc(1005, '/project/test', new Date());
1335
- // No PID file written
1336
- const tryMatch = adapter.tryPidFileMatching.bind(adapter);
1337
- const { direct, fallback } = tryMatch([
1338
- proc
1339
- ]);
1340
- expect(direct).toHaveLength(0);
1341
- expect(fallback).toHaveLength(1);
1342
- });
1343
- it('should fall back when PID file contains malformed JSON', ()=>{
1344
- const proc = makeProc(1006, '/project/test', new Date());
1345
- fs.writeFileSync(path.join(sessionsDir, '1006.json'), 'not valid json {{{');
1346
- const tryMatch = adapter.tryPidFileMatching.bind(adapter);
1347
- expect(()=>{
1348
- const { direct, fallback } = tryMatch([
1349
- proc
1350
- ]);
1351
- expect(direct).toHaveLength(0);
1352
- expect(fallback).toHaveLength(1);
1353
- }).not.toThrow();
1354
- });
1355
- it('should fall back for all processes when sessions dir does not exist', ()=>{
1356
- adapter.sessionsDir = path.join(tmpDir, 'nonexistent-sessions');
1357
- const processes = [
1358
- makeProc(2001, '/a', new Date()),
1359
- makeProc(2002, '/b', new Date())
1360
- ];
1361
- const tryMatch = adapter.tryPidFileMatching.bind(adapter);
1362
- const { direct, fallback } = tryMatch(processes);
1363
- expect(direct).toHaveLength(0);
1364
- expect(fallback).toHaveLength(2);
1365
- });
1366
- it('should correctly split mixed processes (some with PID files, some without)', ()=>{
1367
- const startTime = new Date();
1368
- const proc1 = makeProc(3001, '/project/one', startTime);
1369
- const proc2 = makeProc(3002, '/project/two', startTime);
1370
- const proc3 = makeProc(3003, '/project/three', startTime);
1371
- writePidFile(3001, 'session-one', '/project/one', startTime.getTime());
1372
- writeJsonl('/project/one', 'session-one');
1373
- writePidFile(3003, 'session-three', '/project/three', startTime.getTime());
1374
- writeJsonl('/project/three', 'session-three');
1375
- // proc2 has no PID file
1376
- const tryMatch = adapter.tryPidFileMatching.bind(adapter);
1377
- const { direct, fallback } = tryMatch([
1378
- proc1,
1379
- proc2,
1380
- proc3
1381
- ]);
1382
- expect(direct).toHaveLength(2);
1383
- expect(fallback).toHaveLength(1);
1384
- expect(direct.map((d)=>d.process.pid).sort()).toEqual([
1385
- 3001,
1386
- 3003
1387
- ]);
1388
- expect(fallback[0].pid).toBe(3002);
1389
- });
1390
- it('should skip stale-file check when proc.startTime is undefined', ()=>{
1391
- const proc = makeProc(4001, '/project/test', undefined); // no startTime
1392
- writePidFile(4001, 'no-time-session', '/project/test', Date.now() - 999_999);
1393
- writeJsonl('/project/test', 'no-time-session');
1394
- const tryMatch = adapter.tryPidFileMatching.bind(adapter);
1395
- const { direct, fallback } = tryMatch([
1396
- proc
1397
- ]);
1398
- // startTime undefined → stale check skipped → direct match
1399
- expect(direct).toHaveLength(1);
1400
- expect(fallback).toHaveLength(0);
1401
- });
1402
- });
1403
- describe('getProjectDir', ()=>{
1404
- const encode = (cwd)=>adapter.getProjectDir(cwd);
1405
- it('should replace path separators with hyphens', ()=>{
1406
- const expected = path.join(adapter.projectsDir, '-Users-foo-bar');
1407
- expect(encode('/Users/foo/bar')).toBe(expected);
1408
- });
1409
- it('should encode underscores as hyphens (matches Claude Code CLI)', ()=>{
1410
- const expected = path.join(adapter.projectsDir, '-Users-foo-my-project');
1411
- expect(encode('/Users/foo/my_project')).toBe(expected);
1412
- });
1413
- it('should encode dots as hyphens', ()=>{
1414
- const expected = path.join(adapter.projectsDir, '-Users-foo--worktrees-x');
1415
- expect(encode('/Users/foo/.worktrees/x')).toBe(expected);
1416
- });
1417
- it('should collide paths that differ only in non-alphanumeric chars', ()=>{
1418
- // The encoding is intentionally lossy — callers must
1419
- // disambiguate via session JSONL contents, not dir name.
1420
- expect(encode('/a/b_c')).toBe(encode('/a/b-c'));
1421
- expect(encode('/a/b_c')).toBe(encode('/a/b.c'));
1422
- });
1423
- it('should resolve to a real session dir when cwd contains underscores', ()=>{
1424
- const cwd = '/Users/foo/my_project';
1425
- const projectsDir = adapter.projectsDir;
1426
- const expectedDir = path.join(projectsDir, '-Users-foo-my-project');
1427
- fs.mkdirSync(expectedDir, {
1428
- recursive: true
1429
- });
1430
- const sessionFile = path.join(expectedDir, 'session-underscore.jsonl');
1431
- fs.writeFileSync(sessionFile, '');
1432
- expect(encode(cwd)).toBe(expectedDir);
1433
- expect(fs.existsSync(path.join(encode(cwd), 'session-underscore.jsonl'))).toBe(true);
1434
- });
1435
- });
1436
1095
  describe('readSession', ()=>{
1437
1096
  it('should parse session file with timestamps, cwd, and entry type', ()=>{
1438
1097
  const readSession = adapter.parser.readSession.bind(adapter.parser);