@ai-devkit/agent-manager 0.33.1 → 0.35.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 (191) hide show
  1. package/dist/AgentManager.d.ts +2 -0
  2. package/dist/AgentManager.d.ts.map +1 -1
  3. package/dist/AgentManager.js +25 -0
  4. package/dist/AgentManager.js.map +1 -1
  5. package/dist/__tests__/AgentManager.test.js +74 -0
  6. package/dist/__tests__/AgentManager.test.js.map +1 -1
  7. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +28 -0
  8. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -1
  9. package/dist/__tests__/adapters/CodexAdapter.test.js +7 -0
  10. package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -1
  11. package/dist/__tests__/capacity/codex.test.js +2 -1
  12. package/dist/__tests__/capacity/codex.test.js.map +1 -1
  13. package/dist/__tests__/capacity/index.test.js +6 -3
  14. package/dist/__tests__/capacity/index.test.js.map +1 -1
  15. package/dist/__tests__/capacity/zai.test.js +213 -0
  16. package/dist/__tests__/capacity/zai.test.js.map +1 -0
  17. package/dist/__tests__/providers/copilot/CopilotAdapter.test.js +18 -0
  18. package/dist/__tests__/providers/copilot/CopilotAdapter.test.js.map +1 -1
  19. package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js +36 -0
  20. package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js.map +1 -1
  21. package/dist/__tests__/providers/grok/GrokAgentMapper.test.js +56 -0
  22. package/dist/__tests__/providers/grok/GrokAgentMapper.test.js.map +1 -0
  23. package/dist/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.js +19 -6
  24. package/dist/__tests__/providers/grok/GrokCliAdapter.test.js.map +1 -0
  25. package/dist/__tests__/providers/grok/GrokSessionLocator.test.js +133 -0
  26. package/dist/__tests__/providers/grok/GrokSessionLocator.test.js.map +1 -0
  27. package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js +213 -0
  28. package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js.map +1 -0
  29. package/dist/__tests__/providers/opencode/OpenCodeAgentMapper.test.js +89 -0
  30. package/dist/__tests__/providers/opencode/OpenCodeAgentMapper.test.js.map +1 -0
  31. package/dist/__tests__/providers/opencode/OpenCodeSessionLocator.test.js +94 -0
  32. package/dist/__tests__/providers/opencode/OpenCodeSessionLocator.test.js.map +1 -0
  33. package/dist/__tests__/providers/opencode/OpenCodeSessionParser.test.js +147 -0
  34. package/dist/__tests__/providers/opencode/OpenCodeSessionParser.test.js.map +1 -0
  35. package/dist/__tests__/providers/pi/PiAdapter.test.js +30 -0
  36. package/dist/__tests__/providers/pi/PiAdapter.test.js.map +1 -1
  37. package/dist/__tests__/utils/session.test.js +18 -1
  38. package/dist/__tests__/utils/session.test.js.map +1 -1
  39. package/dist/adapters/AgentAdapter.d.ts +8 -0
  40. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  41. package/dist/adapters/AgentAdapter.js.map +1 -1
  42. package/dist/adapters/index.d.ts +2 -2
  43. package/dist/adapters/index.d.ts.map +1 -1
  44. package/dist/adapters/index.js +2 -2
  45. package/dist/adapters/index.js.map +1 -1
  46. package/dist/capacity/codex.d.ts +1 -0
  47. package/dist/capacity/codex.d.ts.map +1 -1
  48. package/dist/capacity/codex.js +9 -4
  49. package/dist/capacity/codex.js.map +1 -1
  50. package/dist/capacity/index.d.ts +8 -0
  51. package/dist/capacity/index.d.ts.map +1 -1
  52. package/dist/capacity/index.js +10 -9
  53. package/dist/capacity/index.js.map +1 -1
  54. package/dist/capacity/types.d.ts +8 -0
  55. package/dist/capacity/types.d.ts.map +1 -1
  56. package/dist/capacity/types.js.map +1 -1
  57. package/dist/capacity/zai.d.ts +14 -0
  58. package/dist/capacity/zai.d.ts.map +1 -0
  59. package/dist/capacity/zai.js +203 -0
  60. package/dist/capacity/zai.js.map +1 -0
  61. package/dist/index.d.ts +4 -4
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +3 -3
  64. package/dist/index.js.map +1 -1
  65. package/dist/providers/claude/ClaudeCodeAdapter.d.ts +2 -0
  66. package/dist/providers/claude/ClaudeCodeAdapter.d.ts.map +1 -1
  67. package/dist/providers/claude/ClaudeCodeAdapter.js +22 -19
  68. package/dist/providers/claude/ClaudeCodeAdapter.js.map +1 -1
  69. package/dist/providers/claude/ClaudeSessionLocator.d.ts +4 -0
  70. package/dist/providers/claude/ClaudeSessionLocator.d.ts.map +1 -1
  71. package/dist/providers/claude/ClaudeSessionLocator.js +18 -1
  72. package/dist/providers/claude/ClaudeSessionLocator.js.map +1 -1
  73. package/dist/providers/codex/CodexAdapter.d.ts +1 -0
  74. package/dist/providers/codex/CodexAdapter.d.ts.map +1 -1
  75. package/dist/providers/codex/CodexAdapter.js +8 -0
  76. package/dist/providers/codex/CodexAdapter.js.map +1 -1
  77. package/dist/providers/copilot/CopilotAdapter.d.ts +2 -0
  78. package/dist/providers/copilot/CopilotAdapter.d.ts.map +1 -1
  79. package/dist/providers/copilot/CopilotAdapter.js +24 -9
  80. package/dist/providers/copilot/CopilotAdapter.js.map +1 -1
  81. package/dist/providers/copilot/CopilotSessionLocator.d.ts +1 -0
  82. package/dist/providers/copilot/CopilotSessionLocator.d.ts.map +1 -1
  83. package/dist/providers/copilot/CopilotSessionLocator.js +9 -1
  84. package/dist/providers/copilot/CopilotSessionLocator.js.map +1 -1
  85. package/dist/providers/gemini/GeminiCliAdapter.d.ts +1 -0
  86. package/dist/providers/gemini/GeminiCliAdapter.d.ts.map +1 -1
  87. package/dist/providers/gemini/GeminiCliAdapter.js +9 -0
  88. package/dist/providers/gemini/GeminiCliAdapter.js.map +1 -1
  89. package/dist/providers/grok/GrokAgentMapper.d.ts +14 -0
  90. package/dist/providers/grok/GrokAgentMapper.d.ts.map +1 -0
  91. package/dist/providers/grok/GrokAgentMapper.js +36 -0
  92. package/dist/providers/grok/GrokAgentMapper.js.map +1 -0
  93. package/dist/providers/grok/GrokCliAdapter.d.ts +36 -0
  94. package/dist/providers/grok/GrokCliAdapter.d.ts.map +1 -0
  95. package/dist/providers/grok/GrokCliAdapter.js +98 -0
  96. package/dist/providers/grok/GrokCliAdapter.js.map +1 -0
  97. package/dist/providers/grok/GrokSessionLocator.d.ts +62 -0
  98. package/dist/providers/grok/GrokSessionLocator.d.ts.map +1 -0
  99. package/dist/providers/grok/GrokSessionLocator.js +140 -0
  100. package/dist/providers/grok/GrokSessionLocator.js.map +1 -0
  101. package/dist/providers/grok/GrokSessionParser.d.ts +54 -0
  102. package/dist/providers/grok/GrokSessionParser.d.ts.map +1 -0
  103. package/dist/providers/grok/GrokSessionParser.js +122 -0
  104. package/dist/providers/grok/GrokSessionParser.js.map +1 -0
  105. package/dist/{adapters → providers/opencode}/OpenCodeAdapter.d.ts +6 -12
  106. package/dist/providers/opencode/OpenCodeAdapter.d.ts.map +1 -0
  107. package/dist/providers/opencode/OpenCodeAdapter.js +89 -0
  108. package/dist/providers/opencode/OpenCodeAdapter.js.map +1 -0
  109. package/dist/providers/opencode/OpenCodeAgentMapper.d.ts +11 -0
  110. package/dist/providers/opencode/OpenCodeAgentMapper.d.ts.map +1 -0
  111. package/dist/providers/opencode/OpenCodeAgentMapper.js +50 -0
  112. package/dist/providers/opencode/OpenCodeAgentMapper.js.map +1 -0
  113. package/dist/providers/opencode/OpenCodeSessionLocator.d.ts +23 -0
  114. package/dist/providers/opencode/OpenCodeSessionLocator.d.ts.map +1 -0
  115. package/dist/providers/opencode/OpenCodeSessionLocator.js +118 -0
  116. package/dist/providers/opencode/OpenCodeSessionLocator.js.map +1 -0
  117. package/dist/providers/opencode/OpenCodeSessionParser.d.ts +18 -0
  118. package/dist/providers/opencode/OpenCodeSessionParser.d.ts.map +1 -0
  119. package/dist/providers/opencode/OpenCodeSessionParser.js +104 -0
  120. package/dist/providers/opencode/OpenCodeSessionParser.js.map +1 -0
  121. package/dist/providers/pi/PiAdapter.d.ts +1 -0
  122. package/dist/providers/pi/PiAdapter.d.ts.map +1 -1
  123. package/dist/providers/pi/PiAdapter.js +10 -0
  124. package/dist/providers/pi/PiAdapter.js.map +1 -1
  125. package/dist/providers/pi/PiSessionLocator.d.ts +1 -0
  126. package/dist/providers/pi/PiSessionLocator.d.ts.map +1 -1
  127. package/dist/providers/pi/PiSessionLocator.js +8 -1
  128. package/dist/providers/pi/PiSessionLocator.js.map +1 -1
  129. package/dist/utils/session.d.ts +2 -0
  130. package/dist/utils/session.d.ts.map +1 -1
  131. package/dist/utils/session.js +3 -0
  132. package/dist/utils/session.js.map +1 -1
  133. package/package.json +1 -1
  134. package/src/AgentManager.ts +34 -0
  135. package/src/__tests__/AgentManager.test.ts +60 -0
  136. package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +23 -0
  137. package/src/__tests__/adapters/CodexAdapter.test.ts +7 -0
  138. package/src/__tests__/capacity/codex.test.ts +94 -26
  139. package/src/__tests__/capacity/fixtures/zai-auth.json +6 -0
  140. package/src/__tests__/capacity/fixtures/zai-quota-invalid.json +7 -0
  141. package/src/__tests__/capacity/fixtures/zai-quota.json +38 -0
  142. package/src/__tests__/capacity/index.test.ts +10 -3
  143. package/src/__tests__/capacity/zai.test.ts +186 -0
  144. package/src/__tests__/providers/copilot/CopilotAdapter.test.ts +12 -0
  145. package/src/__tests__/providers/gemini/GeminiCliAdapter.test.ts +21 -0
  146. package/src/__tests__/providers/grok/GrokAgentMapper.test.ts +57 -0
  147. package/src/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.ts +18 -9
  148. package/src/__tests__/providers/grok/GrokSessionLocator.test.ts +106 -0
  149. package/src/__tests__/providers/opencode/OpenCodeAdapter.test.ts +215 -0
  150. package/src/__tests__/providers/opencode/OpenCodeAgentMapper.test.ts +83 -0
  151. package/src/__tests__/providers/opencode/OpenCodeSessionLocator.test.ts +84 -0
  152. package/src/__tests__/providers/opencode/OpenCodeSessionParser.test.ts +148 -0
  153. package/src/__tests__/providers/pi/PiAdapter.test.ts +15 -0
  154. package/src/__tests__/utils/session.test.ts +11 -1
  155. package/src/adapters/AgentAdapter.ts +9 -0
  156. package/src/adapters/index.ts +2 -2
  157. package/src/capacity/codex.ts +96 -28
  158. package/src/capacity/index.ts +29 -11
  159. package/src/capacity/types.ts +8 -0
  160. package/src/capacity/zai.ts +237 -0
  161. package/src/index.ts +9 -4
  162. package/src/providers/claude/ClaudeCodeAdapter.ts +28 -23
  163. package/src/providers/claude/ClaudeSessionLocator.ts +20 -0
  164. package/src/providers/codex/CodexAdapter.ts +8 -0
  165. package/src/providers/copilot/CopilotAdapter.ts +26 -10
  166. package/src/providers/copilot/CopilotSessionLocator.ts +7 -1
  167. package/src/providers/gemini/GeminiCliAdapter.ts +8 -0
  168. package/src/providers/grok/GrokAgentMapper.ts +41 -0
  169. package/src/providers/grok/GrokCliAdapter.ts +125 -0
  170. package/src/providers/grok/GrokSessionLocator.ts +179 -0
  171. package/src/providers/grok/GrokSessionParser.ts +166 -0
  172. package/src/providers/opencode/OpenCodeAdapter.ts +116 -0
  173. package/src/providers/opencode/OpenCodeAgentMapper.ts +61 -0
  174. package/src/providers/opencode/OpenCodeSessionLocator.ts +151 -0
  175. package/src/providers/opencode/OpenCodeSessionParser.ts +145 -0
  176. package/src/providers/pi/PiAdapter.ts +12 -0
  177. package/src/providers/pi/PiSessionLocator.ts +16 -1
  178. package/src/utils/session.ts +11 -0
  179. package/dist/__tests__/adapters/GrokCliAdapter.test.js.map +0 -1
  180. package/dist/__tests__/adapters/OpenCodeAdapter.test.js +0 -636
  181. package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +0 -1
  182. package/dist/adapters/GrokCliAdapter.d.ts +0 -80
  183. package/dist/adapters/GrokCliAdapter.d.ts.map +0 -1
  184. package/dist/adapters/GrokCliAdapter.js +0 -310
  185. package/dist/adapters/GrokCliAdapter.js.map +0 -1
  186. package/dist/adapters/OpenCodeAdapter.d.ts.map +0 -1
  187. package/dist/adapters/OpenCodeAdapter.js +0 -289
  188. package/dist/adapters/OpenCodeAdapter.js.map +0 -1
  189. package/src/__tests__/adapters/OpenCodeAdapter.test.ts +0 -544
  190. package/src/adapters/GrokCliAdapter.ts +0 -408
  191. package/src/adapters/OpenCodeAdapter.ts +0 -373
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/__tests__/adapters/CodexAdapter.test.ts"],"sourcesContent":["import type { MockedFunction } from \"vitest\";\nimport * as fs from \"fs\";\nimport * as os from \"os\";\nimport * as path from \"path\";\n\nimport { CodexAdapter } from \"../../providers/codex/CodexAdapter.js\";\nimport { AgentStatus, type ProcessInfo } from \"../../adapters/AgentAdapter.js\";\nimport { AgentRegistry, type RegistryEntry } from \"../../utils/AgentRegistry.js\";\nimport { batchGetSessionFileBirthtimes, type SessionFile } from \"../../utils/session.js\";\nimport {\n captureProcessSnapshot,\n enrichProcesses,\n listAgentProcesses,\n} from \"../../utils/process.js\";\nimport {\n generateAgentName,\n matchProcessesToSessions,\n type MatchResult,\n} from \"../../utils/matching.js\";\n\nvi.mock(\"../../utils/process.js\", async (importOriginal) => {\n const actual = (await importOriginal()) as typeof import(\"../../utils/process.js\");\n return {\n ...actual,\n listAgentProcesses: vi.fn(),\n enrichProcesses: vi.fn(),\n captureProcessSnapshot: vi.fn(),\n };\n});\n\nvi.mock(\"../../utils/session.js\", async () => {\n const actual = (await vi.importActual(\n \"../../utils/session\",\n )) as typeof import(\"../../utils/session\");\n return {\n ...actual,\n batchGetSessionFileBirthtimes: vi.fn(),\n };\n});\n\nvi.mock(\"../../utils/matching.js\", () => ({\n matchProcessesToSessions: vi.fn(),\n generateAgentName: vi.fn(),\n}));\n\nconst mockedListAgentProcesses = listAgentProcesses as MockedFunction<typeof listAgentProcesses>;\nconst mockedEnrichProcesses = enrichProcesses as MockedFunction<typeof enrichProcesses>;\nconst mockedCaptureProcessSnapshot = captureProcessSnapshot as MockedFunction<\n typeof captureProcessSnapshot\n>;\nconst mockedBatchGetSessionFileBirthtimes = batchGetSessionFileBirthtimes as MockedFunction<\n typeof batchGetSessionFileBirthtimes\n>;\nconst mockedMatchProcessesToSessions = matchProcessesToSessions as MockedFunction<\n typeof matchProcessesToSessions\n>;\nconst mockedGenerateAgentName = generateAgentName as MockedFunction<typeof generateAgentName>;\n\ndescribe(\"CodexAdapter\", () => {\n let originalHome: string | undefined;\n let tmpHome: string;\n let adapter: CodexAdapter;\n\n beforeEach(() => {\n originalHome = process.env.HOME;\n tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), \"codex-adapter-\"));\n process.env.HOME = tmpHome;\n\n adapter = new CodexAdapter(new AgentRegistry(path.join(tmpHome, \"agents.json\")));\n mockedListAgentProcesses.mockReset();\n mockedEnrichProcesses.mockReset();\n mockedCaptureProcessSnapshot.mockReset();\n mockedBatchGetSessionFileBirthtimes.mockReset();\n mockedMatchProcessesToSessions.mockReset();\n mockedGenerateAgentName.mockReset();\n\n mockedEnrichProcesses.mockImplementation((processes) => processes);\n mockedCaptureProcessSnapshot.mockImplementation(async (names) =>\n enrichProcesses(names.flatMap((name) => listAgentProcesses(name))),\n );\n mockedGenerateAgentName.mockImplementation(\n (cwd, pid) => `${path.basename(cwd) || \"unknown\"} (${pid})`,\n );\n });\n\n afterEach(() => {\n process.env.HOME = originalHome;\n fs.rmSync(tmpHome, { recursive: true, force: true });\n });\n\n it(\"exposes codex type and handles Codex executable names only\", () => {\n expect(adapter.type).toBe(\"codex\");\n expect(adapter.canHandle({ pid: 1, command: \"codex\", cwd: \"/repo\", tty: \"ttys001\" })).toBe(\n true,\n );\n expect(\n adapter.canHandle({\n pid: 2,\n command: \"/usr/local/bin/CODEX --sandbox workspace-write\",\n cwd: \"/repo\",\n tty: \"ttys002\",\n }),\n ).toBe(true);\n expect(\n adapter.canHandle({ pid: 3, command: \"node app.js\", cwd: \"/repo\", tty: \"ttys003\" }),\n ).toBe(false);\n expect(\n adapter.canHandle({\n pid: 4,\n command: \"node /worktrees/feature-codex-adapter-agent-manager-package/server.js\",\n cwd: \"/repo\",\n tty: \"ttys004\",\n }),\n ).toBe(false);\n });\n\n it(\"returns no agents when no Codex process is running\", async () => {\n mockedListAgentProcesses.mockReturnValue([]);\n\n await expect(adapter.detectAgents()).resolves.toEqual([]);\n expect(mockedListAgentProcesses).toHaveBeenCalledWith(\"codex\");\n });\n\n it(\"returns process-only agents when no session files are discovered\", async () => {\n const processes: ProcessInfo[] = [\n { pid: 100, command: \"codex\", cwd: \"/repo-a\", tty: \"ttys001\" },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n\n await expect(adapter.detectAgents()).resolves.toMatchObject([\n {\n type: \"codex\",\n status: AgentStatus.RUNNING,\n pid: 100,\n projectPath: \"/repo-a\",\n sessionId: \"pid-100\",\n summary: \"Codex process running\",\n },\n ]);\n });\n\n it(\"maps a running Codex process through legacy session matching\", async () => {\n const processInfo: ProcessInfo = {\n pid: 100,\n command: \"codex\",\n cwd: \"/repo-a\",\n tty: \"ttys001\",\n startTime: new Date(\"2026-03-18T15:00:00.000Z\"),\n };\n const { sessionFile, dateDir } = writeSession(\"sess-abc\", \"/repo-a\", [\n {\n type: \"event\",\n timestamp: new Date().toISOString(),\n payload: { type: \"token_count\", message: \"Implement adapter flow\" },\n },\n ]);\n const candidate: SessionFile = {\n sessionId: \"sess-abc\",\n filePath: sessionFile,\n projectDir: dateDir,\n birthtimeMs: new Date(\"2026-03-18T15:00:05Z\").getTime(),\n resolvedCwd: \"\",\n };\n const matches: MatchResult[] = [\n { process: processInfo, session: { ...candidate, resolvedCwd: \"/repo-a\" }, deltaMs: 5000 },\n ];\n\n mockedListAgentProcesses.mockReturnValue([processInfo]);\n mockedBatchGetSessionFileBirthtimes.mockReturnValue([candidate]);\n mockedMatchProcessesToSessions.mockReturnValue(matches);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: \"codex\",\n status: AgentStatus.RUNNING,\n pid: 100,\n projectPath: \"/repo-a\",\n sessionId: \"sess-abc\",\n summary: \"Implement adapter flow\",\n });\n });\n\n it(\"maps resumed sessions directly by id\", async () => {\n const sessionId = \"019eabed-4079-7071-9531-b853ddd9914e\";\n const processInfo: ProcessInfo = {\n pid: 88018,\n command: `codex resume ${sessionId}`,\n cwd: \"/repo-a\",\n tty: \"ttys001\",\n startTime: new Date(\"2026-06-10T12:00:00.000Z\"),\n };\n const { sessionFile } = writeSession(\n sessionId,\n \"/repo-a\",\n [\n {\n type: \"event\",\n timestamp: new Date().toISOString(),\n payload: { type: \"agent_message\", message: \"resumed codex conversation\" },\n },\n ],\n \"2026/06/09\",\n );\n\n mockedListAgentProcesses.mockReturnValue([processInfo]);\n mockedBatchGetSessionFileBirthtimes.mockReturnValue([]);\n\n const agents = await adapter.detectAgents();\n\n expect(mockedBatchGetSessionFileBirthtimes).not.toHaveBeenCalled();\n expect(mockedMatchProcessesToSessions).not.toHaveBeenCalled();\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: \"codex\",\n pid: 88018,\n sessionId,\n projectPath: \"/repo-a\",\n sessionFilePath: sessionFile,\n summary: \"resumed codex conversation\",\n });\n });\n\n it(\"uses hook session mapping before a stale registry entry for the same pid\", async () => {\n const registry = new AgentRegistry(path.join(tmpHome, \"agents.json\"));\n const staleSession = path.join(tmpHome, \"stale.jsonl\");\n fs.writeFileSync(\n staleSession,\n [\n JSON.stringify({\n type: \"session_meta\",\n payload: { id: \"stale\", timestamp: new Date().toISOString(), cwd: \"/repo-stale\" },\n }),\n JSON.stringify({\n type: \"event\",\n timestamp: new Date().toISOString(),\n payload: { type: \"agent_message\", message: \"stale\" },\n }),\n ].join(\"\\n\"),\n );\n registry.register(registryEntry({ sessionFilePath: staleSession }));\n\n const mappedAdapter = new CodexAdapter(registry);\n const { sessionFile } = writeSession(\n \"current\",\n \"/repo-current\",\n [\n {\n type: \"event\",\n timestamp: new Date().toISOString(),\n payload: { type: \"agent_message\", message: \"Hello from current mapping\" },\n },\n ],\n \"2026/06/26\",\n );\n fs.mkdirSync(path.join(tmpHome, \".codex\", \"ai-devkit\"), { recursive: true });\n fs.writeFileSync(\n path.join(tmpHome, \".codex\", \"ai-devkit\", \"sessions.json\"),\n JSON.stringify({ 100: sessionFile }),\n );\n\n mockedListAgentProcesses.mockReturnValue([\n { pid: 100, command: \"codex\", cwd: \"/repo-current\", tty: \"ttys001\", startTime: new Date() },\n ]);\n\n const agents = await mappedAdapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 100,\n sessionId: \"current\",\n sessionFilePath: sessionFile,\n summary: \"Hello from current mapping\",\n });\n expect(mockedBatchGetSessionFileBirthtimes).not.toHaveBeenCalled();\n expect(mockedMatchProcessesToSessions).not.toHaveBeenCalled();\n });\n\n it(\"uses a valid registry cache entry before scanning sessions\", async () => {\n const registry = new AgentRegistry(path.join(tmpHome, \"agents.json\"));\n const { sessionFile } = writeSession(\"cached\", \"/repo-a\", [\n {\n type: \"event\",\n timestamp: new Date().toISOString(),\n payload: { type: \"token_count\", message: \"Hello from cache\" },\n },\n ]);\n registry.register(registryEntry({ sessionId: \"cached\", sessionFilePath: sessionFile }));\n\n const cachedAdapter = new CodexAdapter(registry);\n mockedListAgentProcesses.mockReturnValue([\n { pid: 100, command: \"codex\", cwd: \"/repo-a\", tty: \"ttys001\", startTime: new Date() },\n ]);\n\n const agents = await cachedAdapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: \"codex\",\n pid: 100,\n sessionId: \"cached\",\n sessionFilePath: sessionFile,\n summary: \"Hello from cache\",\n });\n expect(mockedMatchProcessesToSessions).not.toHaveBeenCalled();\n expect(mockedBatchGetSessionFileBirthtimes).not.toHaveBeenCalled();\n });\n\n it(\"delegates conversation parsing and historical session listing\", async () => {\n const { sessionFile } = writeSession(\n \"listed\",\n \"/repo\",\n [\n {\n type: \"event\",\n timestamp: \"2025-01-01T00:00:01Z\",\n payload: { type: \"user_message\", message: \"first user\" },\n },\n {\n type: \"event\",\n timestamp: \"2025-01-01T00:00:02Z\",\n payload: { type: \"agent_message\", message: \"answer\" },\n },\n ],\n \"2025/01/01\",\n );\n\n expect(adapter.getConversation(sessionFile)).toEqual([\n { role: \"user\", content: \"first user\", timestamp: \"2025-01-01T00:00:01Z\" },\n { role: \"assistant\", content: \"answer\", timestamp: \"2025-01-01T00:00:02Z\" },\n ]);\n await expect(adapter.listSessions({ cwd: \"/repo\" })).resolves.toMatchObject([\n {\n type: \"codex\",\n sessionId: \"listed\",\n cwd: \"/repo\",\n firstUserMessage: \"first user\",\n sessionFilePath: sessionFile,\n },\n ]);\n });\n\n function writeSession(\n sessionId: string,\n cwd: string,\n entries: object[],\n day = \"2026/03/18\",\n ): { sessionFile: string; dateDir: string } {\n const dateDir = path.join(tmpHome, \".codex\", \"sessions\", ...day.split(\"/\"));\n fs.mkdirSync(dateDir, { recursive: true });\n const sessionFile = path.join(dateDir, `${sessionId}.jsonl`);\n fs.writeFileSync(\n sessionFile,\n [\n JSON.stringify({\n type: \"session_meta\",\n payload: { id: sessionId, timestamp: \"2026-03-18T15:00:00Z\", cwd },\n }),\n ...entries.map((entry) => JSON.stringify(entry)),\n ].join(\"\\n\"),\n );\n return { sessionFile, dateDir };\n }\n\n function registryEntry(overrides: Partial<RegistryEntry> = {}): RegistryEntry {\n return {\n name: \"codex-100\",\n type: \"codex\",\n pid: 100,\n runtime: \"tmux\",\n runtimeRef: null,\n cwd: \"/repo-a\",\n startedAt: \"2026-05-30T00:00:00.000Z\",\n sessionId: \"cached\",\n sessionFilePath: \"\",\n ...overrides,\n };\n }\n});\n"],"names":["fs","os","path","CodexAdapter","AgentStatus","AgentRegistry","batchGetSessionFileBirthtimes","captureProcessSnapshot","enrichProcesses","listAgentProcesses","generateAgentName","matchProcessesToSessions","vi","mock","importOriginal","actual","fn","importActual","mockedListAgentProcesses","mockedEnrichProcesses","mockedCaptureProcessSnapshot","mockedBatchGetSessionFileBirthtimes","mockedMatchProcessesToSessions","mockedGenerateAgentName","describe","originalHome","tmpHome","adapter","beforeEach","process","env","HOME","mkdtempSync","join","tmpdir","mockReset","mockImplementation","processes","names","flatMap","name","cwd","pid","basename","afterEach","rmSync","recursive","force","it","expect","type","toBe","canHandle","command","tty","mockReturnValue","detectAgents","resolves","toEqual","toHaveBeenCalledWith","toMatchObject","status","RUNNING","projectPath","sessionId","summary","processInfo","startTime","Date","sessionFile","dateDir","writeSession","timestamp","toISOString","payload","message","candidate","filePath","projectDir","birthtimeMs","getTime","resolvedCwd","matches","session","deltaMs","agents","toHaveLength","not","toHaveBeenCalled","sessionFilePath","registry","staleSession","writeFileSync","JSON","stringify","id","register","registryEntry","mappedAdapter","mkdirSync","cachedAdapter","getConversation","role","content","listSessions","firstUserMessage","entries","day","split","map","entry","overrides","runtime","runtimeRef","startedAt"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/__tests__/adapters/CodexAdapter.test.ts"],"sourcesContent":["import type { MockedFunction } from \"vitest\";\nimport * as fs from \"fs\";\nimport * as os from \"os\";\nimport * as path from \"path\";\n\nimport { CodexAdapter } from \"../../providers/codex/CodexAdapter.js\";\nimport { AgentStatus, type ProcessInfo } from \"../../adapters/AgentAdapter.js\";\nimport { AgentRegistry, type RegistryEntry } from \"../../utils/AgentRegistry.js\";\nimport { batchGetSessionFileBirthtimes, type SessionFile } from \"../../utils/session.js\";\nimport {\n captureProcessSnapshot,\n enrichProcesses,\n listAgentProcesses,\n} from \"../../utils/process.js\";\nimport {\n generateAgentName,\n matchProcessesToSessions,\n type MatchResult,\n} from \"../../utils/matching.js\";\n\nvi.mock(\"../../utils/process.js\", async (importOriginal) => {\n const actual = (await importOriginal()) as typeof import(\"../../utils/process.js\");\n return {\n ...actual,\n listAgentProcesses: vi.fn(),\n enrichProcesses: vi.fn(),\n captureProcessSnapshot: vi.fn(),\n };\n});\n\nvi.mock(\"../../utils/session.js\", async () => {\n const actual = (await vi.importActual(\n \"../../utils/session\",\n )) as typeof import(\"../../utils/session\");\n return {\n ...actual,\n batchGetSessionFileBirthtimes: vi.fn(),\n };\n});\n\nvi.mock(\"../../utils/matching.js\", () => ({\n matchProcessesToSessions: vi.fn(),\n generateAgentName: vi.fn(),\n}));\n\nconst mockedListAgentProcesses = listAgentProcesses as MockedFunction<typeof listAgentProcesses>;\nconst mockedEnrichProcesses = enrichProcesses as MockedFunction<typeof enrichProcesses>;\nconst mockedCaptureProcessSnapshot = captureProcessSnapshot as MockedFunction<\n typeof captureProcessSnapshot\n>;\nconst mockedBatchGetSessionFileBirthtimes = batchGetSessionFileBirthtimes as MockedFunction<\n typeof batchGetSessionFileBirthtimes\n>;\nconst mockedMatchProcessesToSessions = matchProcessesToSessions as MockedFunction<\n typeof matchProcessesToSessions\n>;\nconst mockedGenerateAgentName = generateAgentName as MockedFunction<typeof generateAgentName>;\n\ndescribe(\"CodexAdapter\", () => {\n let originalHome: string | undefined;\n let tmpHome: string;\n let adapter: CodexAdapter;\n\n beforeEach(() => {\n originalHome = process.env.HOME;\n tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), \"codex-adapter-\"));\n process.env.HOME = tmpHome;\n\n adapter = new CodexAdapter(new AgentRegistry(path.join(tmpHome, \"agents.json\")));\n mockedListAgentProcesses.mockReset();\n mockedEnrichProcesses.mockReset();\n mockedCaptureProcessSnapshot.mockReset();\n mockedBatchGetSessionFileBirthtimes.mockReset();\n mockedMatchProcessesToSessions.mockReset();\n mockedGenerateAgentName.mockReset();\n\n mockedEnrichProcesses.mockImplementation((processes) => processes);\n mockedCaptureProcessSnapshot.mockImplementation(async (names) =>\n enrichProcesses(names.flatMap((name) => listAgentProcesses(name))),\n );\n mockedGenerateAgentName.mockImplementation(\n (cwd, pid) => `${path.basename(cwd) || \"unknown\"} (${pid})`,\n );\n });\n\n afterEach(() => {\n process.env.HOME = originalHome;\n fs.rmSync(tmpHome, { recursive: true, force: true });\n });\n\n it(\"exposes codex type and handles Codex executable names only\", () => {\n expect(adapter.type).toBe(\"codex\");\n expect(adapter.canHandle({ pid: 1, command: \"codex\", cwd: \"/repo\", tty: \"ttys001\" })).toBe(\n true,\n );\n expect(\n adapter.canHandle({\n pid: 2,\n command: \"/usr/local/bin/CODEX --sandbox workspace-write\",\n cwd: \"/repo\",\n tty: \"ttys002\",\n }),\n ).toBe(true);\n expect(\n adapter.canHandle({ pid: 3, command: \"node app.js\", cwd: \"/repo\", tty: \"ttys003\" }),\n ).toBe(false);\n expect(\n adapter.canHandle({\n pid: 4,\n command: \"node /worktrees/feature-codex-adapter-agent-manager-package/server.js\",\n cwd: \"/repo\",\n tty: \"ttys004\",\n }),\n ).toBe(false);\n });\n\n it(\"returns no agents when no Codex process is running\", async () => {\n mockedListAgentProcesses.mockReturnValue([]);\n\n await expect(adapter.detectAgents()).resolves.toEqual([]);\n expect(mockedListAgentProcesses).toHaveBeenCalledWith(\"codex\");\n });\n\n it(\"returns process-only agents when no session files are discovered\", async () => {\n const processes: ProcessInfo[] = [\n { pid: 100, command: \"codex\", cwd: \"/repo-a\", tty: \"ttys001\" },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n\n await expect(adapter.detectAgents()).resolves.toMatchObject([\n {\n type: \"codex\",\n status: AgentStatus.RUNNING,\n pid: 100,\n projectPath: \"/repo-a\",\n sessionId: \"pid-100\",\n summary: \"Codex process running\",\n },\n ]);\n });\n\n it(\"maps a running Codex process through legacy session matching\", async () => {\n const processInfo: ProcessInfo = {\n pid: 100,\n command: \"codex\",\n cwd: \"/repo-a\",\n tty: \"ttys001\",\n startTime: new Date(\"2026-03-18T15:00:00.000Z\"),\n };\n const { sessionFile, dateDir } = writeSession(\"sess-abc\", \"/repo-a\", [\n {\n type: \"event\",\n timestamp: new Date().toISOString(),\n payload: { type: \"token_count\", message: \"Implement adapter flow\" },\n },\n ]);\n const candidate: SessionFile = {\n sessionId: \"sess-abc\",\n filePath: sessionFile,\n projectDir: dateDir,\n birthtimeMs: new Date(\"2026-03-18T15:00:05Z\").getTime(),\n resolvedCwd: \"\",\n };\n const matches: MatchResult[] = [\n { process: processInfo, session: { ...candidate, resolvedCwd: \"/repo-a\" }, deltaMs: 5000 },\n ];\n\n mockedListAgentProcesses.mockReturnValue([processInfo]);\n mockedBatchGetSessionFileBirthtimes.mockReturnValue([candidate]);\n mockedMatchProcessesToSessions.mockReturnValue(matches);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: \"codex\",\n status: AgentStatus.RUNNING,\n pid: 100,\n projectPath: \"/repo-a\",\n sessionId: \"sess-abc\",\n summary: \"Implement adapter flow\",\n });\n });\n\n it(\"maps resumed sessions directly by id\", async () => {\n const sessionId = \"019eabed-4079-7071-9531-b853ddd9914e\";\n const processInfo: ProcessInfo = {\n pid: 88018,\n command: `codex resume ${sessionId}`,\n cwd: \"/repo-a\",\n tty: \"ttys001\",\n startTime: new Date(\"2026-06-10T12:00:00.000Z\"),\n };\n const { sessionFile } = writeSession(\n sessionId,\n \"/repo-a\",\n [\n {\n type: \"event\",\n timestamp: new Date().toISOString(),\n payload: { type: \"agent_message\", message: \"resumed codex conversation\" },\n },\n ],\n \"2026/06/09\",\n );\n\n mockedListAgentProcesses.mockReturnValue([processInfo]);\n mockedBatchGetSessionFileBirthtimes.mockReturnValue([]);\n\n const agents = await adapter.detectAgents();\n\n expect(mockedBatchGetSessionFileBirthtimes).not.toHaveBeenCalled();\n expect(mockedMatchProcessesToSessions).not.toHaveBeenCalled();\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: \"codex\",\n pid: 88018,\n sessionId,\n projectPath: \"/repo-a\",\n sessionFilePath: sessionFile,\n summary: \"resumed codex conversation\",\n });\n });\n\n it(\"uses hook session mapping before a stale registry entry for the same pid\", async () => {\n const registry = new AgentRegistry(path.join(tmpHome, \"agents.json\"));\n const staleSession = path.join(tmpHome, \"stale.jsonl\");\n fs.writeFileSync(\n staleSession,\n [\n JSON.stringify({\n type: \"session_meta\",\n payload: { id: \"stale\", timestamp: new Date().toISOString(), cwd: \"/repo-stale\" },\n }),\n JSON.stringify({\n type: \"event\",\n timestamp: new Date().toISOString(),\n payload: { type: \"agent_message\", message: \"stale\" },\n }),\n ].join(\"\\n\"),\n );\n registry.register(registryEntry({ sessionFilePath: staleSession }));\n\n const mappedAdapter = new CodexAdapter(registry);\n const { sessionFile } = writeSession(\n \"current\",\n \"/repo-current\",\n [\n {\n type: \"event\",\n timestamp: new Date().toISOString(),\n payload: { type: \"agent_message\", message: \"Hello from current mapping\" },\n },\n ],\n \"2026/06/26\",\n );\n fs.mkdirSync(path.join(tmpHome, \".codex\", \"ai-devkit\"), { recursive: true });\n fs.writeFileSync(\n path.join(tmpHome, \".codex\", \"ai-devkit\", \"sessions.json\"),\n JSON.stringify({ 100: sessionFile }),\n );\n\n mockedListAgentProcesses.mockReturnValue([\n { pid: 100, command: \"codex\", cwd: \"/repo-current\", tty: \"ttys001\", startTime: new Date() },\n ]);\n\n const agents = await mappedAdapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 100,\n sessionId: \"current\",\n sessionFilePath: sessionFile,\n summary: \"Hello from current mapping\",\n });\n expect(mockedBatchGetSessionFileBirthtimes).not.toHaveBeenCalled();\n expect(mockedMatchProcessesToSessions).not.toHaveBeenCalled();\n });\n\n it(\"uses a valid registry cache entry before scanning sessions\", async () => {\n const registry = new AgentRegistry(path.join(tmpHome, \"agents.json\"));\n const { sessionFile } = writeSession(\"cached\", \"/repo-a\", [\n {\n type: \"event\",\n timestamp: new Date().toISOString(),\n payload: { type: \"token_count\", message: \"Hello from cache\" },\n },\n ]);\n registry.register(registryEntry({ sessionId: \"cached\", sessionFilePath: sessionFile }));\n\n const cachedAdapter = new CodexAdapter(registry);\n mockedListAgentProcesses.mockReturnValue([\n { pid: 100, command: \"codex\", cwd: \"/repo-a\", tty: \"ttys001\", startTime: new Date() },\n ]);\n\n const agents = await cachedAdapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: \"codex\",\n pid: 100,\n sessionId: \"cached\",\n sessionFilePath: sessionFile,\n summary: \"Hello from cache\",\n });\n expect(mockedMatchProcessesToSessions).not.toHaveBeenCalled();\n expect(mockedBatchGetSessionFileBirthtimes).not.toHaveBeenCalled();\n });\n\n it(\"delegates conversation parsing and historical session listing\", async () => {\n const { sessionFile } = writeSession(\n \"listed\",\n \"/repo\",\n [\n {\n type: \"event\",\n timestamp: \"2025-01-01T00:00:01Z\",\n payload: { type: \"user_message\", message: \"first user\" },\n },\n {\n type: \"event\",\n timestamp: \"2025-01-01T00:00:02Z\",\n payload: { type: \"agent_message\", message: \"answer\" },\n },\n ],\n \"2025/01/01\",\n );\n\n expect(adapter.getConversation(sessionFile)).toEqual([\n { role: \"user\", content: \"first user\", timestamp: \"2025-01-01T00:00:01Z\" },\n { role: \"assistant\", content: \"answer\", timestamp: \"2025-01-01T00:00:02Z\" },\n ]);\n await expect(adapter.listSessions({ cwd: \"/repo\" })).resolves.toMatchObject([\n {\n type: \"codex\",\n sessionId: \"listed\",\n cwd: \"/repo\",\n firstUserMessage: \"first user\",\n sessionFilePath: sessionFile,\n },\n ]);\n await expect(adapter.findSessionsById(\"listed\")).resolves.toMatchObject([\n {\n type: \"codex\",\n sessionId: \"listed\",\n sessionFilePath: sessionFile,\n },\n ]);\n });\n\n function writeSession(\n sessionId: string,\n cwd: string,\n entries: object[],\n day = \"2026/03/18\",\n ): { sessionFile: string; dateDir: string } {\n const dateDir = path.join(tmpHome, \".codex\", \"sessions\", ...day.split(\"/\"));\n fs.mkdirSync(dateDir, { recursive: true });\n const sessionFile = path.join(dateDir, `${sessionId}.jsonl`);\n fs.writeFileSync(\n sessionFile,\n [\n JSON.stringify({\n type: \"session_meta\",\n payload: { id: sessionId, timestamp: \"2026-03-18T15:00:00Z\", cwd },\n }),\n ...entries.map((entry) => JSON.stringify(entry)),\n ].join(\"\\n\"),\n );\n return { sessionFile, dateDir };\n }\n\n function registryEntry(overrides: Partial<RegistryEntry> = {}): RegistryEntry {\n return {\n name: \"codex-100\",\n type: \"codex\",\n pid: 100,\n runtime: \"tmux\",\n runtimeRef: null,\n cwd: \"/repo-a\",\n startedAt: \"2026-05-30T00:00:00.000Z\",\n sessionId: \"cached\",\n sessionFilePath: \"\",\n ...overrides,\n };\n }\n});\n"],"names":["fs","os","path","CodexAdapter","AgentStatus","AgentRegistry","batchGetSessionFileBirthtimes","captureProcessSnapshot","enrichProcesses","listAgentProcesses","generateAgentName","matchProcessesToSessions","vi","mock","importOriginal","actual","fn","importActual","mockedListAgentProcesses","mockedEnrichProcesses","mockedCaptureProcessSnapshot","mockedBatchGetSessionFileBirthtimes","mockedMatchProcessesToSessions","mockedGenerateAgentName","describe","originalHome","tmpHome","adapter","beforeEach","process","env","HOME","mkdtempSync","join","tmpdir","mockReset","mockImplementation","processes","names","flatMap","name","cwd","pid","basename","afterEach","rmSync","recursive","force","it","expect","type","toBe","canHandle","command","tty","mockReturnValue","detectAgents","resolves","toEqual","toHaveBeenCalledWith","toMatchObject","status","RUNNING","projectPath","sessionId","summary","processInfo","startTime","Date","sessionFile","dateDir","writeSession","timestamp","toISOString","payload","message","candidate","filePath","projectDir","birthtimeMs","getTime","resolvedCwd","matches","session","deltaMs","agents","toHaveLength","not","toHaveBeenCalled","sessionFilePath","registry","staleSession","writeFileSync","JSON","stringify","id","register","registryEntry","mappedAdapter","mkdirSync","cachedAdapter","getConversation","role","content","listSessions","firstUserMessage","findSessionsById","entries","day","split","map","entry","overrides","runtime","runtimeRef","startedAt"],"mappings":"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"}
@@ -127,7 +127,8 @@ describe("tiered Codex probing", ()=>{
127
127
  });
