@ai-devkit/agent-manager 0.34.0 → 0.36.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 (211) 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/AntigravityCliAdapter.test.js +381 -0
  8. package/dist/__tests__/adapters/AntigravityCliAdapter.test.js.map +1 -0
  9. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +28 -0
  10. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -1
  11. package/dist/__tests__/adapters/CodexAdapter.test.js +7 -0
  12. package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -1
  13. package/dist/__tests__/adapters/KiroAdapter.test.js +607 -0
  14. package/dist/__tests__/adapters/KiroAdapter.test.js.map +1 -0
  15. package/dist/__tests__/capacity/claude.test.js +485 -0
  16. package/dist/__tests__/capacity/claude.test.js.map +1 -0
  17. package/dist/__tests__/capacity/openai.test.js +672 -0
  18. package/dist/__tests__/capacity/openai.test.js.map +1 -0
  19. package/dist/__tests__/capacity/wham.test.js +144 -0
  20. package/dist/__tests__/capacity/wham.test.js.map +1 -0
  21. package/dist/__tests__/providers/copilot/CopilotAdapter.test.js +18 -0
  22. package/dist/__tests__/providers/copilot/CopilotAdapter.test.js.map +1 -1
  23. package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js +36 -0
  24. package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js.map +1 -1
  25. package/dist/__tests__/providers/grok/GrokAgentMapper.test.js +56 -0
  26. package/dist/__tests__/providers/grok/GrokAgentMapper.test.js.map +1 -0
  27. package/dist/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.js +19 -6
  28. package/dist/__tests__/providers/grok/GrokCliAdapter.test.js.map +1 -0
  29. package/dist/__tests__/providers/grok/GrokSessionLocator.test.js +133 -0
  30. package/dist/__tests__/providers/grok/GrokSessionLocator.test.js.map +1 -0
  31. package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js +24 -0
  32. package/dist/__tests__/providers/opencode/OpenCodeAdapter.test.js.map +1 -1
  33. package/dist/__tests__/providers/pi/PiAdapter.test.js +30 -0
  34. package/dist/__tests__/providers/pi/PiAdapter.test.js.map +1 -1
  35. package/dist/__tests__/utils/agents.test.js +6 -0
  36. package/dist/__tests__/utils/agents.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 +9 -1
  40. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  41. package/dist/adapters/AgentAdapter.js.map +1 -1
  42. package/dist/adapters/AntigravityCliAdapter.d.ts +57 -0
  43. package/dist/adapters/AntigravityCliAdapter.d.ts.map +1 -0
  44. package/dist/adapters/AntigravityCliAdapter.js +273 -0
  45. package/dist/adapters/AntigravityCliAdapter.js.map +1 -0
  46. package/dist/adapters/KiroAdapter.d.ts +46 -0
  47. package/dist/adapters/KiroAdapter.d.ts.map +1 -0
  48. package/dist/adapters/KiroAdapter.js +344 -0
  49. package/dist/adapters/KiroAdapter.js.map +1 -0
  50. package/dist/adapters/index.d.ts +3 -1
  51. package/dist/adapters/index.d.ts.map +1 -1
  52. package/dist/adapters/index.js +3 -1
  53. package/dist/adapters/index.js.map +1 -1
  54. package/dist/capacity/claude.d.ts +17 -0
  55. package/dist/capacity/claude.d.ts.map +1 -0
  56. package/dist/capacity/claude.js +271 -0
  57. package/dist/capacity/claude.js.map +1 -0
  58. package/dist/capacity/codex.d.ts +1 -2
  59. package/dist/capacity/codex.d.ts.map +1 -1
  60. package/dist/capacity/codex.js +5 -47
  61. package/dist/capacity/codex.js.map +1 -1
  62. package/dist/capacity/index.d.ts +11 -0
  63. package/dist/capacity/index.d.ts.map +1 -1
  64. package/dist/capacity/index.js +17 -0
  65. package/dist/capacity/index.js.map +1 -1
  66. package/dist/capacity/openai.d.ts +48 -0
  67. package/dist/capacity/openai.d.ts.map +1 -0
  68. package/dist/capacity/openai.js +312 -0
  69. package/dist/capacity/openai.js.map +1 -0
  70. package/dist/capacity/wham.d.ts +16 -0
  71. package/dist/capacity/wham.d.ts.map +1 -0
  72. package/dist/capacity/wham.js +66 -0
  73. package/dist/capacity/wham.js.map +1 -0
  74. package/dist/index.d.ts +5 -3
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +4 -2
  77. package/dist/index.js.map +1 -1
  78. package/dist/providers/claude/ClaudeCodeAdapter.d.ts +2 -0
  79. package/dist/providers/claude/ClaudeCodeAdapter.d.ts.map +1 -1
  80. package/dist/providers/claude/ClaudeCodeAdapter.js +22 -19
  81. package/dist/providers/claude/ClaudeCodeAdapter.js.map +1 -1
  82. package/dist/providers/claude/ClaudeSessionLocator.d.ts +4 -0
  83. package/dist/providers/claude/ClaudeSessionLocator.d.ts.map +1 -1
  84. package/dist/providers/claude/ClaudeSessionLocator.js +18 -1
  85. package/dist/providers/claude/ClaudeSessionLocator.js.map +1 -1
  86. package/dist/providers/codex/CodexAdapter.d.ts +1 -0
  87. package/dist/providers/codex/CodexAdapter.d.ts.map +1 -1
  88. package/dist/providers/codex/CodexAdapter.js +8 -0
  89. package/dist/providers/codex/CodexAdapter.js.map +1 -1
  90. package/dist/providers/copilot/CopilotAdapter.d.ts +2 -0
  91. package/dist/providers/copilot/CopilotAdapter.d.ts.map +1 -1
  92. package/dist/providers/copilot/CopilotAdapter.js +24 -9
  93. package/dist/providers/copilot/CopilotAdapter.js.map +1 -1
  94. package/dist/providers/copilot/CopilotSessionLocator.d.ts +1 -0
  95. package/dist/providers/copilot/CopilotSessionLocator.d.ts.map +1 -1
  96. package/dist/providers/copilot/CopilotSessionLocator.js +9 -1
  97. package/dist/providers/copilot/CopilotSessionLocator.js.map +1 -1
  98. package/dist/providers/gemini/GeminiCliAdapter.d.ts +1 -0
  99. package/dist/providers/gemini/GeminiCliAdapter.d.ts.map +1 -1
  100. package/dist/providers/gemini/GeminiCliAdapter.js +9 -0
  101. package/dist/providers/gemini/GeminiCliAdapter.js.map +1 -1
  102. package/dist/providers/grok/GrokAgentMapper.d.ts +14 -0
  103. package/dist/providers/grok/GrokAgentMapper.d.ts.map +1 -0
  104. package/dist/providers/grok/GrokAgentMapper.js +36 -0
  105. package/dist/providers/grok/GrokAgentMapper.js.map +1 -0
  106. package/dist/providers/grok/GrokCliAdapter.d.ts +36 -0
  107. package/dist/providers/grok/GrokCliAdapter.d.ts.map +1 -0
  108. package/dist/providers/grok/GrokCliAdapter.js +98 -0
  109. package/dist/providers/grok/GrokCliAdapter.js.map +1 -0
  110. package/dist/providers/grok/GrokSessionLocator.d.ts +62 -0
  111. package/dist/providers/grok/GrokSessionLocator.d.ts.map +1 -0
  112. package/dist/providers/grok/GrokSessionLocator.js +140 -0
  113. package/dist/providers/grok/GrokSessionLocator.js.map +1 -0
  114. package/dist/providers/grok/GrokSessionParser.d.ts +54 -0
  115. package/dist/providers/grok/GrokSessionParser.d.ts.map +1 -0
  116. package/dist/providers/grok/GrokSessionParser.js +122 -0
  117. package/dist/providers/grok/GrokSessionParser.js.map +1 -0
  118. package/dist/providers/opencode/OpenCodeAdapter.d.ts +1 -0
  119. package/dist/providers/opencode/OpenCodeAdapter.d.ts.map +1 -1
  120. package/dist/providers/opencode/OpenCodeAdapter.js +3 -0
  121. package/dist/providers/opencode/OpenCodeAdapter.js.map +1 -1
  122. package/dist/providers/opencode/OpenCodeSessionLocator.d.ts +2 -0
  123. package/dist/providers/opencode/OpenCodeSessionLocator.d.ts.map +1 -1
  124. package/dist/providers/opencode/OpenCodeSessionLocator.js +31 -12
  125. package/dist/providers/opencode/OpenCodeSessionLocator.js.map +1 -1
  126. package/dist/providers/pi/PiAdapter.d.ts +1 -0
  127. package/dist/providers/pi/PiAdapter.d.ts.map +1 -1
  128. package/dist/providers/pi/PiAdapter.js +10 -0
  129. package/dist/providers/pi/PiAdapter.js.map +1 -1
  130. package/dist/providers/pi/PiSessionLocator.d.ts +1 -0
  131. package/dist/providers/pi/PiSessionLocator.d.ts.map +1 -1
  132. package/dist/providers/pi/PiSessionLocator.js +8 -1
  133. package/dist/providers/pi/PiSessionLocator.js.map +1 -1
  134. package/dist/readiness/AgentReadiness.d.ts +1 -1
  135. package/dist/readiness/AgentReadiness.d.ts.map +1 -1
  136. package/dist/readiness/AgentReadiness.js.map +1 -1
  137. package/dist/runtime/ManagedAgentRuntime.js +3 -1
  138. package/dist/runtime/ManagedAgentRuntime.js.map +1 -1
  139. package/dist/runtime/herdr/HerdrAgentDiscovery.js +10 -0
  140. package/dist/runtime/herdr/HerdrAgentDiscovery.js.map +1 -1
  141. package/dist/utils/agents.d.ts +1 -1
  142. package/dist/utils/agents.d.ts.map +1 -1
  143. package/dist/utils/agents.js +11 -0
  144. package/dist/utils/agents.js.map +1 -1
  145. package/dist/utils/session.d.ts +2 -0
  146. package/dist/utils/session.d.ts.map +1 -1
  147. package/dist/utils/session.js +3 -0
  148. package/dist/utils/session.js.map +1 -1
  149. package/package.json +1 -1
  150. package/src/AgentManager.ts +34 -0
  151. package/src/__tests__/AgentManager.test.ts +60 -0
  152. package/src/__tests__/adapters/AntigravityCliAdapter.test.ts +324 -0
  153. package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +23 -0
  154. package/src/__tests__/adapters/CodexAdapter.test.ts +7 -0
  155. package/src/__tests__/adapters/KiroAdapter.test.ts +534 -0
  156. package/src/__tests__/capacity/claude.test.ts +467 -0
  157. package/src/__tests__/capacity/fixtures/claude-credentials.json +7 -0
  158. package/src/__tests__/capacity/fixtures/claude-usage-malformed.json +1 -0
  159. package/src/__tests__/capacity/fixtures/claude-usage-partial.json +15 -0
  160. package/src/__tests__/capacity/fixtures/claude-usage.json +63 -0
  161. package/src/__tests__/capacity/fixtures/openai-auth.json +6 -0
  162. package/src/__tests__/capacity/fixtures/openai-codex-auth.json +9 -0
  163. package/src/__tests__/capacity/fixtures/openai-usage-completions.json +67 -0
  164. package/src/__tests__/capacity/fixtures/openai-usage-responses.json +20 -0
  165. package/src/__tests__/capacity/fixtures/wham-usage.json +32 -0
  166. package/src/__tests__/capacity/openai.test.ts +570 -0
  167. package/src/__tests__/capacity/wham.test.ts +104 -0
  168. package/src/__tests__/providers/copilot/CopilotAdapter.test.ts +12 -0
  169. package/src/__tests__/providers/gemini/GeminiCliAdapter.test.ts +21 -0
  170. package/src/__tests__/providers/grok/GrokAgentMapper.test.ts +57 -0
  171. package/src/__tests__/{adapters → providers/grok}/GrokCliAdapter.test.ts +18 -9
  172. package/src/__tests__/providers/grok/GrokSessionLocator.test.ts +106 -0
  173. package/src/__tests__/providers/opencode/OpenCodeAdapter.test.ts +18 -0
  174. package/src/__tests__/providers/pi/PiAdapter.test.ts +15 -0
  175. package/src/__tests__/utils/agents.test.ts +7 -0
  176. package/src/__tests__/utils/session.test.ts +11 -1
  177. package/src/adapters/AgentAdapter.ts +11 -0
  178. package/src/adapters/AntigravityCliAdapter.ts +356 -0
  179. package/src/adapters/KiroAdapter.ts +456 -0
  180. package/src/adapters/index.ts +3 -1
  181. package/src/capacity/claude.ts +313 -0
  182. package/src/capacity/codex.ts +25 -134
  183. package/src/capacity/index.ts +33 -4
  184. package/src/capacity/openai.ts +361 -0
  185. package/src/capacity/wham.ts +84 -0
  186. package/src/index.ts +11 -2
  187. package/src/providers/claude/ClaudeCodeAdapter.ts +28 -23
  188. package/src/providers/claude/ClaudeSessionLocator.ts +20 -0
  189. package/src/providers/codex/CodexAdapter.ts +8 -0
  190. package/src/providers/copilot/CopilotAdapter.ts +26 -10
  191. package/src/providers/copilot/CopilotSessionLocator.ts +7 -1
  192. package/src/providers/gemini/GeminiCliAdapter.ts +8 -0
  193. package/src/providers/grok/GrokAgentMapper.ts +41 -0
  194. package/src/providers/grok/GrokCliAdapter.ts +125 -0
  195. package/src/providers/grok/GrokSessionLocator.ts +179 -0
  196. package/src/providers/grok/GrokSessionParser.ts +166 -0
  197. package/src/providers/opencode/OpenCodeAdapter.ts +4 -0
  198. package/src/providers/opencode/OpenCodeSessionLocator.ts +43 -15
  199. package/src/providers/pi/PiAdapter.ts +12 -0
  200. package/src/providers/pi/PiSessionLocator.ts +16 -1
  201. package/src/readiness/AgentReadiness.ts +4 -1
  202. package/src/runtime/ManagedAgentRuntime.ts +2 -0
  203. package/src/runtime/herdr/HerdrAgentDiscovery.ts +5 -1
  204. package/src/utils/agents.ts +11 -1
  205. package/src/utils/session.ts +11 -0
  206. package/dist/__tests__/adapters/GrokCliAdapter.test.js.map +0 -1
  207. package/dist/adapters/GrokCliAdapter.d.ts +0 -80
  208. package/dist/adapters/GrokCliAdapter.d.ts.map +0 -1
  209. package/dist/adapters/GrokCliAdapter.js +0 -310
  210. package/dist/adapters/GrokCliAdapter.js.map +0 -1
  211. package/src/adapters/GrokCliAdapter.ts +0 -408
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/__tests__/capacity/openai.test.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { readFileSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport { describe, expect, it, vi } from \"vitest\";\nimport {\n buildOpenAiReport,\n parseOpenAiUsage,\n probeOpenAiCapacity,\n resolveOpenAiApiKey,\n resolveOpenAiCredential,\n} from \"../../capacity/openai.js\";\n\nconst checkedAt = \"2026-09-26T10:00:00.000Z\";\nconst fixture = (name: string) =>\n readFile(fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)), \"utf8\");\nconst fixtureString = (name: string) =>\n readFileSync(fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)), \"utf8\");\n\ndescribe(\"OpenAI credential resolution\", () => {\n it(\"prefers OPENAI_API_KEY without reading Pi auth\", async () => {\n const readAuth = vi.fn(async () => fixture(\"openai-auth.json\"));\n await expect(\n resolveOpenAiApiKey({\n env: { OPENAI_API_KEY: \"env-openai-test-key\" },\n readFile: readAuth,\n }),\n ).resolves.toBe(\"env-openai-test-key\");\n expect(readAuth).not.toHaveBeenCalled();\n });\n\n it(\"reads the Pi openai api_key credential from ~/.pi/agent/auth.json\", async () => {\n const readAuth = vi.fn(async () => fixture(\"openai-auth.json\"));\n await expect(\n resolveOpenAiApiKey({ env: { HOME: \"/users/test\" }, readFile: readAuth }),\n ).resolves.toBe(\"pi-openai-test-key\");\n expect(readAuth).toHaveBeenCalledWith(\"/users/test/.pi/agent/auth.json\", \"utf8\");\n });\n\n it.each([\n [\n \"missing\",\n async () => Promise.reject(Object.assign(new Error(\"missing\"), { code: \"ENOENT\" })),\n ],\n [\"invalid JSON\", async () => \"{\"],\n [\"wrong credential type\", async () => JSON.stringify({ openai: { type: \"oauth\", key: \"x\" } })],\n [\"missing key\", async () => JSON.stringify({ openai: { type: \"api_key\" } })],\n ])(\"fails clearly for %s credentials without exposing file content\", async (_name, readAuth) => {\n await expect(\n resolveOpenAiApiKey({\n env: { HOME: \"/users/test\" },\n readFile: readAuth,\n }),\n ).rejects.toThrow(/OpenAI credentials not found|OpenAI Pi auth file/);\n });\n});\n\ndescribe(\"OpenAI tiered credential resolution\", () => {\n const oauthFixture = () => fixture(\"openai-codex-auth.json\");\n const bothCredentials = JSON.stringify({\n openai: { type: \"api_key\", key: \"pi-openai-test-key\" },\n \"openai-codex\": {\n type: \"oauth\",\n access: \"fixture-header.eyJleHAiOjE3OTMwMDAwMDB9.fixture-signature\",\n refresh: \"fixture-refresh-token\",\n expires: 1793000000000,\n accountId: \"fixture-chatgpt-account-id\",\n },\n });\n\n it(\"prefers the Pi openai platform credential over the openai-codex OAuth login\", async () => {\n await expect(\n resolveOpenAiCredential({\n env: { HOME: \"/users/test\" },\n readFile: async () => bothCredentials,\n }),\n ).resolves.toEqual({ kind: \"platform\", key: \"pi-openai-test-key\" });\n });\n\n it(\"resolves the Pi openai-codex OAuth login when no platform credential exists\", async () => {\n await expect(\n resolveOpenAiCredential({\n env: { HOME: \"/users/test\" },\n readFile: async () => oauthFixture(),\n }),\n ).resolves.toEqual({\n kind: \"oauth\",\n access: \"fixture-header.eyJleHAiOjE3OTMwMDAwMDB9.fixture-signature\",\n accountId: \"fixture-chatgpt-account-id\",\n expiresMs: 1793000000000,\n });\n });\n\n it(\"normalizes second-based expires values to epoch milliseconds\", async () => {\n const auth = JSON.stringify({\n \"openai-codex\": {\n type: \"oauth\",\n access: \"fixture-access\",\n refresh: \"fixture-refresh\",\n expires: 1793000000,\n accountId: \"fixture-chatgpt-account-id\",\n },\n });\n await expect(\n resolveOpenAiCredential({ env: { HOME: \"/users/test\" }, readFile: async () => auth }),\n ).resolves.toMatchObject({ kind: \"oauth\", expiresMs: 1793000000000 });\n });\n\n it(\"keeps a null expiry for OAuth logins without usable expires metadata\", async () => {\n const auth = JSON.stringify({\n \"openai-codex\": {\n type: \"oauth\",\n access: \"fixture-access\",\n accountId: \"fixture-chatgpt-account-id\",\n },\n });\n await expect(\n resolveOpenAiCredential({ env: { HOME: \"/users/test\" }, readFile: async () => auth }),\n ).resolves.toMatchObject({ kind: \"oauth\", expiresMs: null });\n });\n\n it.each([\n [\"wrong credential type\", JSON.stringify({ \"openai-codex\": { type: \"api_key\", key: \"x\" } })],\n [\"missing access token\", JSON.stringify({ \"openai-codex\": { type: \"oauth\", accountId: \"a\" } })],\n [\"missing account id\", JSON.stringify({ \"openai-codex\": { type: \"oauth\", access: \"t\" } })],\n ])(\n \"skips a malformed openai-codex credential (%s) with a sanitized error\",\n async (_name, auth) => {\n await expect(\n resolveOpenAiCredential({ env: { HOME: \"/users/test\" }, readFile: async () => auth }),\n ).rejects.toThrow(\"OpenAI credentials not found\");\n },\n );\n\n it(\"still throws for OAuth-only machines on the legacy platform-key resolver\", async () => {\n await expect(\n resolveOpenAiApiKey({\n env: { HOME: \"/users/test\" },\n readFile: async () => oauthFixture(),\n }),\n ).rejects.toThrow(\"OpenAI credentials not found\");\n });\n});\n\ndescribe(\"OpenAI OAuth capacity probe\", () => {\n const freshOauth = () => fixture(\"openai-codex-auth.json\");\n const whamResponse = () => new Response(fixtureString(\"wham-usage.json\"), { status: 200 });\n\n it(\"reports wham usage windows for a fresh Pi OAuth login\", async () => {\n const fetch = vi.fn(async () => whamResponse());\n const report = await probeOpenAiCapacity({\n checkedAt,\n env: { HOME: \"/users/test\" },\n readFile: async () => freshOauth(),\n fetch,\n });\n expect(fetch).toHaveBeenCalledWith(\n \"https://chatgpt.com/backend-api/wham/usage\",\n expect.objectContaining({\n method: \"GET\",\n headers: {\n Authorization: \"Bearer fixture-header.eyJleHAiOjE3OTMwMDAwMDB9.fixture-signature\",\n \"ChatGPT-Account-Id\": \"fixture-chatgpt-account-id\",\n },\n }),\n );\n expect(report).toMatchObject({\n harness: \"pi\",\n provider: \"openai\",\n generatedAt: checkedAt,\n authenticated: true,\n available: \"yes\",\n creditsRemaining: 4.5,\n });\n expect(report.windows.map((window) => window.id)).toEqual([\"session\", \"weekly\"]);\n expect(JSON.stringify(report)).not.toContain(\"fixture-signature\");\n });\n\n it(\"does not fetch and reports an authenticated-but-stale state for expired logins\", async () => {\n const staleAuth = JSON.stringify({\n \"openai-codex\": {\n type: \"oauth\",\n access: \"stale-fixture-access\",\n expires: 1790000000000,\n accountId: \"fixture-chatgpt-account-id\",\n },\n });\n const fetch = vi.fn();\n const report = await probeOpenAiCapacity({\n checkedAt,\n env: { HOME: \"/users/test\" },\n readFile: async () => staleAuth,\n fetch,\n });\n expect(fetch).not.toHaveBeenCalled();\n expect(report).toEqual({\n harness: \"pi\",\n provider: \"openai\",\n generatedAt: checkedAt,\n authenticated: true,\n available: \"unknown\",\n windows: [],\n creditsRemaining: null,\n });\n });\n\n it(\"treats second-based expiry metadata as stale the same way\", async () => {\n const staleAuth = JSON.stringify({\n \"openai-codex\": {\n type: \"oauth\",\n access: \"stale-fixture-access\",\n expires: 1790000000,\n accountId: \"fixture-chatgpt-account-id\",\n },\n });\n const fetch = vi.fn();\n const report = await probeOpenAiCapacity({\n checkedAt,\n env: { HOME: \"/users/test\" },\n readFile: async () => staleAuth,\n fetch,\n });\n expect(fetch).not.toHaveBeenCalled();\n expect(report.available).toBe(\"unknown\");\n });\n\n it(\"falls back to the JWT exp when expires metadata is missing\", async () => {\n const expiredJwtAuth = JSON.stringify({\n \"openai-codex\": {\n type: \"oauth\",\n access: \"h.eyJleHAiOjE3ODkwMDAwMDB9.s\",\n accountId: \"fixture-chatgpt-account-id\",\n },\n });\n const noFetch = vi.fn();\n await expect(\n probeOpenAiCapacity({\n checkedAt,\n env: { HOME: \"/users/test\" },\n readFile: async () => expiredJwtAuth,\n fetch: noFetch,\n }),\n ).resolves.toMatchObject({ authenticated: true, available: \"unknown\" });\n expect(noFetch).not.toHaveBeenCalled();\n\n const futureJwtAuth = JSON.stringify({\n \"openai-codex\": {\n type: \"oauth\",\n access: \"h.eyJleHAiOjE3OTMwMDAwMDB9.s\",\n accountId: \"fixture-chatgpt-account-id\",\n },\n });\n const fetch = vi.fn(async () => whamResponse());\n await expect(\n probeOpenAiCapacity({\n checkedAt,\n env: { HOME: \"/users/test\" },\n readFile: async () => futureJwtAuth,\n fetch,\n }),\n ).resolves.toMatchObject({ authenticated: true, available: \"yes\" });\n expect(fetch).toHaveBeenCalledOnce();\n });\n\n it(\"treats unparseable JWT payloads as unknown expiry and probes anyway\", async () => {\n const auth = JSON.stringify({\n \"openai-codex\": {\n type: \"oauth\",\n access: \"fixture-header.bm90LWpzb24.fixture-signature\",\n accountId: \"fixture-chatgpt-account-id\",\n },\n });\n const fetch = vi.fn(async () => whamResponse());\n await expect(\n probeOpenAiCapacity({\n checkedAt,\n env: { HOME: \"/users/test\" },\n readFile: async () => auth,\n fetch,\n }),\n ).resolves.toMatchObject({ authenticated: true, available: \"yes\" });\n expect(fetch).toHaveBeenCalledOnce();\n });\n\n it(\"treats non-JWT and exp-less access tokens as unknown expiry and probes anyway\", async () => {\n for (const access of [\"opaque-fixture-access\", \"h.eyJzdWIiOiJ4In0.s\"]) {\n const auth = JSON.stringify({\n \"openai-codex\": {\n type: \"oauth\",\n access,\n accountId: \"fixture-chatgpt-account-id\",\n },\n });\n const fetch = vi.fn(async () => whamResponse());\n await expect(\n probeOpenAiCapacity({\n checkedAt,\n env: { HOME: \"/users/test\" },\n readFile: async () => auth,\n fetch,\n }),\n ).resolves.toMatchObject({ authenticated: true, available: \"yes\" });\n expect(fetch).toHaveBeenCalledOnce();\n }\n });\n\n it(\"reports unknown availability when wham returns no usable windows\", async () => {\n const report = await probeOpenAiCapacity({\n checkedAt,\n env: { HOME: \"/users/test\" },\n readFile: async () => freshOauth(),\n fetch: vi.fn(async () => new Response(\"{}\", { status: 200 })),\n });\n expect(report).toMatchObject({ authenticated: true, available: \"unknown\", windows: [] });\n });\n\n it(\"reports unauthenticated when wham rejects the OAuth token\", async () => {\n for (const status of [401, 403]) {\n const report = await probeOpenAiCapacity({\n checkedAt,\n env: { HOME: \"/users/test\" },\n readFile: async () => freshOauth(),\n fetch: vi.fn(async () => new Response(\"denied\", { status })),\n });\n expect(report).toMatchObject({\n authenticated: false,\n available: \"unknown\",\n windows: [],\n creditsRemaining: null,\n });\n }\n });\n\n it(\"throws sanitized errors for transport failures without leaking tokens\", async () => {\n const probe = (fetch: unknown) =>\n probeOpenAiCapacity({\n checkedAt,\n env: { HOME: \"/users/test\" },\n readFile: async () => freshOauth(),\n fetch: fetch as typeof globalThis.fetch,\n timeoutMs: 10,\n });\n await expect(\n probe(\n (_url: string, init: RequestInit) =>\n new Promise((_resolve, reject) =>\n init.signal?.addEventListener(\"abort\", () => reject(new Error(\"aborted\"))),\n ),\n ),\n ).rejects.toThrow(\"OpenAI usage request failed\");\n await expect(probe(vi.fn(async () => new Response(\"boom\", { status: 503 })))).rejects.toThrow(\n \"OpenAI usage request failed: HTTP 503\",\n );\n await expect(\n probe(vi.fn(async () => new Response(\"not-json\", { status: 200 }))),\n ).rejects.toThrow(\"OpenAI usage response is not valid JSON\");\n try {\n await probe(vi.fn(async () => new Response(\"boom\", { status: 503 })));\n } catch (error) {\n expect(String(error)).not.toContain(\"fixture-signature\");\n expect(String(error)).not.toContain(\"fixture-chatgpt-account-id\");\n }\n });\n});\n\ndescribe(\"OpenAI usage mapping\", () => {\n it(\"sums per-model input and output tokens into UTC day buckets\", async () => {\n const buckets = parseOpenAiUsage(JSON.parse(await fixture(\"openai-usage-completions.json\")));\n expect(buckets).toEqual([\n { startMs: 1790380800000, tokens: 2000 },\n { startMs: 1790294400000, tokens: 5000 },\n { startMs: 1789862400000, tokens: 1000 },\n { startMs: 1789689600000, tokens: 19998 },\n ]);\n });\n\n it(\"keeps empty buckets and skips entries it cannot place in time\", () => {\n expect(\n parseOpenAiUsage({\n object: \"list\",\n data: [\n null,\n 42,\n { aggregation_timestamp: 1790380800 },\n { aggregation_timestamp: \"yesterday\", results: [] },\n { aggregation_timestamp: 1790294400, results: null },\n ],\n }),\n ).toEqual([\n { startMs: 1790380800000, tokens: 0 },\n { startMs: 1790294400000, tokens: 0 },\n ]);\n });\n\n it(\"rejects responses without a usage data array\", () => {\n expect(() => parseOpenAiUsage({ object: \"list\" })).toThrow(\"Invalid OpenAI usage response\");\n expect(() => parseOpenAiUsage({ data: {} })).toThrow(\"Invalid OpenAI usage response\");\n });\n\n it(\"builds today and 7-day windows without faked limits\", async () => {\n const buckets = [\n ...parseOpenAiUsage(JSON.parse(await fixture(\"openai-usage-completions.json\"))),\n ...parseOpenAiUsage(JSON.parse(await fixture(\"openai-usage-responses.json\"))),\n ];\n const report = buildOpenAiReport(buckets, checkedAt);\n expect(report).toMatchObject({\n harness: \"pi\",\n provider: \"openai\",\n generatedAt: checkedAt,\n authenticated: true,\n available: \"yes\",\n creditsRemaining: null,\n });\n expect(report.windows).toEqual([\n {\n id: \"openai:tokens:today\",\n label: \"Tokens · today (UTC)\",\n limitType: \"TOKENS_LIMIT\",\n durationMinutes: 1440,\n usedPercent: null,\n resetsAt: \"2026-09-27T00:00:00.000Z\",\n total: null,\n current: 2000,\n remaining: null,\n },\n {\n id: \"openai:tokens:week\",\n label: \"Tokens · 7 days\",\n limitType: \"TOKENS_LIMIT\",\n durationMinutes: 10080,\n usedPercent: null,\n resetsAt: null,\n total: null,\n current: 13000,\n remaining: null,\n },\n ]);\n });\n\n it(\"reports zero usage for days without buckets\", () => {\n const report = buildOpenAiReport([], checkedAt);\n expect(report.windows.map((window) => window.current)).toEqual([0, 0]);\n expect(report.windows[0].resetsAt).toBe(\"2026-09-27T00:00:00.000Z\");\n });\n});\n\ndescribe(\"OpenAI request\", () => {\n it(\"GETs both usage endpoints for the trailing week with only Bearer authentication\", async () => {\n const fetch = vi.fn(async (url: string) =>\n url.includes(\"/responses?\")\n ? new Response(await fixture(\"openai-usage-responses.json\"), {\n status: 200,\n })\n : new Response(await fixture(\"openai-usage-completions.json\"), {\n status: 200,\n }),\n );\n const report = await probeOpenAiCapacity({\n checkedAt,\n env: { OPENAI_API_KEY: \"request-test-key\" },\n fetch,\n });\n const query = \"start_time=1789812000&end_time=1790416800\";\n expect(fetch).toHaveBeenCalledTimes(2);\n expect(fetch).toHaveBeenCalledWith(\n `https://api.openai.com/v1/organization/usage/completions?${query}`,\n expect.objectContaining({\n method: \"GET\",\n headers: { Authorization: \"Bearer request-test-key\" },\n }),\n );\n expect(fetch).toHaveBeenCalledWith(\n `https://api.openai.com/v1/organization/usage/responses?${query}`,\n expect.objectContaining({\n headers: { Authorization: \"Bearer request-test-key\" },\n }),\n );\n expect(report.windows.map((window) => window.current)).toEqual([2000, 13000]);\n expect(JSON.stringify(report)).not.toContain(\"request-test-key\");\n });\n\n it(\"reports unauthenticated for rejected keys\", async () => {\n const report = await probeOpenAiCapacity({\n checkedAt,\n env: { OPENAI_API_KEY: \"rejected-key\" },\n fetch: vi.fn(async () => new Response(\"denied\", { status: 401 })),\n });\n expect(report).toMatchObject({\n authenticated: false,\n available: \"unknown\",\n windows: [],\n creditsRemaining: null,\n });\n });\n\n it(\"degrades to authentication-only when usage requires an admin key\", async () => {\n const fetch = vi.fn(async (url: string) =>\n url.endsWith(\"/models\")\n ? new Response(\"{}\", { status: 200 })\n : new Response(\"forbidden\", { status: 403 }),\n );\n const report = await probeOpenAiCapacity({\n checkedAt,\n env: { OPENAI_API_KEY: \"member-key\" },\n fetch,\n });\n expect(fetch).toHaveBeenCalledWith(\n \"https://api.openai.com/v1/models\",\n expect.objectContaining({\n headers: { Authorization: \"Bearer member-key\" },\n }),\n );\n expect(report).toMatchObject({\n authenticated: true,\n available: \"unknown\",\n windows: [],\n });\n });\n\n it(\"falls back to unauthenticated when the degraded check rejects the key\", async () => {\n const fetch = vi.fn(async (url: string) =>\n url.endsWith(\"/models\")\n ? new Response(\"denied\", { status: 401 })\n : new Response(\"forbidden\", { status: 403 }),\n );\n const report = await probeOpenAiCapacity({\n checkedAt,\n env: { OPENAI_API_KEY: \"revoked-key\" },\n fetch,\n });\n expect(report).toMatchObject({ authenticated: false, windows: [] });\n });\n\n it(\"sanitizes network failures on the platform key path\", async () => {\n await expect(\n probeOpenAiCapacity({\n checkedAt,\n env: { OPENAI_API_KEY: \"network-key\" },\n fetch: vi.fn(async () => Promise.reject(new Error(\"ECONNRESET secret\"))),\n }),\n ).rejects.toThrow(\"OpenAI usage request failed\");\n await expect(\n probeOpenAiCapacity({\n checkedAt,\n env: { OPENAI_API_KEY: \"network-key\" },\n fetch: vi.fn(async (url: string) =>\n url.endsWith(\"/models\")\n ? Promise.reject(new Error(\"ECONNRESET secret\"))\n : new Response(\"forbidden\", { status: 403 }),\n ),\n }),\n ).rejects.toThrow(\"OpenAI usage request failed\");\n });\n\n it(\"rejects other statuses and malformed JSON responses with sanitized errors\", async () => {\n await expect(\n probeOpenAiCapacity({\n checkedAt,\n env: { OPENAI_API_KEY: \"never-expose-this-key\" },\n fetch: vi.fn(async () => new Response(\"private body\", { status: 503 })),\n }),\n ).rejects.toThrow(\"OpenAI usage request failed: HTTP 503\");\n await expect(\n probeOpenAiCapacity({\n checkedAt,\n env: { OPENAI_API_KEY: \"never-expose-this-key\" },\n fetch: vi.fn(async () => new Response(\"not-json\", { status: 200 })),\n }),\n ).rejects.toThrow(\"OpenAI usage response is not valid JSON\");\n });\n});\n"],"names":["readFile","readFileSync","fileURLToPath","describe","expect","it","vi","buildOpenAiReport","parseOpenAiUsage","probeOpenAiCapacity","resolveOpenAiApiKey","resolveOpenAiCredential","checkedAt","fixture","name","URL","url","fixtureString","readAuth","fn","env","OPENAI_API_KEY","resolves","toBe","not","toHaveBeenCalled","HOME","toHaveBeenCalledWith","each","Promise","reject","Object","assign","Error","code","JSON","stringify","openai","type","key","_name","rejects","toThrow","oauthFixture","bothCredentials","access","refresh","expires","accountId","toEqual","kind","expiresMs","auth","toMatchObject","freshOauth","whamResponse","Response","status","fetch","report","objectContaining","method","headers","Authorization","harness","provider","generatedAt","authenticated","available","creditsRemaining","windows","map","window","id","toContain","staleAuth","expiredJwtAuth","noFetch","futureJwtAuth","toHaveBeenCalledOnce","probe","timeoutMs","_url","init","_resolve","signal","addEventListener","error","String","buckets","parse","startMs","tokens","object","data","aggregation_timestamp","results","label","limitType","durationMinutes","usedPercent","resetsAt","total","current","remaining","includes","query","toHaveBeenCalledTimes","endsWith"],"mappings":"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"}
@@ -0,0 +1,144 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { fileURLToPath } from "node:url";
3
+ import { describe, expect, it } from "vitest";
4
+ import { parseWhamUsage, toRateWindow } from "../../capacity/wham.js";
5
+ const fixture = (name)=>readFile(fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)), "utf8");
6
+ describe("wham usage parsing", ()=>{
7
+ it("maps primary and secondary rate windows onto session and weekly", async ()=>{
8
+ const snapshot = parseWhamUsage(JSON.parse(await fixture("wham-usage.json")));
9
+ expect(snapshot.windows).toEqual([
10
+ {
11
+ id: "session",
12
+ label: "Session",
13
+ durationMinutes: 300,
14
+ usedPercent: 12.5,
15
+ resetsAt: "2026-09-27T13:52:07.000Z"
16
+ },
17
+ {
18
+ id: "weekly",
19
+ label: "Weekly",
20
+ durationMinutes: 10080,
21
+ usedPercent: 43.2,
22
+ resetsAt: "2026-10-04T08:52:07.000Z"
23
+ }
24
+ ]);
25
+ expect(snapshot.creditsRemaining).toBe(4.5);
26
+ });
27
+ it("accepts epoch seconds and ISO strings for reset_at", ()=>{
28
+ expect(toRateWindow({
29
+ used_percent: 1,
30
+ reset_at: 1_787_220_000
31
+ }, "a", "A")?.resetsAt).toBe("2026-08-20T10:00:00.000Z");
32
+ expect(toRateWindow({
33
+ used_percent: 1,
34
+ reset_at: "2026-08-20T10:00:00Z"
35
+ }, "a", "A")?.resetsAt).toBe("2026-08-20T10:00:00.000Z");
36
+ expect(toRateWindow({
37
+ used_percent: 1,
38
+ reset_at: "nope"
39
+ }, "a", "A")?.resetsAt).toBeNull();
40
+ });
41
+ it("maps additional_rate_limits into scoped windows and tolerates non-array values", ()=>{
42
+ const scoped = parseWhamUsage({
43
+ rate_limit: {},
44
+ additional_rate_limits: [
45
+ {
46
+ limit_name: "code-review",
47
+ rate_limit: {
48
+ primary_window: {
49
+ used_percent: 10,
50
+ limit_window_seconds: 3600,
51
+ reset_at: 100
52
+ },
53
+ secondary_window: {
54
+ used_percent: 20,
55
+ limit_window_seconds: 7200,
56
+ reset_at: 200
57
+ }
58
+ }
59
+ }
60
+ ]
61
+ });
62
+ expect(scoped.windows.map((window)=>window.id)).toEqual([
63
+ "code-review:primary",
64
+ "code-review:secondary"
65
+ ]);
66
+ expect(parseWhamUsage({
67
+ additional_rate_limits: null
68
+ }).windows).toEqual([]);
69
+ });
70
+ it("falls back to positional ids and inline windows for degraded extra limits", ()=>{
71
+ const scoped = parseWhamUsage({
72
+ additional_rate_limits: [
73
+ null,
74
+ 42,
75
+ {
76
+ primary_window: {
77
+ used_percent: 5
78
+ }
79
+ },
80
+ {
81
+ limit_name: "not/safe",
82
+ primary_window: {
83
+ used_percent: 6
84
+ }
85
+ },
86
+ {
87
+ limit_name: "token-123456",
88
+ primary_window: {
89
+ used_percent: 7
90
+ }
91
+ },
92
+ {
93
+ limit_name: "ok",
94
+ secondary_window: {
95
+ used_percent: 8
96
+ }
97
+ }
98
+ ]
99
+ });
100
+ expect(scoped.windows.map((window)=>window.id)).toEqual([
101
+ "extra-3:primary",
102
+ "extra-4:primary",
103
+ "extra-5:primary",
104
+ "ok:secondary"
105
+ ]);
106
+ });
107
+ it("parses non-object payloads into an empty snapshot", ()=>{
108
+ expect(parseWhamUsage(null)).toEqual({
109
+ windows: [],
110
+ creditsRemaining: null
111
+ });
112
+ expect(parseWhamUsage(42)).toEqual({
113
+ windows: [],
114
+ creditsRemaining: null
115
+ });
116
+ });
117
+ it("never invents usage for windows it cannot parse", ()=>{
118
+ const snapshot = parseWhamUsage({
119
+ rate_limit: {
120
+ primary_window: {
121
+ limit_window_seconds: 18000
122
+ },
123
+ secondary_window: null
124
+ }
125
+ });
126
+ expect(snapshot.windows).toEqual([
127
+ {
128
+ id: "session",
129
+ label: "Session",
130
+ durationMinutes: 300,
131
+ usedPercent: null,
132
+ resetsAt: null
133
+ }
134
+ ]);
135
+ expect(parseWhamUsage({}).creditsRemaining).toBeNull();
136
+ expect(parseWhamUsage({
137
+ credits: {
138
+ balance: "nan"
139
+ }
140
+ }).creditsRemaining).toBeNull();
141
+ });
142
+ });
143
+
144
+ //# sourceMappingURL=wham.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/__tests__/capacity/wham.test.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { describe, expect, it } from \"vitest\";\nimport { parseWhamUsage, toRateWindow } from \"../../capacity/wham.js\";\n\nconst fixture = (name: string) =>\n readFile(fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)), \"utf8\");\n\ndescribe(\"wham usage parsing\", () => {\n it(\"maps primary and secondary rate windows onto session and weekly\", async () => {\n const snapshot = parseWhamUsage(JSON.parse(await fixture(\"wham-usage.json\")));\n expect(snapshot.windows).toEqual([\n {\n id: \"session\",\n label: \"Session\",\n durationMinutes: 300,\n usedPercent: 12.5,\n resetsAt: \"2026-09-27T13:52:07.000Z\",\n },\n {\n id: \"weekly\",\n label: \"Weekly\",\n durationMinutes: 10080,\n usedPercent: 43.2,\n resetsAt: \"2026-10-04T08:52:07.000Z\",\n },\n ]);\n expect(snapshot.creditsRemaining).toBe(4.5);\n });\n\n it(\"accepts epoch seconds and ISO strings for reset_at\", () => {\n expect(toRateWindow({ used_percent: 1, reset_at: 1_787_220_000 }, \"a\", \"A\")?.resetsAt).toBe(\n \"2026-08-20T10:00:00.000Z\",\n );\n expect(\n toRateWindow({ used_percent: 1, reset_at: \"2026-08-20T10:00:00Z\" }, \"a\", \"A\")?.resetsAt,\n ).toBe(\"2026-08-20T10:00:00.000Z\");\n expect(toRateWindow({ used_percent: 1, reset_at: \"nope\" }, \"a\", \"A\")?.resetsAt).toBeNull();\n });\n\n it(\"maps additional_rate_limits into scoped windows and tolerates non-array values\", () => {\n const scoped = parseWhamUsage({\n rate_limit: {},\n additional_rate_limits: [\n {\n limit_name: \"code-review\",\n rate_limit: {\n primary_window: { used_percent: 10, limit_window_seconds: 3600, reset_at: 100 },\n secondary_window: { used_percent: 20, limit_window_seconds: 7200, reset_at: 200 },\n },\n },\n ],\n });\n expect(scoped.windows.map((window) => window.id)).toEqual([\n \"code-review:primary\",\n \"code-review:secondary\",\n ]);\n expect(parseWhamUsage({ additional_rate_limits: null }).windows).toEqual([]);\n });\n\n it(\"falls back to positional ids and inline windows for degraded extra limits\", () => {\n const scoped = parseWhamUsage({\n additional_rate_limits: [\n null,\n 42,\n { primary_window: { used_percent: 5 } },\n { limit_name: \"not/safe\", primary_window: { used_percent: 6 } },\n { limit_name: \"token-123456\", primary_window: { used_percent: 7 } },\n { limit_name: \"ok\", secondary_window: { used_percent: 8 } },\n ],\n });\n expect(scoped.windows.map((window) => window.id)).toEqual([\n \"extra-3:primary\",\n \"extra-4:primary\",\n \"extra-5:primary\",\n \"ok:secondary\",\n ]);\n });\n\n it(\"parses non-object payloads into an empty snapshot\", () => {\n expect(parseWhamUsage(null)).toEqual({ windows: [], creditsRemaining: null });\n expect(parseWhamUsage(42)).toEqual({ windows: [], creditsRemaining: null });\n });\n\n it(\"never invents usage for windows it cannot parse\", () => {\n const snapshot = parseWhamUsage({\n rate_limit: {\n primary_window: { limit_window_seconds: 18000 },\n secondary_window: null,\n },\n });\n expect(snapshot.windows).toEqual([\n {\n id: \"session\",\n label: \"Session\",\n durationMinutes: 300,\n usedPercent: null,\n resetsAt: null,\n },\n ]);\n expect(parseWhamUsage({}).creditsRemaining).toBeNull();\n expect(parseWhamUsage({ credits: { balance: \"nan\" } }).creditsRemaining).toBeNull();\n });\n});\n"],"names":["readFile","fileURLToPath","describe","expect","it","parseWhamUsage","toRateWindow","fixture","name","URL","url","snapshot","JSON","parse","windows","toEqual","id","label","durationMinutes","usedPercent","resetsAt","creditsRemaining","toBe","used_percent","reset_at","toBeNull","scoped","rate_limit","additional_rate_limits","limit_name","primary_window","limit_window_seconds","secondary_window","map","window","credits","balance"],"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: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/__tests__/providers/copilot/CopilotAdapter.test.ts"],"sourcesContent":["/**\n * Tests for CopilotAdapter\n */\n\nimport type { MockedFunction } from \"vitest\";\nimport * as fs from \"fs\";\nimport * as os from \"os\";\nimport * as path from \"path\";\n\nimport { CopilotAdapter } from \"../../../providers/copilot/CopilotAdapter.js\";\nimport type { ProcessInfo } from \"../../../adapters/AgentAdapter.js\";\nimport { AgentStatus } from \"../../../adapters/AgentAdapter.js\";\nimport {\n listAgentProcesses,\n enrichProcesses,\n captureProcessSnapshot,\n} from \"../../../utils/process.js\";\nimport { generateAgentName } from \"../../../utils/matching.js\";\nimport { AgentRegistry } from \"../../../utils/AgentRegistry.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/matching.js\", () => ({\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 mockedGenerateAgentName = generateAgentName as MockedFunction<typeof generateAgentName>;\n\ndescribe(\"CopilotAdapter\", () => {\n let adapter: CopilotAdapter;\n let tmpDir: string;\n let sessionStateDir: string;\n\n beforeEach(() => {\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), \"copilot-test-\"));\n sessionStateDir = path.join(tmpDir, \"session-state\");\n fs.mkdirSync(sessionStateDir, { recursive: true });\n adapter = new CopilotAdapter(undefined, { sessionStateDir });\n\n mockedListAgentProcesses.mockReset();\n mockedEnrichProcesses.mockReset();\n mockedCaptureProcessSnapshot.mockReset();\n mockedGenerateAgentName.mockReset();\n mockedEnrichProcesses.mockImplementation((procs) => procs);\n // Compatibility shim for standalone adapter discovery; the manager captures once and slices by name.\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 fs.rmSync(tmpDir, { recursive: true, force: true });\n });\n\n function writeSession(\n sessionId: string,\n options: {\n events?: Array<object | string>;\n workspace?: Record<string, string>;\n lockPid?: number | string;\n },\n ): string {\n const sessionDir = path.join(sessionStateDir, sessionId);\n fs.mkdirSync(sessionDir, { recursive: true });\n\n if (options.events) {\n const lines = options.events.map((entry) =>\n typeof entry === \"string\" ? entry : JSON.stringify(entry),\n );\n fs.writeFileSync(path.join(sessionDir, \"events.jsonl\"), lines.join(\"\\n\"));\n }\n\n if (options.workspace) {\n const lines = Object.entries(options.workspace).map(([key, value]) => `${key}: ${value}`);\n fs.writeFileSync(path.join(sessionDir, \"workspace.yaml\"), lines.join(\"\\n\"));\n }\n\n if (options.lockPid !== undefined) {\n fs.writeFileSync(path.join(sessionDir, `inuse.${options.lockPid}.lock`), \"\");\n }\n\n return sessionDir;\n }\n\n function sessionStart(\n sessionId: string,\n cwd: string,\n startTime = \"2026-06-09T09:50:00.000Z\",\n ): object {\n return {\n type: \"session.start\",\n data: {\n sessionId,\n startTime,\n context: {\n cwd,\n gitRoot: cwd,\n branch: \"main\",\n },\n },\n timestamp: startTime,\n };\n }\n\n describe(\"initialization\", () => {\n it(\"exposes copilot type\", () => {\n expect(adapter.type).toBe(\"copilot\");\n });\n });\n\n describe(\"canHandle\", () => {\n it(\"returns true for copilot commands\", () => {\n expect(adapter.canHandle({ pid: 1, command: \"copilot\", cwd: \"/repo\", tty: \"ttys001\" })).toBe(\n true,\n );\n });\n\n it(\"returns true for full-path Homebrew copilot commands\", () => {\n expect(\n adapter.canHandle({\n pid: 2,\n command: \"/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot\",\n cwd: \"/repo\",\n tty: \"ttys002\",\n }),\n ).toBe(true);\n });\n\n it(\"returns true for copilot.exe commands\", () => {\n expect(\n adapter.canHandle({\n pid: 3,\n command: \"/usr/bin/copilot.exe\",\n cwd: \"/repo\",\n tty: \"ttys003\",\n }),\n ).toBe(true);\n });\n\n it(\"returns false when copilot appears only in an argument\", () => {\n expect(\n adapter.canHandle({\n pid: 4,\n command: \"node /repo/copilot-plugin/index.js\",\n cwd: \"/repo\",\n tty: \"ttys004\",\n }),\n ).toBe(false);\n });\n });\n\n describe(\"detectAgents\", () => {\n it(\"returns empty list when no copilot process is running\", async () => {\n mockedListAgentProcesses.mockReturnValue([]);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toEqual([]);\n expect(mockedListAgentProcesses).toHaveBeenCalledWith(\"copilot\");\n });\n\n it(\"maps matching inuse lock and events to an active agent\", async () => {\n const processes: ProcessInfo[] = [\n {\n pid: 14096,\n command: \"/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot\",\n cwd: \"/repo\",\n tty: \"ttys001\",\n },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession(\"sess-a\", {\n lockPid: 14096,\n events: [\n sessionStart(\"sess-a\", \"/repo\", \"2026-06-09T09:50:00.000Z\"),\n {\n type: \"user.message\",\n data: { content: \"Build the Copilot adapter\" },\n timestamp: new Date().toISOString(),\n },\n {\n type: \"assistant.message\",\n data: { content: \"I will inspect the code.\" },\n timestamp: new Date().toISOString(),\n },\n ],\n workspace: {\n id: \"sess-a\",\n cwd: \"/fallback\",\n name: \"Fallback Name\",\n created_at: \"2026-06-09T09:49:00.000Z\",\n updated_at: \"2026-06-09T09:51:00.000Z\",\n },\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: \"copilot\",\n status: AgentStatus.WAITING,\n pid: 14096,\n projectPath: \"/repo\",\n sessionId: \"sess-a\",\n summary: \"Build the Copilot adapter\",\n sessionFilePath: path.join(sessionStateDir, \"sess-a\", \"events.jsonl\"),\n });\n });\n\n it(\"ignores invalid and unmatched lock PIDs\", async () => {\n const processes: ProcessInfo[] = [\n { pid: 100, command: \"copilot\", cwd: \"/repo\", tty: \"ttys001\" },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession(\"invalid\", {\n lockPid: \"not-a-pid\",\n events: [sessionStart(\"invalid\", \"/invalid\")],\n });\n writeSession(\"unmatched\", {\n lockPid: 999999,\n events: [sessionStart(\"unmatched\", \"/unmatched\")],\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 100,\n sessionId: \"pid-100\",\n summary: \"Copilot process running\",\n });\n });\n\n it(\"falls back to process-only agent when no session lock matches\", async () => {\n const processes: ProcessInfo[] = [\n { pid: 200, command: \"copilot\", cwd: \"/repo-b\", tty: \"ttys002\" },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: \"copilot\",\n status: AgentStatus.RUNNING,\n pid: 200,\n projectPath: \"/repo-b\",\n sessionId: \"pid-200\",\n summary: \"Copilot process running\",\n });\n });\n\n it(\"suppresses wrapper process-only agents before the session lock exists\", async () => {\n const processes: ProcessInfo[] = [\n { pid: 86800, command: \"copilot\", cwd: \"/repo\", tty: \"ttys001\", ppid: 84174 },\n {\n pid: 86810,\n command: \"/custom/install/copilot\",\n cwd: \"/repo\",\n tty: \"ttys001\",\n ppid: 86800,\n },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 86810,\n sessionId: \"pid-86810\",\n summary: \"Copilot process running\",\n });\n });\n\n it(\"carries the managed wrapper name to a process-only child before the session lock exists\", async () => {\n const registry = new AgentRegistry(path.join(tmpDir, \"agents.json\"));\n adapter = new CopilotAdapter(registry, { sessionStateDir });\n const processes: ProcessInfo[] = [\n { pid: 86800, command: \"copilot\", cwd: \"/repo\", tty: \"ttys001\", ppid: 84174 },\n {\n pid: 86810,\n command: \"/custom/install/copilot\",\n cwd: \"/repo\",\n tty: \"ttys001\",\n ppid: 86800,\n },\n ];\n registry.register({\n name: \"copilot-started\",\n type: \"copilot\",\n pid: 86800,\n runtime: \"tmux\",\n runtimeRef: { session: \"copilot-started\" },\n cwd: \"/repo\",\n startedAt: \"2026-06-13T19:15:16.211Z\",\n sessionId: \"pid-86800\",\n sessionFilePath: \"\",\n });\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n name: \"copilot-started\",\n pid: 86810,\n sessionId: \"pid-86810\",\n });\n });\n\n it(\"does not add duplicate process-only agent for wrapper process in the same terminal\", async () => {\n const processes: ProcessInfo[] = [\n { pid: 14095, command: \"copilot\", cwd: \"/repo\", tty: \"ttys001\" },\n {\n pid: 14096,\n command: \"/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot\",\n cwd: \"/repo\",\n tty: \"ttys001\",\n },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession(\"sess-wrapper\", {\n lockPid: 14096,\n events: [\n sessionStart(\"sess-wrapper\", \"/repo\", \"2026-06-09T09:50:00.000Z\"),\n { type: \"user.message\", data: { content: \"hello\" }, timestamp: new Date().toISOString() },\n ],\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 14096,\n sessionId: \"sess-wrapper\",\n });\n });\n\n it(\"carries the managed wrapper name to a lock-backed child process\", async () => {\n const registry = new AgentRegistry(path.join(tmpDir, \"agents.json\"));\n adapter = new CopilotAdapter(registry, { sessionStateDir });\n const processes: ProcessInfo[] = [\n { pid: 14095, command: \"copilot\", cwd: \"/repo\", tty: \"ttys001\", ppid: 84174 },\n {\n pid: 14096,\n command: \"/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot\",\n cwd: \"/repo\",\n tty: \"ttys001\",\n ppid: 14095,\n },\n ];\n registry.register({\n name: \"copilot-started\",\n type: \"copilot\",\n pid: 14095,\n runtime: \"tmux\",\n runtimeRef: { session: \"copilot-started\" },\n cwd: \"/repo\",\n startedAt: \"2026-06-13T19:15:16.211Z\",\n sessionId: \"pid-14095\",\n sessionFilePath: \"\",\n });\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession(\"sess-wrapper\", {\n lockPid: 14096,\n events: [\n sessionStart(\"sess-wrapper\", \"/repo\", \"2026-06-09T09:50:00.000Z\"),\n { type: \"user.message\", data: { content: \"hello\" }, timestamp: new Date().toISOString() },\n ],\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n name: \"copilot-started\",\n pid: 14096,\n sessionId: \"sess-wrapper\",\n });\n });\n\n it(\"uses workspace metadata when events are missing\", async () => {\n const processes: ProcessInfo[] = [\n { pid: 300, command: \"copilot\", cwd: \"/proc-cwd\", tty: \"ttys003\" },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession(\"workspace-only\", {\n lockPid: 300,\n workspace: {\n id: \"workspace-only\",\n cwd: \"/workspace-cwd\",\n name: \"Workspace Session\",\n created_at: \"2026-06-09T09:00:00.000Z\",\n updated_at: \"2026-06-09T09:10:00.000Z\",\n },\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 300,\n projectPath: \"/workspace-cwd\",\n sessionId: \"workspace-only\",\n summary: \"Workspace Session\",\n });\n });\n });\n\n describe(\"getConversation\", () => {\n it(\"parses user and assistant message events\", () => {\n const sessionDir = writeSession(\"conv\", {\n events: [\n sessionStart(\"conv\", \"/repo\"),\n {\n type: \"user.message\",\n data: { content: \"hello\" },\n timestamp: \"2026-06-09T09:50:01.000Z\",\n },\n {\n type: \"assistant.message\",\n data: { content: \"Hi there\" },\n timestamp: \"2026-06-09T09:50:02.000Z\",\n },\n ],\n });\n\n const messages = adapter.getConversation(path.join(sessionDir, \"events.jsonl\"));\n\n expect(messages).toEqual([\n { role: \"user\", content: \"hello\", timestamp: \"2026-06-09T09:50:01.000Z\" },\n { role: \"assistant\", content: \"Hi there\", timestamp: \"2026-06-09T09:50:02.000Z\" },\n ]);\n });\n\n it(\"includes system/info/warning messages only in verbose mode\", () => {\n const sessionDir = writeSession(\"verbose\", {\n events: [\n {\n type: \"session.warning\",\n data: { message: \"MCP failed\" },\n timestamp: \"2026-06-09T09:50:01.000Z\",\n },\n {\n type: \"tool.execution_complete\",\n data: { result: { content: \"Tool result\" } },\n timestamp: \"2026-06-09T09:50:02.000Z\",\n },\n ],\n });\n const filePath = path.join(sessionDir, \"events.jsonl\");\n\n expect(adapter.getConversation(filePath)).toEqual([]);\n expect(adapter.getConversation(filePath, { verbose: true })).toEqual([\n { role: \"system\", content: \"MCP failed\", timestamp: \"2026-06-09T09:50:01.000Z\" },\n { role: \"system\", content: \"Tool result\", timestamp: \"2026-06-09T09:50:02.000Z\" },\n ]);\n });\n\n it(\"skips malformed JSONL lines and missing text\", () => {\n const sessionDir = writeSession(\"malformed\", {\n events: [\n \"not json\",\n {\n type: \"user.message\",\n data: { content: \"valid\" },\n timestamp: \"2026-06-09T09:50:01.000Z\",\n },\n { type: \"assistant.message\", data: {}, timestamp: \"2026-06-09T09:50:02.000Z\" },\n ],\n });\n\n const messages = adapter.getConversation(path.join(sessionDir, \"events.jsonl\"));\n\n expect(messages).toEqual([\n { role: \"user\", content: \"valid\", timestamp: \"2026-06-09T09:50:01.000Z\" },\n ]);\n });\n\n it(\"returns empty array for missing file\", () => {\n expect(adapter.getConversation(path.join(tmpDir, \"missing.jsonl\"))).toEqual([]);\n });\n });\n\n describe(\"listSessions\", () => {\n it(\"returns historical sessions without active locks\", async () => {\n writeSession(\"history-a\", {\n events: [\n sessionStart(\"history-a\", \"/repo-a\", \"2026-06-09T09:00:00.000Z\"),\n {\n type: \"user.message\",\n data: { content: \"first user\" },\n timestamp: \"2026-06-09T09:00:01.000Z\",\n },\n {\n type: \"assistant.message\",\n data: { content: \"answer\" },\n timestamp: \"2026-06-09T09:00:02.000Z\",\n },\n ],\n });\n writeSession(\"history-b\", {\n events: [\n sessionStart(\"history-b\", \"/repo-b\", \"2026-06-09T10:00:00.000Z\"),\n {\n type: \"user.message\",\n data: { content: \"other user\" },\n timestamp: \"2026-06-09T10:00:01.000Z\",\n },\n ],\n });\n\n const sessions = await adapter.listSessions();\n\n expect(sessions).toHaveLength(2);\n const byId = Object.fromEntries(sessions.map((session) => [session.sessionId, session]));\n expect(byId[\"history-a\"]).toMatchObject({\n type: \"copilot\",\n cwd: \"/repo-a\",\n firstUserMessage: \"first user\",\n sessionFilePath: path.join(sessionStateDir, \"history-a\", \"events.jsonl\"),\n });\n expect(byId[\"history-b\"]).toMatchObject({\n type: \"copilot\",\n cwd: \"/repo-b\",\n firstUserMessage: \"other user\",\n });\n });\n\n it(\"applies strict cwd filter\", async () => {\n writeSession(\"keep\", { events: [sessionStart(\"keep\", \"/repo\")] });\n writeSession(\"drop\", { events: [sessionStart(\"drop\", \"/other\")] });\n\n const sessions = await adapter.listSessions({ cwd: \"/repo\" });\n\n expect(sessions).toHaveLength(1);\n expect(sessions[0].sessionId).toBe(\"keep\");\n });\n\n it(\"uses workspace fallback for historical sessions without events\", async () => {\n writeSession(\"workspace-history\", {\n workspace: {\n id: \"workspace-history\",\n cwd: \"/workspace-repo\",\n name: \"Workspace History\",\n created_at: \"2026-06-09T08:00:00.000Z\",\n updated_at: \"2026-06-09T08:10:00.000Z\",\n },\n });\n\n const sessions = await adapter.listSessions();\n\n expect(sessions).toHaveLength(1);\n expect(sessions[0]).toMatchObject({\n type: \"copilot\",\n sessionId: \"workspace-history\",\n cwd: \"/workspace-repo\",\n firstUserMessage: \"\",\n sessionFilePath: path.join(sessionStateDir, \"workspace-history\", \"events.jsonl\"),\n });\n });\n\n it(\"skips session directories without events or workspace metadata\", async () => {\n fs.mkdirSync(path.join(sessionStateDir, \"empty\"), { recursive: true });\n\n const sessions = await adapter.listSessions();\n\n expect(sessions).toEqual([]);\n });\n\n it(\"returns empty when session-state directory does not exist\", async () => {\n adapter = new CopilotAdapter(undefined, { sessionStateDir: path.join(tmpDir, \"missing\") });\n\n await expect(adapter.listSessions()).resolves.toEqual([]);\n });\n });\n});\n"],"names":["fs","os","path","CopilotAdapter","AgentStatus","listAgentProcesses","enrichProcesses","captureProcessSnapshot","generateAgentName","AgentRegistry","vi","mock","importOriginal","actual","fn","mockedListAgentProcesses","mockedEnrichProcesses","mockedCaptureProcessSnapshot","mockedGenerateAgentName","describe","adapter","tmpDir","sessionStateDir","beforeEach","mkdtempSync","join","tmpdir","mkdirSync","recursive","undefined","mockReset","mockImplementation","procs","names","flatMap","name","cwd","pid","basename","afterEach","rmSync","force","writeSession","sessionId","options","sessionDir","events","lines","map","entry","JSON","stringify","writeFileSync","workspace","Object","entries","key","value","lockPid","sessionStart","startTime","type","data","context","gitRoot","branch","timestamp","it","expect","toBe","canHandle","command","tty","mockReturnValue","agents","detectAgents","toEqual","toHaveBeenCalledWith","processes","content","Date","toISOString","id","created_at","updated_at","toHaveLength","toMatchObject","status","WAITING","projectPath","summary","sessionFilePath","RUNNING","ppid","registry","register","runtime","runtimeRef","session","startedAt","messages","getConversation","role","message","result","filePath","verbose","sessions","listSessions","byId","fromEntries","firstUserMessage","resolves"],"mappings":"AAAA;;CAEC,GAGD,YAAYA,QAAQ,KAAK;AACzB,YAAYC,QAAQ,KAAK;AACzB,YAAYC,UAAU,OAAO;AAE7B,SAASC,cAAc,QAAQ,+CAA+C;AAE9E,SAASC,WAAW,QAAQ,oCAAoC;AAChE,SACEC,kBAAkB,EAClBC,eAAe,EACfC,sBAAsB,QACjB,4BAA4B;AACnC,SAASC,iBAAiB,QAAQ,6BAA6B;AAC/D,SAASC,aAAa,QAAQ,kCAAkC;AAEhEC,GAAGC,IAAI,CAAC,6BAA6B,OAAOC;IAC1C,MAAMC,SAAU,MAAMD;IACtB,OAAO;QACL,GAAGC,MAAM;QACTR,oBAAoBK,GAAGI,EAAE;QACzBR,iBAAiBI,GAAGI,EAAE;QACtBP,wBAAwBG,GAAGI,EAAE;IAC/B;AACF;AAEAJ,GAAGC,IAAI,CAAC,8BAA8B,IAAO,CAAA;QAC3CH,mBAAmBE,GAAGI,EAAE;IAC1B,CAAA;AAEA,MAAMC,2BAA2BV;AACjC,MAAMW,wBAAwBV;AAC9B,MAAMW,+BAA+BV;AAGrC,MAAMW,0BAA0BV;AAEhCW,SAAS,kBAAkB;IACzB,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IAEJC,WAAW;QACTF,SAASrB,GAAGwB,WAAW,CAACtB,KAAKuB,IAAI,CAACxB,GAAGyB,MAAM,IAAI;QAC/CJ,kBAAkBpB,KAAKuB,IAAI,CAACJ,QAAQ;QACpCrB,GAAG2B,SAAS,CAACL,iBAAiB;YAAEM,WAAW;QAAK;QAChDR,UAAU,IAAIjB,eAAe0B,WAAW;YAAEP;QAAgB;QAE1DP,yBAAyBe,SAAS;QAClCd,sBAAsBc,SAAS;QAC/Bb,6BAA6Ba,SAAS;QACtCZ,wBAAwBY,SAAS;QACjCd,sBAAsBe,kBAAkB,CAAC,CAACC,QAAUA;QACpD,qGAAqG;QACrGf,6BAA6Bc,kBAAkB,CAAC,OAAOE,QACrD3B,gBAAgB2B,MAAMC,OAAO,CAAC,CAACC,OAAS9B,mBAAmB8B;QAE7DjB,wBAAwBa,kBAAkB,CACxC,CAACK,KAAKC,MAAQ,GAAGnC,KAAKoC,QAAQ,CAACF,QAAQ,UAAU,EAAE,EAAEC,IAAI,CAAC,CAAC;IAE/D;IAEAE,UAAU;QACRvC,GAAGwC,MAAM,CAACnB,QAAQ;YAAEO,WAAW;YAAMa,OAAO;QAAK;IACnD;IAEA,SAASC,aACPC,SAAiB,EACjBC,OAIC;QAED,MAAMC,aAAa3C,KAAKuB,IAAI,CAACH,iBAAiBqB;QAC9C3C,GAAG2B,SAAS,CAACkB,YAAY;YAAEjB,WAAW;QAAK;QAE3C,IAAIgB,QAAQE,MAAM,EAAE;YAClB,MAAMC,QAAQH,QAAQE,MAAM,CAACE,GAAG,CAAC,CAACC,QAChC,OAAOA,UAAU,WAAWA,QAAQC,KAAKC,SAAS,CAACF;YAErDjD,GAAGoD,aAAa,CAAClD,KAAKuB,IAAI,CAACoB,YAAY,iBAAiBE,MAAMtB,IAAI,CAAC;QACrE;QAEA,IAAImB,QAAQS,SAAS,EAAE;YACrB,MAAMN,QAAQO,OAAOC,OAAO,CAACX,QAAQS,SAAS,EAAEL,GAAG,CAAC,CAAC,CAACQ,KAAKC,MAAM,GAAK,GAAGD,IAAI,EAAE,EAAEC,OAAO;YACxFzD,GAAGoD,aAAa,CAAClD,KAAKuB,IAAI,CAACoB,YAAY,mBAAmBE,MAAMtB,IAAI,CAAC;QACvE;QAEA,IAAImB,QAAQc,OAAO,KAAK7B,WAAW;YACjC7B,GAAGoD,aAAa,CAAClD,KAAKuB,IAAI,CAACoB,YAAY,CAAC,MAAM,EAAED,QAAQc,OAAO,CAAC,KAAK,CAAC,GAAG;QAC3E;QAEA,OAAOb;IACT;IAEA,SAASc,aACPhB,SAAiB,EACjBP,GAAW,EACXwB,YAAY,0BAA0B;QAEtC,OAAO;YACLC,MAAM;YACNC,MAAM;gBACJnB;gBACAiB;gBACAG,SAAS;oBACP3B;oBACA4B,SAAS5B;oBACT6B,QAAQ;gBACV;YACF;YACAC,WAAWN;QACb;IACF;IAEAzC,SAAS,kBAAkB;QACzBgD,GAAG,wBAAwB;YACzBC,OAAOhD,QAAQyC,IAAI,EAAEQ,IAAI,CAAC;QAC5B;IACF;IAEAlD,SAAS,aAAa;QACpBgD,GAAG,qCAAqC;YACtCC,OAAOhD,QAAQkD,SAAS,CAAC;gBAAEjC,KAAK;gBAAGkC,SAAS;gBAAWnC,KAAK;gBAASoC,KAAK;YAAU,IAAIH,IAAI,CAC1F;QAEJ;QAEAF,GAAG,wDAAwD;YACzDC,OACEhD,QAAQkD,SAAS,CAAC;gBAChBjC,KAAK;gBACLkC,SAAS;gBACTnC,KAAK;gBACLoC,KAAK;YACP,IACAH,IAAI,CAAC;QACT;QAEAF,GAAG,yCAAyC;YAC1CC,OACEhD,QAAQkD,SAAS,CAAC;gBAChBjC,KAAK;gBACLkC,SAAS;gBACTnC,KAAK;gBACLoC,KAAK;YACP,IACAH,IAAI,CAAC;QACT;QAEAF,GAAG,0DAA0D;YAC3DC,OACEhD,QAAQkD,SAAS,CAAC;gBAChBjC,KAAK;gBACLkC,SAAS;gBACTnC,KAAK;gBACLoC,KAAK;YACP,IACAH,IAAI,CAAC;QACT;IACF;IAEAlD,SAAS,gBAAgB;QACvBgD,GAAG,yDAAyD;YAC1DpD,yBAAyB0D,eAAe,CAAC,EAAE;YAE3C,MAAMC,SAAS,MAAMtD,QAAQuD,YAAY;YAEzCP,OAAOM,QAAQE,OAAO,CAAC,EAAE;YACzBR,OAAOrD,0BAA0B8D,oBAAoB,CAAC;QACxD;QAEAV,GAAG,0DAA0D;YAC3D,MAAMW,YAA2B;gBAC/B;oBACEzC,KAAK;oBACLkC,SAAS;oBACTnC,KAAK;oBACLoC,KAAK;gBACP;aACD;YACDzD,yBAAyB0D,eAAe,CAACK;YACzC9D,sBAAsByD,eAAe,CAACK;YACtCpC,aAAa,UAAU;gBACrBgB,SAAS;gBACTZ,QAAQ;oBACNa,aAAa,UAAU,SAAS;oBAChC;wBACEE,MAAM;wBACNC,MAAM;4BAAEiB,SAAS;wBAA4B;wBAC7Cb,WAAW,IAAIc,OAAOC,WAAW;oBACnC;oBACA;wBACEpB,MAAM;wBACNC,MAAM;4BAAEiB,SAAS;wBAA2B;wBAC5Cb,WAAW,IAAIc,OAAOC,WAAW;oBACnC;iBACD;gBACD5B,WAAW;oBACT6B,IAAI;oBACJ9C,KAAK;oBACLD,MAAM;oBACNgD,YAAY;oBACZC,YAAY;gBACd;YACF;YAEA,MAAMV,SAAS,MAAMtD,QAAQuD,YAAY;YAEzCP,OAAOM,QAAQW,YAAY,CAAC;YAC5BjB,OAAOM,MAAM,CAAC,EAAE,EAAEY,aAAa,CAAC;gBAC9BzB,MAAM;gBACN0B,QAAQnF,YAAYoF,OAAO;gBAC3BnD,KAAK;gBACLoD,aAAa;gBACb9C,WAAW;gBACX+C,SAAS;gBACTC,iBAAiBzF,KAAKuB,IAAI,CAACH,iBAAiB,UAAU;YACxD;QACF;QAEA6C,GAAG,2CAA2C;YAC5C,MAAMW,YAA2B;gBAC/B;oBAAEzC,KAAK;oBAAKkC,SAAS;oBAAWnC,KAAK;oBAASoC,KAAK;gBAAU;aAC9D;YACDzD,yBAAyB0D,eAAe,CAACK;YACzC9D,sBAAsByD,eAAe,CAACK;YACtCpC,aAAa,WAAW;gBACtBgB,SAAS;gBACTZ,QAAQ;oBAACa,aAAa,WAAW;iBAAY;YAC/C;YACAjB,aAAa,aAAa;gBACxBgB,SAAS;gBACTZ,QAAQ;oBAACa,aAAa,aAAa;iBAAc;YACnD;YAEA,MAAMe,SAAS,MAAMtD,QAAQuD,YAAY;YAEzCP,OAAOM,QAAQW,YAAY,CAAC;YAC5BjB,OAAOM,MAAM,CAAC,EAAE,EAAEY,aAAa,CAAC;gBAC9BjD,KAAK;gBACLM,WAAW;gBACX+C,SAAS;YACX;QACF;QAEAvB,GAAG,iEAAiE;YAClE,MAAMW,YAA2B;gBAC/B;oBAAEzC,KAAK;oBAAKkC,SAAS;oBAAWnC,KAAK;oBAAWoC,KAAK;gBAAU;aAChE;YACDzD,yBAAyB0D,eAAe,CAACK;YACzC9D,sBAAsByD,eAAe,CAACK;YAEtC,MAAMJ,SAAS,MAAMtD,QAAQuD,YAAY;YAEzCP,OAAOM,QAAQW,YAAY,CAAC;YAC5BjB,OAAOM,MAAM,CAAC,EAAE,EAAEY,aAAa,CAAC;gBAC9BzB,MAAM;gBACN0B,QAAQnF,YAAYwF,OAAO;gBAC3BvD,KAAK;gBACLoD,aAAa;gBACb9C,WAAW;gBACX+C,SAAS;YACX;QACF;QAEAvB,GAAG,yEAAyE;YAC1E,MAAMW,YAA2B;gBAC/B;oBAAEzC,KAAK;oBAAOkC,SAAS;oBAAWnC,KAAK;oBAASoC,KAAK;oBAAWqB,MAAM;gBAAM;gBAC5E;oBACExD,KAAK;oBACLkC,SAAS;oBACTnC,KAAK;oBACLoC,KAAK;oBACLqB,MAAM;gBACR;aACD;YACD9E,yBAAyB0D,eAAe,CAACK;YACzC9D,sBAAsByD,eAAe,CAACK;YAEtC,MAAMJ,SAAS,MAAMtD,QAAQuD,YAAY;YAEzCP,OAAOM,QAAQW,YAAY,CAAC;YAC5BjB,OAAOM,MAAM,CAAC,EAAE,EAAEY,aAAa,CAAC;gBAC9BjD,KAAK;gBACLM,WAAW;gBACX+C,SAAS;YACX;QACF;QAEAvB,GAAG,2FAA2F;YAC5F,MAAM2B,WAAW,IAAIrF,cAAcP,KAAKuB,IAAI,CAACJ,QAAQ;YACrDD,UAAU,IAAIjB,eAAe2F,UAAU;gBAAExE;YAAgB;YACzD,MAAMwD,YAA2B;gBAC/B;oBAAEzC,KAAK;oBAAOkC,SAAS;oBAAWnC,KAAK;oBAASoC,KAAK;oBAAWqB,MAAM;gBAAM;gBAC5E;oBACExD,KAAK;oBACLkC,SAAS;oBACTnC,KAAK;oBACLoC,KAAK;oBACLqB,MAAM;gBACR;aACD;YACDC,SAASC,QAAQ,CAAC;gBAChB5D,MAAM;gBACN0B,MAAM;gBACNxB,KAAK;gBACL2D,SAAS;gBACTC,YAAY;oBAAEC,SAAS;gBAAkB;gBACzC9D,KAAK;gBACL+D,WAAW;gBACXxD,WAAW;gBACXgD,iBAAiB;YACnB;YACA5E,yBAAyB0D,eAAe,CAACK;YACzC9D,sBAAsByD,eAAe,CAACK;YAEtC,MAAMJ,SAAS,MAAMtD,QAAQuD,YAAY;YAEzCP,OAAOM,QAAQW,YAAY,CAAC;YAC5BjB,OAAOM,MAAM,CAAC,EAAE,EAAEY,aAAa,CAAC;gBAC9BnD,MAAM;gBACNE,KAAK;gBACLM,WAAW;YACb;QACF;QAEAwB,GAAG,sFAAsF;YACvF,MAAMW,YAA2B;gBAC/B;oBAAEzC,KAAK;oBAAOkC,SAAS;oBAAWnC,KAAK;oBAASoC,KAAK;gBAAU;gBAC/D;oBACEnC,KAAK;oBACLkC,SAAS;oBACTnC,KAAK;oBACLoC,KAAK;gBACP;aACD;YACDzD,yBAAyB0D,eAAe,CAACK;YACzC9D,sBAAsByD,eAAe,CAACK;YACtCpC,aAAa,gBAAgB;gBAC3BgB,SAAS;gBACTZ,QAAQ;oBACNa,aAAa,gBAAgB,SAAS;oBACtC;wBAAEE,MAAM;wBAAgBC,MAAM;4BAAEiB,SAAS;wBAAQ;wBAAGb,WAAW,IAAIc,OAAOC,WAAW;oBAAG;iBACzF;YACH;YAEA,MAAMP,SAAS,MAAMtD,QAAQuD,YAAY;YAEzCP,OAAOM,QAAQW,YAAY,CAAC;YAC5BjB,OAAOM,MAAM,CAAC,EAAE,EAAEY,aAAa,CAAC;gBAC9BjD,KAAK;gBACLM,WAAW;YACb;QACF;QAEAwB,GAAG,mEAAmE;YACpE,MAAM2B,WAAW,IAAIrF,cAAcP,KAAKuB,IAAI,CAACJ,QAAQ;YACrDD,UAAU,IAAIjB,eAAe2F,UAAU;gBAAExE;YAAgB;YACzD,MAAMwD,YAA2B;gBAC/B;oBAAEzC,KAAK;oBAAOkC,SAAS;oBAAWnC,KAAK;oBAASoC,KAAK;oBAAWqB,MAAM;gBAAM;gBAC5E;oBACExD,KAAK;oBACLkC,SAAS;oBACTnC,KAAK;oBACLoC,KAAK;oBACLqB,MAAM;gBACR;aACD;YACDC,SAASC,QAAQ,CAAC;gBAChB5D,MAAM;gBACN0B,MAAM;gBACNxB,KAAK;gBACL2D,SAAS;gBACTC,YAAY;oBAAEC,SAAS;gBAAkB;gBACzC9D,KAAK;gBACL+D,WAAW;gBACXxD,WAAW;gBACXgD,iBAAiB;YACnB;YACA5E,yBAAyB0D,eAAe,CAACK;YACzC9D,sBAAsByD,eAAe,CAACK;YACtCpC,aAAa,gBAAgB;gBAC3BgB,SAAS;gBACTZ,QAAQ;oBACNa,aAAa,gBAAgB,SAAS;oBACtC;wBAAEE,MAAM;wBAAgBC,MAAM;4BAAEiB,SAAS;wBAAQ;wBAAGb,WAAW,IAAIc,OAAOC,WAAW;oBAAG;iBACzF;YACH;YAEA,MAAMP,SAAS,MAAMtD,QAAQuD,YAAY;YAEzCP,OAAOM,QAAQW,YAAY,CAAC;YAC5BjB,OAAOM,MAAM,CAAC,EAAE,EAAEY,aAAa,CAAC;gBAC9BnD,MAAM;gBACNE,KAAK;gBACLM,WAAW;YACb;QACF;QAEAwB,GAAG,mDAAmD;YACpD,MAAMW,YAA2B;gBAC/B;oBAAEzC,KAAK;oBAAKkC,SAAS;oBAAWnC,KAAK;oBAAaoC,KAAK;gBAAU;aAClE;YACDzD,yBAAyB0D,eAAe,CAACK;YACzC9D,sBAAsByD,eAAe,CAACK;YACtCpC,aAAa,kBAAkB;gBAC7BgB,SAAS;gBACTL,WAAW;oBACT6B,IAAI;oBACJ9C,KAAK;oBACLD,MAAM;oBACNgD,YAAY;oBACZC,YAAY;gBACd;YACF;YAEA,MAAMV,SAAS,MAAMtD,QAAQuD,YAAY;YAEzCP,OAAOM,QAAQW,YAAY,CAAC;YAC5BjB,OAAOM,MAAM,CAAC,EAAE,EAAEY,aAAa,CAAC;gBAC9BjD,KAAK;gBACLoD,aAAa;gBACb9C,WAAW;gBACX+C,SAAS;YACX;QACF;IACF;IAEAvE,SAAS,mBAAmB;QAC1BgD,GAAG,4CAA4C;YAC7C,MAAMtB,aAAaH,aAAa,QAAQ;gBACtCI,QAAQ;oBACNa,aAAa,QAAQ;oBACrB;wBACEE,MAAM;wBACNC,MAAM;4BAAEiB,SAAS;wBAAQ;wBACzBb,WAAW;oBACb;oBACA;wBACEL,MAAM;wBACNC,MAAM;4BAAEiB,SAAS;wBAAW;wBAC5Bb,WAAW;oBACb;iBACD;YACH;YAEA,MAAMkC,WAAWhF,QAAQiF,eAAe,CAACnG,KAAKuB,IAAI,CAACoB,YAAY;YAE/DuB,OAAOgC,UAAUxB,OAAO,CAAC;gBACvB;oBAAE0B,MAAM;oBAAQvB,SAAS;oBAASb,WAAW;gBAA2B;gBACxE;oBAAEoC,MAAM;oBAAavB,SAAS;oBAAYb,WAAW;gBAA2B;aACjF;QACH;QAEAC,GAAG,8DAA8D;YAC/D,MAAMtB,aAAaH,aAAa,WAAW;gBACzCI,QAAQ;oBACN;wBACEe,MAAM;wBACNC,MAAM;4BAAEyC,SAAS;wBAAa;wBAC9BrC,WAAW;oBACb;oBACA;wBACEL,MAAM;wBACNC,MAAM;4BAAE0C,QAAQ;gCAAEzB,SAAS;4BAAc;wBAAE;wBAC3Cb,WAAW;oBACb;iBACD;YACH;YACA,MAAMuC,WAAWvG,KAAKuB,IAAI,CAACoB,YAAY;YAEvCuB,OAAOhD,QAAQiF,eAAe,CAACI,WAAW7B,OAAO,CAAC,EAAE;YACpDR,OAAOhD,QAAQiF,eAAe,CAACI,UAAU;gBAAEC,SAAS;YAAK,IAAI9B,OAAO,CAAC;gBACnE;oBAAE0B,MAAM;oBAAUvB,SAAS;oBAAcb,WAAW;gBAA2B;gBAC/E;oBAAEoC,MAAM;oBAAUvB,SAAS;oBAAeb,WAAW;gBAA2B;aACjF;QACH;QAEAC,GAAG,gDAAgD;YACjD,MAAMtB,aAAaH,aAAa,aAAa;gBAC3CI,QAAQ;oBACN;oBACA;wBACEe,MAAM;wBACNC,MAAM;4BAAEiB,SAAS;wBAAQ;wBACzBb,WAAW;oBACb;oBACA;wBAAEL,MAAM;wBAAqBC,MAAM,CAAC;wBAAGI,WAAW;oBAA2B;iBAC9E;YACH;YAEA,MAAMkC,WAAWhF,QAAQiF,eAAe,CAACnG,KAAKuB,IAAI,CAACoB,YAAY;YAE/DuB,OAAOgC,UAAUxB,OAAO,CAAC;gBACvB;oBAAE0B,MAAM;oBAAQvB,SAAS;oBAASb,WAAW;gBAA2B;aACzE;QACH;QAEAC,GAAG,wCAAwC;YACzCC,OAAOhD,QAAQiF,eAAe,CAACnG,KAAKuB,IAAI,CAACJ,QAAQ,mBAAmBuD,OAAO,CAAC,EAAE;QAChF;IACF;IAEAzD,SAAS,gBAAgB;QACvBgD,GAAG,oDAAoD;YACrDzB,aAAa,aAAa;gBACxBI,QAAQ;oBACNa,aAAa,aAAa,WAAW;oBACrC;wBACEE,MAAM;wBACNC,MAAM;4BAAEiB,SAAS;wBAAa;wBAC9Bb,WAAW;oBACb;oBACA;wBACEL,MAAM;wBACNC,MAAM;4BAAEiB,SAAS;wBAAS;wBAC1Bb,WAAW;oBACb;iBACD;YACH;YACAxB,aAAa,aAAa;gBACxBI,QAAQ;oBACNa,aAAa,aAAa,WAAW;oBACrC;wBACEE,MAAM;wBACNC,MAAM;4BAAEiB,SAAS;wBAAa;wBAC9Bb,WAAW;oBACb;iBACD;YACH;YAEA,MAAMyC,WAAW,MAAMvF,QAAQwF,YAAY;YAE3CxC,OAAOuC,UAAUtB,YAAY,CAAC;YAC9B,MAAMwB,OAAOvD,OAAOwD,WAAW,CAACH,SAAS3D,GAAG,CAAC,CAACkD,UAAY;oBAACA,QAAQvD,SAAS;oBAAEuD;iBAAQ;YACtF9B,OAAOyC,IAAI,CAAC,YAAY,EAAEvB,aAAa,CAAC;gBACtCzB,MAAM;gBACNzB,KAAK;gBACL2E,kBAAkB;gBAClBpB,iBAAiBzF,KAAKuB,IAAI,CAACH,iBAAiB,aAAa;YAC3D;YACA8C,OAAOyC,IAAI,CAAC,YAAY,EAAEvB,aAAa,CAAC;gBACtCzB,MAAM;gBACNzB,KAAK;gBACL2E,kBAAkB;YACpB;QACF;QAEA5C,GAAG,6BAA6B;YAC9BzB,aAAa,QAAQ;gBAAEI,QAAQ;oBAACa,aAAa,QAAQ;iBAAS;YAAC;YAC/DjB,aAAa,QAAQ;gBAAEI,QAAQ;oBAACa,aAAa,QAAQ;iBAAU;YAAC;YAEhE,MAAMgD,WAAW,MAAMvF,QAAQwF,YAAY,CAAC;gBAAExE,KAAK;YAAQ;YAE3DgC,OAAOuC,UAAUtB,YAAY,CAAC;YAC9BjB,OAAOuC,QAAQ,CAAC,EAAE,CAAChE,SAAS,EAAE0B,IAAI,CAAC;QACrC;QAEAF,GAAG,kEAAkE;YACnEzB,aAAa,qBAAqB;gBAChCW,WAAW;oBACT6B,IAAI;oBACJ9C,KAAK;oBACLD,MAAM;oBACNgD,YAAY;oBACZC,YAAY;gBACd;YACF;YAEA,MAAMuB,WAAW,MAAMvF,QAAQwF,YAAY;YAE3CxC,OAAOuC,UAAUtB,YAAY,CAAC;YAC9BjB,OAAOuC,QAAQ,CAAC,EAAE,EAAErB,aAAa,CAAC;gBAChCzB,MAAM;gBACNlB,WAAW;gBACXP,KAAK;gBACL2E,kBAAkB;gBAClBpB,iBAAiBzF,KAAKuB,IAAI,CAACH,iBAAiB,qBAAqB;YACnE;QACF;QAEA6C,GAAG,kEAAkE;YACnEnE,GAAG2B,SAAS,CAACzB,KAAKuB,IAAI,CAACH,iBAAiB,UAAU;gBAAEM,WAAW;YAAK;YAEpE,MAAM+E,WAAW,MAAMvF,QAAQwF,YAAY;YAE3CxC,OAAOuC,UAAU/B,OAAO,CAAC,EAAE;QAC7B;QAEAT,GAAG,6DAA6D;YAC9D/C,UAAU,IAAIjB,eAAe0B,WAAW;gBAAEP,iBAAiBpB,KAAKuB,IAAI,CAACJ,QAAQ;YAAW;YAExF,MAAM+C,OAAOhD,QAAQwF,YAAY,IAAII,QAAQ,CAACpC,OAAO,CAAC,EAAE;QAC1D;IACF;AACF"}
1
+ {"version":3,"sources":["../../../../src/__tests__/providers/copilot/CopilotAdapter.test.ts"],"sourcesContent":["/**\n * Tests for CopilotAdapter\n */\n\nimport type { MockedFunction } from \"vitest\";\nimport * as fs from \"fs\";\nimport * as os from \"os\";\nimport * as path from \"path\";\n\nimport { CopilotAdapter } from \"../../../providers/copilot/CopilotAdapter.js\";\nimport type { ProcessInfo } from \"../../../adapters/AgentAdapter.js\";\nimport { AgentStatus } from \"../../../adapters/AgentAdapter.js\";\nimport {\n listAgentProcesses,\n enrichProcesses,\n captureProcessSnapshot,\n} from \"../../../utils/process.js\";\nimport { generateAgentName } from \"../../../utils/matching.js\";\nimport { AgentRegistry } from \"../../../utils/AgentRegistry.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/matching.js\", () => ({\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 mockedGenerateAgentName = generateAgentName as MockedFunction<typeof generateAgentName>;\n\ndescribe(\"CopilotAdapter\", () => {\n let adapter: CopilotAdapter;\n let tmpDir: string;\n let sessionStateDir: string;\n\n beforeEach(() => {\n tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), \"copilot-test-\"));\n sessionStateDir = path.join(tmpDir, \"session-state\");\n fs.mkdirSync(sessionStateDir, { recursive: true });\n adapter = new CopilotAdapter(undefined, { sessionStateDir });\n\n mockedListAgentProcesses.mockReset();\n mockedEnrichProcesses.mockReset();\n mockedCaptureProcessSnapshot.mockReset();\n mockedGenerateAgentName.mockReset();\n mockedEnrichProcesses.mockImplementation((procs) => procs);\n // Compatibility shim for standalone adapter discovery; the manager captures once and slices by name.\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 fs.rmSync(tmpDir, { recursive: true, force: true });\n });\n\n function writeSession(\n sessionId: string,\n options: {\n events?: Array<object | string>;\n workspace?: Record<string, string>;\n lockPid?: number | string;\n },\n ): string {\n const sessionDir = path.join(sessionStateDir, sessionId);\n fs.mkdirSync(sessionDir, { recursive: true });\n\n if (options.events) {\n const lines = options.events.map((entry) =>\n typeof entry === \"string\" ? entry : JSON.stringify(entry),\n );\n fs.writeFileSync(path.join(sessionDir, \"events.jsonl\"), lines.join(\"\\n\"));\n }\n\n if (options.workspace) {\n const lines = Object.entries(options.workspace).map(([key, value]) => `${key}: ${value}`);\n fs.writeFileSync(path.join(sessionDir, \"workspace.yaml\"), lines.join(\"\\n\"));\n }\n\n if (options.lockPid !== undefined) {\n fs.writeFileSync(path.join(sessionDir, `inuse.${options.lockPid}.lock`), \"\");\n }\n\n return sessionDir;\n }\n\n function sessionStart(\n sessionId: string,\n cwd: string,\n startTime = \"2026-06-09T09:50:00.000Z\",\n ): object {\n return {\n type: \"session.start\",\n data: {\n sessionId,\n startTime,\n context: {\n cwd,\n gitRoot: cwd,\n branch: \"main\",\n },\n },\n timestamp: startTime,\n };\n }\n\n describe(\"initialization\", () => {\n it(\"exposes copilot type\", () => {\n expect(adapter.type).toBe(\"copilot\");\n });\n });\n\n describe(\"canHandle\", () => {\n it(\"returns true for copilot commands\", () => {\n expect(adapter.canHandle({ pid: 1, command: \"copilot\", cwd: \"/repo\", tty: \"ttys001\" })).toBe(\n true,\n );\n });\n\n it(\"returns true for full-path Homebrew copilot commands\", () => {\n expect(\n adapter.canHandle({\n pid: 2,\n command: \"/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot\",\n cwd: \"/repo\",\n tty: \"ttys002\",\n }),\n ).toBe(true);\n });\n\n it(\"returns true for copilot.exe commands\", () => {\n expect(\n adapter.canHandle({\n pid: 3,\n command: \"/usr/bin/copilot.exe\",\n cwd: \"/repo\",\n tty: \"ttys003\",\n }),\n ).toBe(true);\n });\n\n it(\"returns false when copilot appears only in an argument\", () => {\n expect(\n adapter.canHandle({\n pid: 4,\n command: \"node /repo/copilot-plugin/index.js\",\n cwd: \"/repo\",\n tty: \"ttys004\",\n }),\n ).toBe(false);\n });\n });\n\n describe(\"detectAgents\", () => {\n it(\"returns empty list when no copilot process is running\", async () => {\n mockedListAgentProcesses.mockReturnValue([]);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toEqual([]);\n expect(mockedListAgentProcesses).toHaveBeenCalledWith(\"copilot\");\n });\n\n it(\"maps matching inuse lock and events to an active agent\", async () => {\n const processes: ProcessInfo[] = [\n {\n pid: 14096,\n command: \"/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot\",\n cwd: \"/repo\",\n tty: \"ttys001\",\n },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession(\"sess-a\", {\n lockPid: 14096,\n events: [\n sessionStart(\"sess-a\", \"/repo\", \"2026-06-09T09:50:00.000Z\"),\n {\n type: \"user.message\",\n data: { content: \"Build the Copilot adapter\" },\n timestamp: new Date().toISOString(),\n },\n {\n type: \"assistant.message\",\n data: { content: \"I will inspect the code.\" },\n timestamp: new Date().toISOString(),\n },\n ],\n workspace: {\n id: \"sess-a\",\n cwd: \"/fallback\",\n name: \"Fallback Name\",\n created_at: \"2026-06-09T09:49:00.000Z\",\n updated_at: \"2026-06-09T09:51:00.000Z\",\n },\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: \"copilot\",\n status: AgentStatus.WAITING,\n pid: 14096,\n projectPath: \"/repo\",\n sessionId: \"sess-a\",\n summary: \"Build the Copilot adapter\",\n sessionFilePath: path.join(sessionStateDir, \"sess-a\", \"events.jsonl\"),\n });\n });\n\n it(\"ignores invalid and unmatched lock PIDs\", async () => {\n const processes: ProcessInfo[] = [\n { pid: 100, command: \"copilot\", cwd: \"/repo\", tty: \"ttys001\" },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession(\"invalid\", {\n lockPid: \"not-a-pid\",\n events: [sessionStart(\"invalid\", \"/invalid\")],\n });\n writeSession(\"unmatched\", {\n lockPid: 999999,\n events: [sessionStart(\"unmatched\", \"/unmatched\")],\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 100,\n sessionId: \"pid-100\",\n summary: \"Copilot process running\",\n });\n });\n\n it(\"falls back to process-only agent when no session lock matches\", async () => {\n const processes: ProcessInfo[] = [\n { pid: 200, command: \"copilot\", cwd: \"/repo-b\", tty: \"ttys002\" },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n type: \"copilot\",\n status: AgentStatus.RUNNING,\n pid: 200,\n projectPath: \"/repo-b\",\n sessionId: \"pid-200\",\n summary: \"Copilot process running\",\n });\n });\n\n it(\"suppresses wrapper process-only agents before the session lock exists\", async () => {\n const processes: ProcessInfo[] = [\n { pid: 86800, command: \"copilot\", cwd: \"/repo\", tty: \"ttys001\", ppid: 84174 },\n {\n pid: 86810,\n command: \"/custom/install/copilot\",\n cwd: \"/repo\",\n tty: \"ttys001\",\n ppid: 86800,\n },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 86810,\n sessionId: \"pid-86810\",\n summary: \"Copilot process running\",\n });\n });\n\n it(\"carries the managed wrapper name to a process-only child before the session lock exists\", async () => {\n const registry = new AgentRegistry(path.join(tmpDir, \"agents.json\"));\n adapter = new CopilotAdapter(registry, { sessionStateDir });\n const processes: ProcessInfo[] = [\n { pid: 86800, command: \"copilot\", cwd: \"/repo\", tty: \"ttys001\", ppid: 84174 },\n {\n pid: 86810,\n command: \"/custom/install/copilot\",\n cwd: \"/repo\",\n tty: \"ttys001\",\n ppid: 86800,\n },\n ];\n registry.register({\n name: \"copilot-started\",\n type: \"copilot\",\n pid: 86800,\n runtime: \"tmux\",\n runtimeRef: { session: \"copilot-started\" },\n cwd: \"/repo\",\n startedAt: \"2026-06-13T19:15:16.211Z\",\n sessionId: \"pid-86800\",\n sessionFilePath: \"\",\n });\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n name: \"copilot-started\",\n pid: 86810,\n sessionId: \"pid-86810\",\n });\n });\n\n it(\"does not add duplicate process-only agent for wrapper process in the same terminal\", async () => {\n const processes: ProcessInfo[] = [\n { pid: 14095, command: \"copilot\", cwd: \"/repo\", tty: \"ttys001\" },\n {\n pid: 14096,\n command: \"/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot\",\n cwd: \"/repo\",\n tty: \"ttys001\",\n },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession(\"sess-wrapper\", {\n lockPid: 14096,\n events: [\n sessionStart(\"sess-wrapper\", \"/repo\", \"2026-06-09T09:50:00.000Z\"),\n { type: \"user.message\", data: { content: \"hello\" }, timestamp: new Date().toISOString() },\n ],\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 14096,\n sessionId: \"sess-wrapper\",\n });\n });\n\n it(\"carries the managed wrapper name to a lock-backed child process\", async () => {\n const registry = new AgentRegistry(path.join(tmpDir, \"agents.json\"));\n adapter = new CopilotAdapter(registry, { sessionStateDir });\n const processes: ProcessInfo[] = [\n { pid: 14095, command: \"copilot\", cwd: \"/repo\", tty: \"ttys001\", ppid: 84174 },\n {\n pid: 14096,\n command: \"/opt/homebrew/Caskroom/copilot-cli/1.0.60/copilot\",\n cwd: \"/repo\",\n tty: \"ttys001\",\n ppid: 14095,\n },\n ];\n registry.register({\n name: \"copilot-started\",\n type: \"copilot\",\n pid: 14095,\n runtime: \"tmux\",\n runtimeRef: { session: \"copilot-started\" },\n cwd: \"/repo\",\n startedAt: \"2026-06-13T19:15:16.211Z\",\n sessionId: \"pid-14095\",\n sessionFilePath: \"\",\n });\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession(\"sess-wrapper\", {\n lockPid: 14096,\n events: [\n sessionStart(\"sess-wrapper\", \"/repo\", \"2026-06-09T09:50:00.000Z\"),\n { type: \"user.message\", data: { content: \"hello\" }, timestamp: new Date().toISOString() },\n ],\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n name: \"copilot-started\",\n pid: 14096,\n sessionId: \"sess-wrapper\",\n });\n });\n\n it(\"uses workspace metadata when events are missing\", async () => {\n const processes: ProcessInfo[] = [\n { pid: 300, command: \"copilot\", cwd: \"/proc-cwd\", tty: \"ttys003\" },\n ];\n mockedListAgentProcesses.mockReturnValue(processes);\n mockedEnrichProcesses.mockReturnValue(processes);\n writeSession(\"workspace-only\", {\n lockPid: 300,\n workspace: {\n id: \"workspace-only\",\n cwd: \"/workspace-cwd\",\n name: \"Workspace Session\",\n created_at: \"2026-06-09T09:00:00.000Z\",\n updated_at: \"2026-06-09T09:10:00.000Z\",\n },\n });\n\n const agents = await adapter.detectAgents();\n\n expect(agents).toHaveLength(1);\n expect(agents[0]).toMatchObject({\n pid: 300,\n projectPath: \"/workspace-cwd\",\n sessionId: \"workspace-only\",\n summary: \"Workspace Session\",\n });\n });\n });\n\n describe(\"getConversation\", () => {\n it(\"parses user and assistant message events\", () => {\n const sessionDir = writeSession(\"conv\", {\n events: [\n sessionStart(\"conv\", \"/repo\"),\n {\n type: \"user.message\",\n data: { content: \"hello\" },\n timestamp: \"2026-06-09T09:50:01.000Z\",\n },\n {\n type: \"assistant.message\",\n data: { content: \"Hi there\" },\n timestamp: \"2026-06-09T09:50:02.000Z\",\n },\n ],\n });\n\n const messages = adapter.getConversation(path.join(sessionDir, \"events.jsonl\"));\n\n expect(messages).toEqual([\n { role: \"user\", content: \"hello\", timestamp: \"2026-06-09T09:50:01.000Z\" },\n { role: \"assistant\", content: \"Hi there\", timestamp: \"2026-06-09T09:50:02.000Z\" },\n ]);\n });\n\n it(\"includes system/info/warning messages only in verbose mode\", () => {\n const sessionDir = writeSession(\"verbose\", {\n events: [\n {\n type: \"session.warning\",\n data: { message: \"MCP failed\" },\n timestamp: \"2026-06-09T09:50:01.000Z\",\n },\n {\n type: \"tool.execution_complete\",\n data: { result: { content: \"Tool result\" } },\n timestamp: \"2026-06-09T09:50:02.000Z\",\n },\n ],\n });\n const filePath = path.join(sessionDir, \"events.jsonl\");\n\n expect(adapter.getConversation(filePath)).toEqual([]);\n expect(adapter.getConversation(filePath, { verbose: true })).toEqual([\n { role: \"system\", content: \"MCP failed\", timestamp: \"2026-06-09T09:50:01.000Z\" },\n { role: \"system\", content: \"Tool result\", timestamp: \"2026-06-09T09:50:02.000Z\" },\n ]);\n });\n\n it(\"skips malformed JSONL lines and missing text\", () => {\n const sessionDir = writeSession(\"malformed\", {\n events: [\n \"not json\",\n {\n type: \"user.message\",\n data: { content: \"valid\" },\n timestamp: \"2026-06-09T09:50:01.000Z\",\n },\n { type: \"assistant.message\", data: {}, timestamp: \"2026-06-09T09:50:02.000Z\" },\n ],\n });\n\n const messages = adapter.getConversation(path.join(sessionDir, \"events.jsonl\"));\n\n expect(messages).toEqual([\n { role: \"user\", content: \"valid\", timestamp: \"2026-06-09T09:50:01.000Z\" },\n ]);\n });\n\n it(\"returns empty array for missing file\", () => {\n expect(adapter.getConversation(path.join(tmpDir, \"missing.jsonl\"))).toEqual([]);\n });\n });\n\n describe(\"listSessions\", () => {\n it(\"returns historical sessions without active locks\", async () => {\n writeSession(\"history-a\", {\n events: [\n sessionStart(\"history-a\", \"/repo-a\", \"2026-06-09T09:00:00.000Z\"),\n {\n type: \"user.message\",\n data: { content: \"first user\" },\n timestamp: \"2026-06-09T09:00:01.000Z\",\n },\n {\n type: \"assistant.message\",\n data: { content: \"answer\" },\n timestamp: \"2026-06-09T09:00:02.000Z\",\n },\n ],\n });\n writeSession(\"history-b\", {\n events: [\n sessionStart(\"history-b\", \"/repo-b\", \"2026-06-09T10:00:00.000Z\"),\n {\n type: \"user.message\",\n data: { content: \"other user\" },\n timestamp: \"2026-06-09T10:00:01.000Z\",\n },\n ],\n });\n\n const sessions = await adapter.listSessions();\n\n expect(sessions).toHaveLength(2);\n const byId = Object.fromEntries(sessions.map((session) => [session.sessionId, session]));\n expect(byId[\"history-a\"]).toMatchObject({\n type: \"copilot\",\n cwd: \"/repo-a\",\n firstUserMessage: \"first user\",\n sessionFilePath: path.join(sessionStateDir, \"history-a\", \"events.jsonl\"),\n });\n expect(byId[\"history-b\"]).toMatchObject({\n type: \"copilot\",\n cwd: \"/repo-b\",\n firstUserMessage: \"other user\",\n });\n });\n\n it(\"finds an exact session ID from its session directory\", async () => {\n writeSession(\"wanted\", { events: [sessionStart(\"wanted\", \"/repo\")] });\n writeSession(\"other\", { events: [sessionStart(\"other\", \"/other\")] });\n\n await expect(adapter.findSessionsById(\"wanted\")).resolves.toEqual([\n expect.objectContaining({\n sessionId: \"wanted\",\n sessionFilePath: path.join(sessionStateDir, \"wanted\", \"events.jsonl\"),\n }),\n ]);\n });\n\n it(\"applies strict cwd filter\", async () => {\n writeSession(\"keep\", { events: [sessionStart(\"keep\", \"/repo\")] });\n writeSession(\"drop\", { events: [sessionStart(\"drop\", \"/other\")] });\n\n const sessions = await adapter.listSessions({ cwd: \"/repo\" });\n\n expect(sessions).toHaveLength(1);\n expect(sessions[0].sessionId).toBe(\"keep\");\n });\n\n it(\"uses workspace fallback for historical sessions without events\", async () => {\n writeSession(\"workspace-history\", {\n workspace: {\n id: \"workspace-history\",\n cwd: \"/workspace-repo\",\n name: \"Workspace History\",\n created_at: \"2026-06-09T08:00:00.000Z\",\n updated_at: \"2026-06-09T08:10:00.000Z\",\n },\n });\n\n const sessions = await adapter.listSessions();\n\n expect(sessions).toHaveLength(1);\n expect(sessions[0]).toMatchObject({\n type: \"copilot\",\n sessionId: \"workspace-history\",\n cwd: \"/workspace-repo\",\n firstUserMessage: \"\",\n sessionFilePath: path.join(sessionStateDir, \"workspace-history\", \"events.jsonl\"),\n });\n });\n\n it(\"skips session directories without events or workspace metadata\", async () => {\n fs.mkdirSync(path.join(sessionStateDir, \"empty\"), { recursive: true });\n\n const sessions = await adapter.listSessions();\n\n expect(sessions).toEqual([]);\n });\n\n it(\"returns empty when session-state directory does not exist\", async () => {\n adapter = new CopilotAdapter(undefined, { sessionStateDir: path.join(tmpDir, \"missing\") });\n\n await expect(adapter.listSessions()).resolves.toEqual([]);\n });\n });\n});\n"],"names":["fs","os","path","CopilotAdapter","AgentStatus","listAgentProcesses","enrichProcesses","captureProcessSnapshot","generateAgentName","AgentRegistry","vi","mock","importOriginal","actual","fn","mockedListAgentProcesses","mockedEnrichProcesses","mockedCaptureProcessSnapshot","mockedGenerateAgentName","describe","adapter","tmpDir","sessionStateDir","beforeEach","mkdtempSync","join","tmpdir","mkdirSync","recursive","undefined","mockReset","mockImplementation","procs","names","flatMap","name","cwd","pid","basename","afterEach","rmSync","force","writeSession","sessionId","options","sessionDir","events","lines","map","entry","JSON","stringify","writeFileSync","workspace","Object","entries","key","value","lockPid","sessionStart","startTime","type","data","context","gitRoot","branch","timestamp","it","expect","toBe","canHandle","command","tty","mockReturnValue","agents","detectAgents","toEqual","toHaveBeenCalledWith","processes","content","Date","toISOString","id","created_at","updated_at","toHaveLength","toMatchObject","status","WAITING","projectPath","summary","sessionFilePath","RUNNING","ppid","registry","register","runtime","runtimeRef","session","startedAt","messages","getConversation","role","message","result","filePath","verbose","sessions","listSessions","byId","fromEntries","firstUserMessage","findSessionsById","resolves","objectContaining"],"mappings":"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"}
@@ -1241,6 +1241,42 @@ describe("GeminiCliAdapter", ()=>{
1241
1241
  firstUserMessage: "hello b"
1242
1242
  });