128
128
  expect(rpc).not.toHaveBeenCalled();
129
129
  expect(result).toMatchObject({
130
- provider: "codex",
130
+ harness: "codex",
131
+ provider: "openai",
131
132
  available: "yes",
132
133
  creditsRemaining: 12.5,
133
134
  authenticated: true
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/__tests__/capacity/codex.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from \"vitest\";\nimport {\n CODEX_APP_SERVER_ARGS,\n parseUsage,\n probeCodexCapacity,\n resolveCodexAuthPath,\n toRateWindow,\n} from \"../../capacity/codex.js\";\n\nconst checkedAt = \"2026-08-20T10:00:00.000Z\";\nconst context = { installed: true, checkedAt };\n\nfunction apiUsage(overrides: Record<string, unknown> = {}) {\n return {\n rate_limit: {\n primary_window: { used_percent: 20, limit_window_seconds: 18_000, reset_at: 1_787_220_000 },\n secondary_window: {\n used_percent: 60,\n limit_window_seconds: 604_800,\n reset_at: 1_787_824_800,\n },\n ...overrides,\n },\n credits: { balance: 12.5 },\n additional_rate_limits: [\n {\n limit_name: \"reviews\",\n rate_limit: {\n primary_window: {\n used_percent: 10,\n limit_window_seconds: 3_600,\n reset_at: 1_787_220_000,\n },\n },\n },\n ],\n };\n}\n\ndescribe(\"Codex auth resolution\", () => {\n it(\"uses CODEX_HOME before HOME\", () => {\n expect(resolveCodexAuthPath({ CODEX_HOME: \"/custom/codex\", HOME: \"/users/test\" })).toBe(\n \"/custom/codex/auth.json\",\n );\n });\n\n it(\"falls back to ~/.codex/auth.json\", () => {\n expect(resolveCodexAuthPath({ HOME: \"/users/test\" })).toBe(\"/users/test/.codex/auth.json\");\n });\n});\n\ndescribe(\"Codex API usage mapping\", () => {\n it(\"converts an API window without treating missing data as zero\", () => {\n expect(\n toRateWindow(\n { used_percent: 25, limit_window_seconds: 18_000, reset_at: 1_787_220_000 },\n \"session\",\n \"Session\",\n ),\n ).toEqual({\n id: \"session\",\n label: \"Session\",\n durationMinutes: 300,\n usedPercent: 25,\n resetsAt: \"2026-08-20T10:00:00.000Z\",\n });\n expect(toRateWindow({}, \"session\", \"Session\")).toMatchObject({ usedPercent: null });\n });\n\n it(\"maps session, weekly, credits, and extra limits\", () => {\n const snapshot = parseUsage(apiUsage(), \"pat\");\n expect(snapshot).toMatchObject({ source: \"pat\", creditsRemaining: 12.5 });\n expect(snapshot.windows).toEqual([\n expect.objectContaining({ id: \"session\", durationMinutes: 300 }),\n expect.objectContaining({ id: \"weekly\", durationMinutes: 10080 }),\n expect.objectContaining({ id: \"reviews:primary\", durationMinutes: 60 }),\n ]);\n });\n\n it(\"represents missing limits as unavailable rather than zero\", () => {\n const snapshot = parseUsage({ credits: {} }, \"oauth\");\n expect(snapshot.windows).toEqual([]);\n expect(snapshot.creditsRemaining).toBeNull();\n });\n});\n\ndescribe(\"tiered Codex probing\", () => {\n it(\"selects PAT, calls whoami then usage, and never invokes the CLI\", async () => {\n const fetch = vi\n .fn()\n .mockResolvedValueOnce(\n new Response(JSON.stringify({ chatgpt_account_id: \"acct-1\" }), { status: 200 }),\n )\n .mockResolvedValueOnce(new Response(JSON.stringify(apiUsage()), { status: 200 }));\n const rpc = vi.fn();\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () =>\n JSON.stringify({\n personal_access_token: \"pat-secret\",\n tokens: { access_token: \"ignored-oauth\", account_id: \"ignored-account\" },\n }),\n fetch,\n rpc,\n });\n expect(fetch).toHaveBeenCalledTimes(2);\n expect(fetch.mock.calls[0][0]).toBe(\n \"https://auth.openai.com/api/accounts/v1/user-auth-credential/whoami\",\n );\n expect(fetch.mock.calls[1][0]).toBe(\"https://chatgpt.com/backend-api/wham/usage\");\n expect(fetch.mock.calls[1][1].headers).toMatchObject({\n Authorization: \"Bearer pat-secret\",\n \"ChatGPT-Account-Id\": \"acct-1\",\n });\n expect(rpc).not.toHaveBeenCalled();\n expect(result).toMatchObject({\n provider: \"codex\",\n available: \"yes\",\n creditsRemaining: 12.5,\n authenticated: true,\n });\n expect(result.windows.map((window) => window.id)).toEqual([\n \"session\",\n \"weekly\",\n \"reviews:primary\",\n ]);\n });\n\n it(\"selects a fresh OAuth token without calling whoami\", async () => {\n const fetch = vi\n .fn()\n .mockResolvedValue(new Response(JSON.stringify(apiUsage()), { status: 200 }));\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () =>\n JSON.stringify({\n tokens: { access_token: \"oauth-secret\", account_id: \"acct-2\", expires_at: 1_800_000_000 },\n }),\n fetch,\n now: () => new Date(\"2026-08-20T10:00:00.000Z\"),\n });\n expect(fetch).toHaveBeenCalledOnce();\n expect(fetch.mock.calls[0][1].headers).toMatchObject({\n Authorization: \"Bearer oauth-secret\",\n \"ChatGPT-Account-Id\": \"acct-2\",\n });\n expect(result.available).toBe(\"yes\");\n });\n\n it.each([\n [\n \"missing auth file\",\n async () => {\n throw Object.assign(new Error(\"missing\"), { code: \"ENOENT\" });\n },\n ],\n [\n \"stale OAuth token\",\n async () =>\n JSON.stringify({\n tokens: { access_token: \"stale-secret\", account_id: \"acct\", expires_at: 1 },\n }),\n ],\n [\n \"OAuth 401\",\n async () =>\n JSON.stringify({\n tokens: { access_token: \"oauth-secret\", account_id: \"acct\", expires_at: 1_800_000_000 },\n }),\n ],\n ])(\"falls back to the CLI for %s\", async (name, readFile) => {\n const fetch = vi\n .fn()\n .mockResolvedValue(new Response(\"\", { status: name === \"OAuth 401\" ? 401 : 200 }));\n const rpc = vi.fn(async () => ({\n rateLimits: {\n rateLimits: { primary: { usedPercent: 5, windowDurationMins: 300, resetsAt: null } },\n },\n account: { account: { type: \"chatgpt\" } },\n }));\n const result = await probeCodexCapacity({\n ...context,\n readFile,\n fetch,\n rpc,\n now: () => new Date(checkedAt),\n });\n expect(rpc).toHaveBeenCalledOnce();\n expect(result.windows).toHaveLength(1);\n });\n\n it(\"falls back to CLI if PAT requests fail\", async () => {\n const rpc = vi.fn(async () => ({ rateLimits: {}, account: { account: null } }));\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () => JSON.stringify({ personal_access_token: \"pat-secret\" }),\n fetch: vi.fn().mockRejectedValue(new Error(\"network failure pat-secret\")),\n rpc,\n });\n expect(rpc).toHaveBeenCalledOnce();\n expect(result.available).toBe(\"unknown\");\n });\n\n it(\"tries fresh OAuth after a PAT request fails\", async () => {\n const fetch = vi\n .fn()\n .mockRejectedValueOnce(new Error(\"PAT failed\"))\n .mockResolvedValueOnce(new Response(JSON.stringify(apiUsage()), { status: 200 }));\n const rpc = vi.fn();\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () =>\n JSON.stringify({\n personal_access_token: \"pat-secret\",\n tokens: { access_token: \"oauth-secret\", account_id: \"acct\", expires_at: 1_800_000_000 },\n }),\n fetch,\n rpc,\n now: () => new Date(checkedAt),\n });\n expect(fetch).toHaveBeenCalledTimes(2);\n expect(result.available).toBe(\"yes\");\n expect(rpc).not.toHaveBeenCalled();\n });\n\n it(\"uses hardened read-only app-server arguments and both account methods\", async () => {\n const rpc = vi.fn(async () => ({\n rateLimits: {\n rateLimits: { primary: { usedPercent: 5, windowDurationMins: 300, resetsAt: null } },\n },\n account: { account: { type: \"chatgpt\" } },\n }));\n await probeCodexCapacity({ ...context, readFile: async () => \"{}\", rpc });\n const messages = rpc.mock.calls[0][0];\n expect(messages.map((message) => message.method)).toEqual([\n \"initialize\",\n \"initialized\",\n \"account/rateLimits/read\",\n \"account/read\",\n ]);\n expect(JSON.stringify(messages)).not.toMatch(/prompt|turn\\/start/);\n expect(CODEX_APP_SERVER_ARGS).toEqual([\"-s\", \"read-only\", \"-a\", \"untrusted\", \"app-server\"]);\n });\n\n it(\"uses account/read to distinguish logged-out CLI state\", async () => {\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () => \"{}\",\n rpc: async () => ({ rateLimits: {}, account: { account: null } }),\n });\n expect(result).toMatchObject({ authenticated: false, available: \"unknown\" });\n });\n\n it(\"never exposes tokens or raw auth content through failures\", async () => {\n const secrets = [\"pat-secret-value\", \"oauth-secret-value\", \"refresh-secret-value\"];\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () =>\n JSON.stringify({\n personal_access_token: secrets[0],\n tokens: { access_token: secrets[1], refresh_token: secrets[2] },\n }),\n fetch: vi.fn().mockRejectedValue(new Error(secrets.join(\" \"))),\n rpc: async () => {\n throw new Error(secrets.join(\" \"));\n },\n });\n const output = JSON.stringify(result);\n for (const secret of secrets) expect(output).not.toContain(secret);\n });\n});\n"],"names":["describe","expect","it","vi","CODEX_APP_SERVER_ARGS","parseUsage","probeCodexCapacity","resolveCodexAuthPath","toRateWindow","checkedAt","context","installed","apiUsage","overrides","rate_limit","primary_window","used_percent","limit_window_seconds","reset_at","secondary_window","credits","balance","additional_rate_limits","limit_name","CODEX_HOME","HOME","toBe","toEqual","id","label","durationMinutes","usedPercent","resetsAt","toMatchObject","snapshot","source","creditsRemaining","windows","objectContaining","toBeNull","fetch","fn","mockResolvedValueOnce","Response","JSON","stringify","chatgpt_account_id","status","rpc","result","readFile","personal_access_token","tokens","access_token","account_id","toHaveBeenCalledTimes","mock","calls","headers","Authorization","not","toHaveBeenCalled","provider","available","authenticated","map","window","mockResolvedValue","expires_at","now","Date","toHaveBeenCalledOnce","each","Object","assign","Error","code","name","rateLimits","primary","windowDurationMins","account","type","toHaveLength","mockRejectedValue","mockRejectedValueOnce","messages","message","method","toMatch","secrets","refresh_token","join","output","secret","toContain"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/__tests__/capacity/codex.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from \"vitest\";\nimport {\n CODEX_APP_SERVER_ARGS,\n parseUsage,\n probeCodexCapacity,\n resolveCodexAuthPath,\n toRateWindow,\n} from \"../../capacity/codex.js\";\n\nconst checkedAt = \"2026-08-20T10:00:00.000Z\";\nconst context = { installed: true, checkedAt };\n\nfunction apiUsage(overrides: Record<string, unknown> = {}) {\n return {\n rate_limit: {\n primary_window: {\n used_percent: 20,\n limit_window_seconds: 18_000,\n reset_at: 1_787_220_000,\n },\n secondary_window: {\n used_percent: 60,\n limit_window_seconds: 604_800,\n reset_at: 1_787_824_800,\n },\n ...overrides,\n },\n credits: { balance: 12.5 },\n additional_rate_limits: [\n {\n limit_name: \"reviews\",\n rate_limit: {\n primary_window: {\n used_percent: 10,\n limit_window_seconds: 3_600,\n reset_at: 1_787_220_000,\n },\n },\n },\n ],\n };\n}\n\ndescribe(\"Codex auth resolution\", () => {\n it(\"uses CODEX_HOME before HOME\", () => {\n expect(\n resolveCodexAuthPath({\n CODEX_HOME: \"/custom/codex\",\n HOME: \"/users/test\",\n }),\n ).toBe(\"/custom/codex/auth.json\");\n });\n\n it(\"falls back to ~/.codex/auth.json\", () => {\n expect(resolveCodexAuthPath({ HOME: \"/users/test\" })).toBe(\n \"/users/test/.codex/auth.json\",\n );\n });\n});\n\ndescribe(\"Codex API usage mapping\", () => {\n it(\"converts an API window without treating missing data as zero\", () => {\n expect(\n toRateWindow(\n {\n used_percent: 25,\n limit_window_seconds: 18_000,\n reset_at: 1_787_220_000,\n },\n \"session\",\n \"Session\",\n ),\n ).toEqual({\n id: \"session\",\n label: \"Session\",\n durationMinutes: 300,\n usedPercent: 25,\n resetsAt: \"2026-08-20T10:00:00.000Z\",\n });\n expect(toRateWindow({}, \"session\", \"Session\")).toMatchObject({\n usedPercent: null,\n });\n });\n\n it(\"maps session, weekly, credits, and extra limits\", () => {\n const snapshot = parseUsage(apiUsage(), \"pat\");\n expect(snapshot).toMatchObject({ source: \"pat\", creditsRemaining: 12.5 });\n expect(snapshot.windows).toEqual([\n expect.objectContaining({ id: \"session\", durationMinutes: 300 }),\n expect.objectContaining({ id: \"weekly\", durationMinutes: 10080 }),\n expect.objectContaining({ id: \"reviews:primary\", durationMinutes: 60 }),\n ]);\n });\n\n it(\"represents missing limits as unavailable rather than zero\", () => {\n const snapshot = parseUsage({ credits: {} }, \"oauth\");\n expect(snapshot.windows).toEqual([]);\n expect(snapshot.creditsRemaining).toBeNull();\n });\n});\n\ndescribe(\"tiered Codex probing\", () => {\n it(\"selects PAT, calls whoami then usage, and never invokes the CLI\", async () => {\n const fetch = vi\n .fn()\n .mockResolvedValueOnce(\n new Response(JSON.stringify({ chatgpt_account_id: \"acct-1\" }), {\n status: 200,\n }),\n )\n .mockResolvedValueOnce(\n new Response(JSON.stringify(apiUsage()), { status: 200 }),\n );\n const rpc = vi.fn();\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () =>\n JSON.stringify({\n personal_access_token: \"pat-secret\",\n tokens: {\n access_token: \"ignored-oauth\",\n account_id: \"ignored-account\",\n },\n }),\n fetch,\n rpc,\n });\n expect(fetch).toHaveBeenCalledTimes(2);\n expect(fetch.mock.calls[0][0]).toBe(\n \"https://auth.openai.com/api/accounts/v1/user-auth-credential/whoami\",\n );\n expect(fetch.mock.calls[1][0]).toBe(\n \"https://chatgpt.com/backend-api/wham/usage\",\n );\n expect(fetch.mock.calls[1][1].headers).toMatchObject({\n Authorization: \"Bearer pat-secret\",\n \"ChatGPT-Account-Id\": \"acct-1\",\n });\n expect(rpc).not.toHaveBeenCalled();\n expect(result).toMatchObject({\n harness: \"codex\",\n provider: \"openai\",\n available: \"yes\",\n creditsRemaining: 12.5,\n authenticated: true,\n });\n expect(result.windows.map((window) => window.id)).toEqual([\n \"session\",\n \"weekly\",\n \"reviews:primary\",\n ]);\n });\n\n it(\"selects a fresh OAuth token without calling whoami\", async () => {\n const fetch = vi\n .fn()\n .mockResolvedValue(\n new Response(JSON.stringify(apiUsage()), { status: 200 }),\n );\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () =>\n JSON.stringify({\n tokens: {\n access_token: \"oauth-secret\",\n account_id: \"acct-2\",\n expires_at: 1_800_000_000,\n },\n }),\n fetch,\n now: () => new Date(\"2026-08-20T10:00:00.000Z\"),\n });\n expect(fetch).toHaveBeenCalledOnce();\n expect(fetch.mock.calls[0][1].headers).toMatchObject({\n Authorization: \"Bearer oauth-secret\",\n \"ChatGPT-Account-Id\": \"acct-2\",\n });\n expect(result.available).toBe(\"yes\");\n });\n\n it.each([\n [\n \"missing auth file\",\n async () => {\n throw Object.assign(new Error(\"missing\"), { code: \"ENOENT\" });\n },\n ],\n [\n \"stale OAuth token\",\n async () =>\n JSON.stringify({\n tokens: {\n access_token: \"stale-secret\",\n account_id: \"acct\",\n expires_at: 1,\n },\n }),\n ],\n [\n \"OAuth 401\",\n async () =>\n JSON.stringify({\n tokens: {\n access_token: \"oauth-secret\",\n account_id: \"acct\",\n expires_at: 1_800_000_000,\n },\n }),\n ],\n ])(\"falls back to the CLI for %s\", async (name, readFile) => {\n const fetch = vi\n .fn()\n .mockResolvedValue(\n new Response(\"\", { status: name === \"OAuth 401\" ? 401 : 200 }),\n );\n const rpc = vi.fn(async () => ({\n rateLimits: {\n rateLimits: {\n primary: { usedPercent: 5, windowDurationMins: 300, resetsAt: null },\n },\n },\n account: { account: { type: \"chatgpt\" } },\n }));\n const result = await probeCodexCapacity({\n ...context,\n readFile,\n fetch,\n rpc,\n now: () => new Date(checkedAt),\n });\n expect(rpc).toHaveBeenCalledOnce();\n expect(result.windows).toHaveLength(1);\n });\n\n it(\"falls back to CLI if PAT requests fail\", async () => {\n const rpc = vi.fn(async () => ({\n rateLimits: {},\n account: { account: null },\n }));\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () =>\n JSON.stringify({ personal_access_token: \"pat-secret\" }),\n fetch: vi.fn().mockRejectedValue(new Error(\"network failure pat-secret\")),\n rpc,\n });\n expect(rpc).toHaveBeenCalledOnce();\n expect(result.available).toBe(\"unknown\");\n });\n\n it(\"tries fresh OAuth after a PAT request fails\", async () => {\n const fetch = vi\n .fn()\n .mockRejectedValueOnce(new Error(\"PAT failed\"))\n .mockResolvedValueOnce(\n new Response(JSON.stringify(apiUsage()), { status: 200 }),\n );\n const rpc = vi.fn();\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () =>\n JSON.stringify({\n personal_access_token: \"pat-secret\",\n tokens: {\n access_token: \"oauth-secret\",\n account_id: \"acct\",\n expires_at: 1_800_000_000,\n },\n }),\n fetch,\n rpc,\n now: () => new Date(checkedAt),\n });\n expect(fetch).toHaveBeenCalledTimes(2);\n expect(result.available).toBe(\"yes\");\n expect(rpc).not.toHaveBeenCalled();\n });\n\n it(\"uses hardened read-only app-server arguments and both account methods\", async () => {\n const rpc = vi.fn(async () => ({\n rateLimits: {\n rateLimits: {\n primary: { usedPercent: 5, windowDurationMins: 300, resetsAt: null },\n },\n },\n account: { account: { type: \"chatgpt\" } },\n }));\n await probeCodexCapacity({ ...context, readFile: async () => \"{}\", rpc });\n const messages = rpc.mock.calls[0][0];\n expect(messages.map((message) => message.method)).toEqual([\n \"initialize\",\n \"initialized\",\n \"account/rateLimits/read\",\n \"account/read\",\n ]);\n expect(JSON.stringify(messages)).not.toMatch(/prompt|turn\\/start/);\n expect(CODEX_APP_SERVER_ARGS).toEqual([\n \"-s\",\n \"read-only\",\n \"-a\",\n \"untrusted\",\n \"app-server\",\n ]);\n });\n\n it(\"uses account/read to distinguish logged-out CLI state\", async () => {\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () => \"{}\",\n rpc: async () => ({ rateLimits: {}, account: { account: null } }),\n });\n expect(result).toMatchObject({\n authenticated: false,\n available: \"unknown\",\n });\n });\n\n it(\"never exposes tokens or raw auth content through failures\", async () => {\n const secrets = [\n \"pat-secret-value\",\n \"oauth-secret-value\",\n \"refresh-secret-value\",\n ];\n const result = await probeCodexCapacity({\n ...context,\n readFile: async () =>\n JSON.stringify({\n personal_access_token: secrets[0],\n tokens: { access_token: secrets[1], refresh_token: secrets[2] },\n }),\n fetch: vi.fn().mockRejectedValue(new Error(secrets.join(\" \"))),\n rpc: async () => {\n throw new Error(secrets.join(\" \"));\n },\n });\n const output = JSON.stringify(result);\n for (const secret of secrets) expect(output).not.toContain(secret);\n });\n});\n"],"names":["describe","expect","it","vi","CODEX_APP_SERVER_ARGS","parseUsage","probeCodexCapacity","resolveCodexAuthPath","toRateWindow","checkedAt","context","installed","apiUsage","overrides","rate_limit","primary_window","used_percent","limit_window_seconds","reset_at","secondary_window","credits","balance","additional_rate_limits","limit_name","CODEX_HOME","HOME","toBe","toEqual","id","label","durationMinutes","usedPercent","resetsAt","toMatchObject","snapshot","source","creditsRemaining","windows","objectContaining","toBeNull","fetch","fn","mockResolvedValueOnce","Response","JSON","stringify","chatgpt_account_id","status","rpc","result","readFile","personal_access_token","tokens","access_token","account_id","toHaveBeenCalledTimes","mock","calls","headers","Authorization","not","toHaveBeenCalled","harness","provider","available","authenticated","map","window","mockResolvedValue","expires_at","now","Date","toHaveBeenCalledOnce","each","Object","assign","Error","code","name","rateLimits","primary","windowDurationMins","account","type","toHaveLength","mockRejectedValue","mockRejectedValueOnce","messages","message","method","toMatch","secrets","refresh_token","join","output","secret","toContain"],"mappings":"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"}
@@ -4,7 +4,8 @@ const checkedAt = "2026-08-09T10:00:00.000Z";
4
4
  describe("getCodexCapacityReport", ()=>{
5
5
  it("checks Codex installation before probing", async ()=>{
6
6
  const probe = vi.fn(async (context)=>({
7
- provider: "codex",
7
+ harness: "codex",
8
+ provider: "openai",
8
9
  generatedAt: context.checkedAt,
9
10
  authenticated: true,
10
11
  available: "yes",
@@ -24,7 +25,8 @@ describe("getCodexCapacityReport", ()=>{
24
25
  checkedAt
25
26
  });
26
27
  expect(report).toMatchObject({
27
- provider: "codex",
28
+ harness: "codex",
29
+ provider: "openai",
28
30
  generatedAt: checkedAt,
29
31
  available: "yes"
30
32
  });
@@ -38,7 +40,8 @@ describe("getCodexCapacityReport", ()=>{
38
40
  }
39
41
  });
40
42
  expect(report).toMatchObject({
41
- provider: "codex",
43
+ harness: "codex",
44
+ provider: "openai",
42
45
  available: "unknown",
43
46
  authenticated: null,
44
47
  windows: []
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/__tests__/capacity/index.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from \"vitest\";\nimport { getCodexCapacityReport } from \"../../capacity/index.js\";\n\nconst checkedAt = \"2026-08-09T10:00:00.000Z\";\n\ndescribe(\"getCodexCapacityReport\", () => {\n it(\"checks Codex installation before probing\", async () => {\n const probe = vi.fn(async (context) => ({\n provider: \"codex\",\n generatedAt: context.checkedAt,\n authenticated: true,\n available: \"yes\" as const,\n windows: [],\n creditsRemaining: null,\n }));\n\n const report = await getCodexCapacityReport({\n now: () => new Date(checkedAt),\n path: \"/usr/bin:/opt/bin\",\n access: async (target) => {\n if (target !== \"/opt/bin/codex\") throw new Error(\"missing\");\n },\n probe,\n });\n\n expect(probe).toHaveBeenCalledWith({ installed: true, checkedAt });\n expect(report).toMatchObject({ provider: \"codex\", generatedAt: checkedAt, available: \"yes\" });\n });\n\n it(\"redacts unexpected probe failures into a stable unknown result\", async () => {\n const report = await getCodexCapacityReport({\n now: () => new Date(checkedAt),\n path: \"\",\n probe: async () => {\n throw new Error(\"private provider response\");\n },\n });\n\n expect(report).toMatchObject({\n provider: \"codex\",\n available: \"unknown\",\n authenticated: null,\n windows: [],\n });\n expect(JSON.stringify(report)).not.toContain(\"private provider response\");\n });\n});\n"],"names":["describe","expect","it","vi","getCodexCapacityReport","checkedAt","probe","fn","context","provider","generatedAt","authenticated","available","windows","creditsRemaining","report","now","Date","path","access","target","Error","toHaveBeenCalledWith","installed","toMatchObject","JSON","stringify","not","toContain"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAS;AAClD,SAASC,sBAAsB,QAAQ,0BAA0B;AAEjE,MAAMC,YAAY;AAElBL,SAAS,0BAA0B;IACjCE,GAAG,4CAA4C;QAC7C,MAAMI,QAAQH,GAAGI,EAAE,CAAC,OAAOC,UAAa,CAAA;gBACtCC,UAAU;gBACVC,aAAaF,QAAQH,SAAS;gBAC9BM,eAAe;gBACfC,WAAW;gBACXC,SAAS,EAAE;gBACXC,kBAAkB;YACpB,CAAA;QAEA,MAAMC,SAAS,MAAMX,uBAAuB;YAC1CY,KAAK,IAAM,IAAIC,KAAKZ;YACpBa,MAAM;YACNC,QAAQ,OAAOC;gBACb,IAAIA,WAAW,kBAAkB,MAAM,IAAIC,MAAM;YACnD;YACAf;QACF;QAEAL,OAAOK,OAAOgB,oBAAoB,CAAC;YAAEC,WAAW;YAAMlB;QAAU;QAChEJ,OAAOc,QAAQS,aAAa,CAAC;YAAEf,UAAU;YAASC,aAAaL;YAAWO,WAAW;QAAM;IAC7F;IAEAV,GAAG,kEAAkE;QACnE,MAAMa,SAAS,MAAMX,uBAAuB;YAC1CY,KAAK,IAAM,IAAIC,KAAKZ;YACpBa,MAAM;YACNZ,OAAO;gBACL,MAAM,IAAIe,MAAM;YAClB;QACF;QAEApB,OAAOc,QAAQS,aAAa,CAAC;YAC3Bf,UAAU;YACVG,WAAW;YACXD,eAAe;YACfE,SAAS,EAAE;QACb;QACAZ,OAAOwB,KAAKC,SAAS,CAACX,SAASY,GAAG,CAACC,SAAS,CAAC;IAC/C;AACF"}
1
+ {"version":3,"sources":["../../../src/__tests__/capacity/index.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from \"vitest\";\nimport { getCodexCapacityReport } from \"../../capacity/index.js\";\n\nconst checkedAt = \"2026-08-09T10:00:00.000Z\";\n\ndescribe(\"getCodexCapacityReport\", () => {\n it(\"checks Codex installation before probing\", async () => {\n const probe = vi.fn(async (context) => ({\n harness: \"codex\",\n provider: \"openai\",\n generatedAt: context.checkedAt,\n authenticated: true,\n available: \"yes\" as const,\n windows: [],\n creditsRemaining: null,\n }));\n\n const report = await getCodexCapacityReport({\n now: () => new Date(checkedAt),\n path: \"/usr/bin:/opt/bin\",\n access: async (target) => {\n if (target !== \"/opt/bin/codex\") throw new Error(\"missing\");\n },\n probe,\n });\n\n expect(probe).toHaveBeenCalledWith({ installed: true, checkedAt });\n expect(report).toMatchObject({\n harness: \"codex\",\n provider: \"openai\",\n generatedAt: checkedAt,\n available: \"yes\",\n });\n });\n\n it(\"redacts unexpected probe failures into a stable unknown result\", async () => {\n const report = await getCodexCapacityReport({\n now: () => new Date(checkedAt),\n path: \"\",\n probe: async () => {\n throw new Error(\"private provider response\");\n },\n });\n\n expect(report).toMatchObject({\n harness: \"codex\",\n provider: \"openai\",\n available: \"unknown\",\n authenticated: null,\n windows: [],\n });\n expect(JSON.stringify(report)).not.toContain(\"private provider response\");\n });\n});\n"],"names":["describe","expect","it","vi","getCodexCapacityReport","checkedAt","probe","fn","context","harness","provider","generatedAt","authenticated","available","windows","creditsRemaining","report","now","Date","path","access","target","Error","toHaveBeenCalledWith","installed","toMatchObject","JSON","stringify","not","toContain"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,EAAEC,EAAE,QAAQ,SAAS;AAClD,SAASC,sBAAsB,QAAQ,0BAA0B;AAEjE,MAAMC,YAAY;AAElBL,SAAS,0BAA0B;IACjCE,GAAG,4CAA4C;QAC7C,MAAMI,QAAQH,GAAGI,EAAE,CAAC,OAAOC,UAAa,CAAA;gBACtCC,SAAS;gBACTC,UAAU;gBACVC,aAAaH,QAAQH,SAAS;gBAC9BO,eAAe;gBACfC,WAAW;gBACXC,SAAS,EAAE;gBACXC,kBAAkB;YACpB,CAAA;QAEA,MAAMC,SAAS,MAAMZ,uBAAuB;YAC1Ca,KAAK,IAAM,IAAIC,KAAKb;YACpBc,MAAM;YACNC,QAAQ,OAAOC;gBACb,IAAIA,WAAW,kBAAkB,MAAM,IAAIC,MAAM;YACnD;YACAhB;QACF;QAEAL,OAAOK,OAAOiB,oBAAoB,CAAC;YAAEC,WAAW;YAAMnB;QAAU;QAChEJ,OAAOe,QAAQS,aAAa,CAAC;YAC3BhB,SAAS;YACTC,UAAU;YACVC,aAAaN;YACbQ,WAAW;QACb;IACF;IAEAX,GAAG,kEAAkE;QACnE,MAAMc,SAAS,MAAMZ,uBAAuB;YAC1Ca,KAAK,IAAM,IAAIC,KAAKb;YACpBc,MAAM;YACNb,OAAO;gBACL,MAAM,IAAIgB,MAAM;YAClB;QACF;QAEArB,OAAOe,QAAQS,aAAa,CAAC;YAC3BhB,SAAS;YACTC,UAAU;YACVG,WAAW;YACXD,eAAe;YACfE,SAAS,EAAE;QACb;QACAb,OAAOyB,KAAKC,SAAS,CAACX,SAASY,GAAG,CAACC,SAAS,CAAC;IAC/C;AACF"}
@@ -0,0 +1,213 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { fileURLToPath } from "node:url";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { parseZaiQuota, probeZaiCapacity, resolveZaiApiKey } from "../../capacity/zai.js";
5
+ const checkedAt = "2026-08-20T10:00:00.000Z";
6
+ const fixture = (name)=>readFile(fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)), "utf8");
7
+ describe("z.ai credential resolution", ()=>{
8
+ it("prefers Z_AI_API_KEY without reading Pi auth", async ()=>{
9
+ const readAuth = vi.fn(async ()=>fixture("zai-auth.json"));
10
+ await expect(resolveZaiApiKey({
11
+ env: {
12
+ Z_AI_API_KEY: "env-zai-test-key"
13
+ },
14
+ readFile: readAuth
15
+ })).resolves.toBe("env-zai-test-key");
16
+ expect(readAuth).not.toHaveBeenCalled();
17
+ });
18
+ it("reads the Pi zai api_key credential from ~/.pi/agent/auth.json", async ()=>{
19
+ const readAuth = vi.fn(async ()=>fixture("zai-auth.json"));
20
+ await expect(resolveZaiApiKey({
21
+ env: {
22
+ HOME: "/users/test"
23
+ },
24
+ readFile: readAuth
25
+ })).resolves.toBe("pi-zai-test-key");
26
+ expect(readAuth).toHaveBeenCalledWith("/users/test/.pi/agent/auth.json", "utf8");
27
+ });
28
+ it.each([
29
+ [
30
+ "missing",
31
+ async ()=>Promise.reject(Object.assign(new Error("missing"), {
32
+ code: "ENOENT"
33
+ }))
34
+ ],
35
+ [
36
+ "invalid JSON",
37
+ async ()=>"{"
38
+ ],
39
+ [
40
+ "wrong credential type",
41
+ async ()=>JSON.stringify({
42
+ zai: {
43
+ type: "oauth",
44
+ key: "x"
45
+ }
46
+ })
47
+ ],
48
+ [
49
+ "missing key",
50
+ async ()=>JSON.stringify({
51
+ zai: {
52
+ type: "api_key"
53
+ }
54
+ })
55
+ ]
56
+ ])("fails clearly for %s credentials without exposing file content", async (_name, readAuth)=>{
57
+ await expect(resolveZaiApiKey({
58
+ env: {
59
+ HOME: "/users/test"
60
+ },
61
+ readFile: readAuth
62
+ })).rejects.toThrow(/z\.ai API key|z\.ai Pi auth file/);
63
+ });
64
+ });
65
+ describe("z.ai quota mapping", ()=>{
66
+ it("maps token, credit, and monthly MCP windows with recomputed usage", async ()=>{
67
+ const report = parseZaiQuota(JSON.parse(await fixture("zai-quota.json")), checkedAt);
68
+ expect(report).toMatchObject({
69
+ harness: "pi",
70
+ provider: "zai",
71
+ generatedAt: checkedAt,
72
+ authenticated: true,
73
+ available: "yes",
74
+ creditsRemaining: null
75
+ });
76
+ expect(report.windows).toEqual([
77
+ {
78
+ id: "zai:tokens:1",
79
+ label: "Tokens · 5 hours",
80
+ limitType: "TOKENS_LIMIT",
81
+ durationMinutes: 300,
82
+ usedPercent: 30,
83
+ resetsAt: "2026-08-20T10:00:00.000Z",
84
+ total: 1000,
85
+ current: 200,
86
+ remaining: 700
87
+ },
88
+ {
89
+ id: "zai:credit:2",
90
+ label: "Credit · 1 week",
91
+ limitType: "CREDIT_LIMIT",
92
+ durationMinutes: 10080,
93
+ usedPercent: 40,
94
+ resetsAt: "2026-08-27T10:00:00.000Z",
95
+ total: 200,
96
+ current: 80,
97
+ remaining: 150
98
+ },
99
+ {
100
+ id: "zai:time:3",
101
+ label: "MCP · monthly",
102
+ limitType: "TIME_LIMIT",
103
+ durationMinutes: 43200,
104
+ usedPercent: 20,
105
+ resetsAt: "2026-09-19T10:00:00.000Z",
106
+ total: 100,
107
+ current: 20,
108
+ remaining: 90
109
+ }
110
+ ]);
111
+ });
112
+ it("uses and clamps the server percentage when quota amounts are unavailable", ()=>{
113
+ const report = parseZaiQuota({
114
+ success: true,
115
+ code: 200,
116
+ data: {
117
+ limits: [
118
+ {
119
+ type: "TOKENS_LIMIT",
120
+ unit: 1,
121
+ number: 1,
122
+ percentage: 120
123
+ },
124
+ {
125
+ type: "CREDIT_LIMIT",
126
+ unit: 5,
127
+ number: 30,
128
+ percentage: -5
129
+ }
130
+ ]
131
+ }
132
+ }, checkedAt);
133
+ expect(report.windows.map((window)=>window.usedPercent)).toEqual([
134
+ 100,
135
+ 0
136
+ ]);
137
+ });
138
+ it.each([
139
+ [
140
+ "invalid fixture",
141
+ async ()=>JSON.parse(await fixture("zai-quota-invalid.json"))
142
+ ],
143
+ [
144
+ "unsuccessful envelope",
145
+ async ()=>({
146
+ success: false,
147
+ code: 401,
148
+ data: {
149
+ limits: []
150
+ }
151
+ })
152
+ ],
153
+ [
154
+ "invalid entry",
155
+ async ()=>({
156
+ success: true,
157
+ code: 200,
158
+ data: {
159
+ limits: [
160
+ {}
161
+ ]
162
+ }
163
+ })
164
+ ]
165
+ ])("rejects %s", async (_name, input)=>{
166
+ const value = await input();
167
+ expect(()=>parseZaiQuota(value, checkedAt)).toThrow(/z\.ai quota/);
168
+ });
169
+ });
170
+ describe("z.ai request", ()=>{
171
+ it("GETs the global quota endpoint with only Bearer authentication", async ()=>{
172
+ const body = await fixture("zai-quota.json");
173
+ const fetch = vi.fn(async ()=>new Response(body, {
174
+ status: 200
175
+ }));
176
+ const report = await probeZaiCapacity({
177
+ checkedAt,
178
+ env: {
179
+ Z_AI_API_KEY: "request-test-key"
180
+ },
181
+ fetch
182
+ });
183
+ expect(fetch).toHaveBeenCalledWith("https://api.z.ai/api/monitor/usage/quota/limit", expect.objectContaining({
184
+ method: "GET",
185
+ headers: {
186
+ Authorization: "Bearer request-test-key"
187
+ }
188
+ }));
189
+ expect(JSON.stringify(report)).not.toContain("request-test-key");
190
+ });
191
+ it("rejects non-200 and malformed JSON responses with sanitized errors", async ()=>{
192
+ await expect(probeZaiCapacity({
193
+ checkedAt,
194
+ env: {
195
+ Z_AI_API_KEY: "never-expose-this-key"
196
+ },
197
+ fetch: vi.fn(async ()=>new Response("private body", {
198
+ status: 503
199
+ }))
200
+ })).rejects.toThrow("z.ai quota request failed: HTTP 503");
201
+ await expect(probeZaiCapacity({
202
+ checkedAt,
203
+ env: {
204
+ Z_AI_API_KEY: "never-expose-this-key"
205
+ },
206
+ fetch: vi.fn(async ()=>new Response("not-json", {
207
+ status: 200
208
+ }))
209
+ })).rejects.toThrow("z.ai quota response is not valid JSON");
210
+ });
211
+ });
212
+
213
+ //# sourceMappingURL=zai.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/__tests__/capacity/zai.test.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { describe, expect, it, vi } from \"vitest\";\nimport {\n parseZaiQuota,\n probeZaiCapacity,\n resolveZaiApiKey,\n} from \"../../capacity/zai.js\";\n\nconst checkedAt = \"2026-08-20T10:00:00.000Z\";\nconst fixture = (name: string) =>\n readFile(\n fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)),\n \"utf8\",\n );\n\ndescribe(\"z.ai credential resolution\", () => {\n it(\"prefers Z_AI_API_KEY without reading Pi auth\", async () => {\n const readAuth = vi.fn(async () => fixture(\"zai-auth.json\"));\n await expect(\n resolveZaiApiKey({\n env: { Z_AI_API_KEY: \"env-zai-test-key\" },\n readFile: readAuth,\n }),\n ).resolves.toBe(\"env-zai-test-key\");\n expect(readAuth).not.toHaveBeenCalled();\n });\n\n it(\"reads the Pi zai api_key credential from ~/.pi/agent/auth.json\", async () => {\n const readAuth = vi.fn(async () => fixture(\"zai-auth.json\"));\n await expect(\n resolveZaiApiKey({ env: { HOME: \"/users/test\" }, readFile: readAuth }),\n ).resolves.toBe(\"pi-zai-test-key\");\n expect(readAuth).toHaveBeenCalledWith(\n \"/users/test/.pi/agent/auth.json\",\n \"utf8\",\n );\n });\n\n it.each([\n [\n \"missing\",\n async () =>\n Promise.reject(Object.assign(new Error(\"missing\"), { code: \"ENOENT\" })),\n ],\n [\"invalid JSON\", async () => \"{\"],\n [\n \"wrong credential type\",\n async () => JSON.stringify({ zai: { type: \"oauth\", key: \"x\" } }),\n ],\n [\"missing key\", async () => JSON.stringify({ zai: { type: \"api_key\" } })],\n ])(\n \"fails clearly for %s credentials without exposing file content\",\n async (_name, readAuth) => {\n await expect(\n resolveZaiApiKey({ env: { HOME: \"/users/test\" }, readFile: readAuth }),\n ).rejects.toThrow(/z\\.ai API key|z\\.ai Pi auth file/);\n },\n );\n});\n\ndescribe(\"z.ai quota mapping\", () => {\n it(\"maps token, credit, and monthly MCP windows with recomputed usage\", async () => {\n const report = parseZaiQuota(\n JSON.parse(await fixture(\"zai-quota.json\")),\n checkedAt,\n );\n expect(report).toMatchObject({\n harness: \"pi\",\n provider: \"zai\",\n generatedAt: checkedAt,\n authenticated: true,\n available: \"yes\",\n creditsRemaining: null,\n });\n expect(report.windows).toEqual([\n {\n id: \"zai:tokens:1\",\n label: \"Tokens · 5 hours\",\n limitType: \"TOKENS_LIMIT\",\n durationMinutes: 300,\n usedPercent: 30,\n resetsAt: \"2026-08-20T10:00:00.000Z\",\n total: 1000,\n current: 200,\n remaining: 700,\n },\n {\n id: \"zai:credit:2\",\n label: \"Credit · 1 week\",\n limitType: \"CREDIT_LIMIT\",\n durationMinutes: 10080,\n usedPercent: 40,\n resetsAt: \"2026-08-27T10:00:00.000Z\",\n total: 200,\n current: 80,\n remaining: 150,\n },\n {\n id: \"zai:time:3\",\n label: \"MCP · monthly\",\n limitType: \"TIME_LIMIT\",\n durationMinutes: 43200,\n usedPercent: 20,\n resetsAt: \"2026-09-19T10:00:00.000Z\",\n total: 100,\n current: 20,\n remaining: 90,\n },\n ]);\n });\n\n it(\"uses and clamps the server percentage when quota amounts are unavailable\", () => {\n const report = parseZaiQuota(\n {\n success: true,\n code: 200,\n data: {\n limits: [\n { type: \"TOKENS_LIMIT\", unit: 1, number: 1, percentage: 120 },\n { type: \"CREDIT_LIMIT\", unit: 5, number: 30, percentage: -5 },\n ],\n },\n },\n checkedAt,\n );\n expect(report.windows.map((window) => window.usedPercent)).toEqual([\n 100, 0,\n ]);\n });\n\n it.each([\n [\n \"invalid fixture\",\n async () => JSON.parse(await fixture(\"zai-quota-invalid.json\")),\n ],\n [\n \"unsuccessful envelope\",\n async () => ({ success: false, code: 401, data: { limits: [] } }),\n ],\n [\n \"invalid entry\",\n async () => ({ success: true, code: 200, data: { limits: [{}] } }),\n ],\n ])(\"rejects %s\", async (_name, input) => {\n const value = await input();\n expect(() => parseZaiQuota(value, checkedAt)).toThrow(/z\\.ai quota/);\n });\n});\n\ndescribe(\"z.ai request\", () => {\n it(\"GETs the global quota endpoint with only Bearer authentication\", async () => {\n const body = await fixture(\"zai-quota.json\");\n const fetch = vi.fn(async () => new Response(body, { status: 200 }));\n const report = await probeZaiCapacity({\n checkedAt,\n env: { Z_AI_API_KEY: \"request-test-key\" },\n fetch,\n });\n expect(fetch).toHaveBeenCalledWith(\n \"https://api.z.ai/api/monitor/usage/quota/limit\",\n expect.objectContaining({\n method: \"GET\",\n headers: { Authorization: \"Bearer request-test-key\" },\n }),\n );\n expect(JSON.stringify(report)).not.toContain(\"request-test-key\");\n });\n\n it(\"rejects non-200 and malformed JSON responses with sanitized errors\", async () => {\n await expect(\n probeZaiCapacity({\n checkedAt,\n env: { Z_AI_API_KEY: \"never-expose-this-key\" },\n fetch: vi.fn(async () => new Response(\"private body\", { status: 503 })),\n }),\n ).rejects.toThrow(\"z.ai quota request failed: HTTP 503\");\n await expect(\n probeZaiCapacity({\n checkedAt,\n env: { Z_AI_API_KEY: \"never-expose-this-key\" },\n fetch: vi.fn(async () => new Response(\"not-json\", { status: 200 })),\n }),\n ).rejects.toThrow(\"z.ai quota response is not valid JSON\");\n });\n});\n"],"names":["readFile","fileURLToPath","describe","expect","it","vi","parseZaiQuota","probeZaiCapacity","resolveZaiApiKey","checkedAt","fixture","name","URL","url","readAuth","fn","env","Z_AI_API_KEY","resolves","toBe","not","toHaveBeenCalled","HOME","toHaveBeenCalledWith","each","Promise","reject","Object","assign","Error","code","JSON","stringify","zai","type","key","_name","rejects","toThrow","report","parse","toMatchObject","harness","provider","generatedAt","authenticated","available","creditsRemaining","windows","toEqual","id","label","limitType","durationMinutes","usedPercent","resetsAt","total","current","remaining","success","data","limits","unit","number","percentage","map","window","input","value","body","fetch","Response","status","objectContaining","method","headers","Authorization","toContain"],"mappings":"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"}
@@ -585,6 +585,24 @@ describe("CopilotAdapter", ()=>{
585
585
  firstUserMessage: "other user"
586
586
  });
587
587
  });
588
+ it("finds an exact session ID from its session directory", async ()=>{
589
+ writeSession("wanted", {
590
+ events: [
591
+ sessionStart("wanted", "/repo")
592
+ ]
593
+ });
594
+ writeSession("other", {
595
+ events: [
596
+ sessionStart("other", "/other")
597
+ ]
598
+ });
599
+ await expect(adapter.findSessionsById("wanted")).resolves.toEqual([
600
+ expect.objectContaining({
601
+ sessionId: "wanted",
602
+ sessionFilePath: path.join(sessionStateDir, "wanted", "events.jsonl")
603
+ })
604
+ ]);
605
+ });
588
606
  it("applies strict cwd filter", async ()=>{
589
607
  writeSession("keep", {
590
608
  events: [