1243
1243
  });
1244
+ it("finds an exact session ID from Gemini metadata", async ()=>{
1245
+ const wantedPath = writeSession(tmpHome, "aaa", "session-one", {
1246
+ sessionId: "wanted",
1247
+ projectHash: hashProjectRoot("/repo"),
1248
+ startTime: "2025-01-01T00:00:00Z",
1249
+ directories: [
1250
+ "/repo"
1251
+ ],
1252
+ messages: [
1253
+ {
1254
+ type: "user",
1255
+ content: "wanted prompt"
1256
+ }
1257
+ ]
1258
+ });
1259
+ writeSession(tmpHome, "bbb", "session-two", {
1260
+ sessionId: "other",
1261
+ projectHash: hashProjectRoot("/other"),
1262
+ startTime: "2025-01-01T00:00:00Z",
1263
+ directories: [
1264
+ "/other"
1265
+ ],
1266
+ messages: [
1267
+ {
1268
+ type: "user",
1269
+ content: "other prompt"
1270
+ }
1271
+ ]
1272
+ });
1273
+ await expect(adapter.findSessionsById("wanted")).resolves.toEqual([
1274
+ expect.objectContaining({
1275
+ sessionId: "wanted",
1276
+ sessionFilePath: wantedPath
1277
+ })
1278
+ ]);
1279
+ });
1244
1280
  it("applies strict-equality cwd filter against directories[0]", async ()=>{
1245
1281
  writeSession(tmpHome, "aaa", "session-keep", {
1246
1282
  sessionId: "keep",