@ai-devkit/agent-manager 0.31.0 → 0.33.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.
- package/dist/AgentManager.d.ts +3 -3
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +80 -79
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/AgentManager.test.js +413 -388
- package/dist/__tests__/AgentManager.test.js.map +1 -1
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +642 -642
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/CodexAdapter.test.js +322 -2321
- package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/CopilotAdapter.test.js +254 -248
- package/dist/__tests__/adapters/CopilotAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js +564 -558
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/GrokCliAdapter.test.js +114 -114
- package/dist/__tests__/adapters/GrokCliAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js +169 -169
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +1 -1
- package/dist/__tests__/capacity/codex.test.js +93 -93
- package/dist/__tests__/capacity/codex.test.js.map +1 -1
- package/dist/__tests__/capacity/index.test.js +17 -17
- package/dist/__tests__/capacity/index.test.js.map +1 -1
- package/dist/__tests__/durable/CodexCliProbe.test.js +60 -60
- package/dist/__tests__/durable/CodexCliProbe.test.js.map +1 -1
- package/dist/__tests__/durable/CodexDurableAgentRepository.test.js +28 -28
- package/dist/__tests__/durable/CodexDurableAgentRepository.test.js.map +1 -1
- package/dist/__tests__/durable/CodexPrintAgent.integration.test.js +30 -30
- package/dist/__tests__/durable/CodexPrintAgent.integration.test.js.map +1 -1
- package/dist/__tests__/durable/CodexPrintAgentService.test.js +71 -71
- package/dist/__tests__/durable/CodexPrintAgentService.test.js.map +1 -1
- package/dist/__tests__/durable/CodexPrintRunner.test.js +125 -125
- package/dist/__tests__/durable/CodexPrintRunner.test.js.map +1 -1
- package/dist/__tests__/print/ClaudeCliProbe.test.js +20 -20
- package/dist/__tests__/print/ClaudeCliProbe.test.js.map +1 -1
- package/dist/__tests__/print/ClaudePrintAgent.integration.test.js +20 -20
- package/dist/__tests__/print/ClaudePrintAgent.integration.test.js.map +1 -1
- package/dist/__tests__/print/ClaudePrintAgentService.test.js +58 -58
- package/dist/__tests__/print/ClaudePrintAgentService.test.js.map +1 -1
- package/dist/__tests__/print/ClaudePrintRunner.test.js +55 -55
- package/dist/__tests__/print/ClaudePrintRunner.test.js.map +1 -1
- package/dist/__tests__/print/DurableAgent.test.js +9 -9
- package/dist/__tests__/print/DurableAgent.test.js.map +1 -1
- package/dist/__tests__/print/DurableAgentRepository.sqlite.test.js +40 -40
- package/dist/__tests__/print/DurableAgentRepository.sqlite.test.js.map +1 -1
- package/dist/__tests__/print/DurableAgentRepository.test.js +66 -66
- package/dist/__tests__/print/DurableAgentRepository.test.js.map +1 -1
- package/dist/__tests__/providers/claude/ClaudeAgentMapper.test.js +34 -34
- package/dist/__tests__/providers/claude/ClaudeAgentMapper.test.js.map +1 -1
- package/dist/__tests__/providers/claude/ClaudeSessionLocator.test.js +132 -132
- package/dist/__tests__/providers/claude/ClaudeSessionLocator.test.js.map +1 -1
- package/dist/__tests__/providers/codex/CodexSessionLocator.test.js +194 -0
- package/dist/__tests__/providers/codex/CodexSessionLocator.test.js.map +1 -0
- package/dist/__tests__/providers/codex/CodexSessionMapping.test.js +86 -0
- package/dist/__tests__/providers/codex/CodexSessionMapping.test.js.map +1 -0
- package/dist/__tests__/providers/codex/CodexSessionParser.test.js +254 -0
- package/dist/__tests__/providers/codex/CodexSessionParser.test.js.map +1 -0
- package/dist/__tests__/providers/pi/PiAdapter.test.js +337 -0
- package/dist/__tests__/providers/pi/PiAdapter.test.js.map +1 -0
- package/dist/__tests__/providers/pi/PiSessionParser.test.js +246 -0
- package/dist/__tests__/providers/pi/PiSessionParser.test.js.map +1 -0
- package/dist/__tests__/providers/pi/PiSessionTracker.test.js +108 -0
- package/dist/__tests__/providers/pi/PiSessionTracker.test.js.map +1 -0
- package/dist/__tests__/providers/pi/durable/PiCliProbe.test.js +33 -33
- package/dist/__tests__/providers/pi/durable/PiCliProbe.test.js.map +1 -1
- package/dist/__tests__/providers/pi/durable/PiDurableAgentRepository.test.js +16 -16
- package/dist/__tests__/providers/pi/durable/PiDurableAgentRepository.test.js.map +1 -1
- package/dist/__tests__/providers/pi/durable/PiPrintAgentService.test.js +52 -52
- package/dist/__tests__/providers/pi/durable/PiPrintAgentService.test.js.map +1 -1
- package/dist/__tests__/providers/pi/durable/PiPrintProtocol.test.js +44 -44
- package/dist/__tests__/providers/pi/durable/PiPrintProtocol.test.js.map +1 -1
- package/dist/__tests__/providers/pi/durable/PiPrintRunner.test.js +126 -126
- package/dist/__tests__/providers/pi/durable/PiPrintRunner.test.js.map +1 -1
- package/dist/__tests__/providers/pi/durable/PiStreamParser.test.js +14 -14
- package/dist/__tests__/providers/pi/durable/PiStreamParser.test.js.map +1 -1
- package/dist/__tests__/readiness/AgentReadiness.test.js +301 -0
- package/dist/__tests__/readiness/AgentReadiness.test.js.map +1 -0
- package/dist/__tests__/runtime/AgentRuntime.test.js +13 -0
- package/dist/__tests__/runtime/AgentRuntime.test.js.map +1 -0
- package/dist/__tests__/runtime/HerdrAgentRuntime.test.js +452 -0
- package/dist/__tests__/runtime/HerdrAgentRuntime.test.js.map +1 -0
- package/dist/__tests__/runtime/ManagedAgentRuntime.test.js +588 -0
- package/dist/__tests__/runtime/ManagedAgentRuntime.test.js.map +1 -0
- package/dist/__tests__/terminal/TerminalFocusManager.test.js +100 -100
- package/dist/__tests__/terminal/TerminalFocusManager.test.js.map +1 -1
- package/dist/__tests__/terminal/TmuxManager.test.js +101 -101
- package/dist/__tests__/terminal/TmuxManager.test.js.map +1 -1
- package/dist/__tests__/terminal/TtyWriter.test.js +185 -185
- package/dist/__tests__/terminal/TtyWriter.test.js.map +1 -1
- package/dist/__tests__/utils/AgentRegistry.test.js +335 -217
- package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -1
- package/dist/__tests__/utils/ClaudeSessionParser.test.js +103 -101
- package/dist/__tests__/utils/ClaudeSessionParser.test.js.map +1 -1
- package/dist/__tests__/utils/agent-requests.test.js +43 -43
- package/dist/__tests__/utils/agent-requests.test.js.map +1 -1
- package/dist/__tests__/utils/agents.test.js +17 -17
- package/dist/__tests__/utils/agents.test.js.map +1 -1
- package/dist/__tests__/utils/matching.test.js +92 -92
- package/dist/__tests__/utils/matching.test.js.map +1 -1
- package/dist/__tests__/utils/process.test.js +108 -108
- package/dist/__tests__/utils/process.test.js.map +1 -1
- package/dist/__tests__/utils/session.test.js +53 -53
- package/dist/__tests__/utils/session.test.js.map +1 -1
- package/dist/adapters/AgentAdapter.d.ts +2 -2
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/AgentAdapter.js +1 -1
- package/dist/adapters/AgentAdapter.js.map +1 -1
- package/dist/adapters/CopilotAdapter.d.ts +2 -2
- package/dist/adapters/CopilotAdapter.d.ts.map +1 -1
- package/dist/adapters/CopilotAdapter.js +51 -51
- package/dist/adapters/CopilotAdapter.js.map +1 -1
- package/dist/adapters/GeminiCliAdapter.d.ts +2 -2
- package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -1
- package/dist/adapters/GeminiCliAdapter.js +86 -86
- package/dist/adapters/GeminiCliAdapter.js.map +1 -1
- package/dist/adapters/GrokCliAdapter.d.ts +1 -1
- package/dist/adapters/GrokCliAdapter.d.ts.map +1 -1
- package/dist/adapters/GrokCliAdapter.js +80 -80
- package/dist/adapters/GrokCliAdapter.js.map +1 -1
- package/dist/adapters/OpenCodeAdapter.d.ts +1 -1
- package/dist/adapters/OpenCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/OpenCodeAdapter.js +37 -36
- package/dist/adapters/OpenCodeAdapter.js.map +1 -1
- package/dist/adapters/index.d.ts +9 -9
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +8 -8
- package/dist/adapters/index.js.map +1 -1
- package/dist/capacity/codex.d.ts +3 -3
- package/dist/capacity/codex.d.ts.map +1 -1
- package/dist/capacity/codex.js +57 -57
- package/dist/capacity/codex.js.map +1 -1
- package/dist/capacity/index.d.ts +3 -3
- package/dist/capacity/index.d.ts.map +1 -1
- package/dist/capacity/index.js +8 -8
- package/dist/capacity/index.js.map +1 -1
- package/dist/capacity/types.d.ts +1 -1
- package/dist/capacity/types.js.map +1 -1
- package/dist/database/connection.d.ts +1 -1
- package/dist/database/connection.d.ts.map +1 -1
- package/dist/database/connection.js +15 -15
- package/dist/database/connection.js.map +1 -1
- package/dist/database/index.d.ts +3 -3
- package/dist/database/index.d.ts.map +1 -1
- package/dist/database/index.js +2 -2
- package/dist/database/index.js.map +1 -1
- package/dist/database/migrations/005_agent_runtime.sql +2 -0
- package/dist/database/schema.d.ts +1 -1
- package/dist/database/schema.d.ts.map +1 -1
- package/dist/database/schema.js +7 -7
- package/dist/database/schema.js.map +1 -1
- package/dist/durable/DurableAgent.d.ts +9 -9
- package/dist/durable/DurableAgent.d.ts.map +1 -1
- package/dist/durable/DurableAgent.js +16 -16
- package/dist/durable/DurableAgent.js.map +1 -1
- package/dist/durable/DurableAgentRepository.d.ts +4 -4
- package/dist/durable/DurableAgentRepository.d.ts.map +1 -1
- package/dist/durable/DurableAgentRepository.js +49 -48
- package/dist/durable/DurableAgentRepository.js.map +1 -1
- package/dist/durable/process.d.ts +1 -1
- package/dist/durable/process.d.ts.map +1 -1
- package/dist/durable/process.js +14 -14
- package/dist/durable/process.js.map +1 -1
- package/dist/durable/run.d.ts +2 -2
- package/dist/durable/run.d.ts.map +1 -1
- package/dist/durable/run.js +6 -6
- package/dist/durable/run.js.map +1 -1
- package/dist/durable/utils.d.ts +1 -1
- package/dist/durable/utils.d.ts.map +1 -1
- package/dist/durable/utils.js +4 -4
- package/dist/durable/utils.js.map +1 -1
- package/dist/index.d.ts +46 -41
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -25
- package/dist/index.js.map +1 -1
- package/dist/providers/claude/ClaudeAgentMapper.d.ts +4 -4
- package/dist/providers/claude/ClaudeAgentMapper.d.ts.map +1 -1
- package/dist/providers/claude/ClaudeAgentMapper.js +10 -10
- package/dist/providers/claude/ClaudeAgentMapper.js.map +1 -1
- package/dist/providers/claude/ClaudeCodeAdapter.d.ts +1 -1
- package/dist/providers/claude/ClaudeCodeAdapter.d.ts.map +1 -1
- package/dist/providers/claude/ClaudeCodeAdapter.js +14 -14
- package/dist/providers/claude/ClaudeCodeAdapter.js.map +1 -1
- package/dist/providers/claude/ClaudeSessionLocator.d.ts +4 -4
- package/dist/providers/claude/ClaudeSessionLocator.d.ts.map +1 -1
- package/dist/providers/claude/ClaudeSessionLocator.js +79 -79
- package/dist/providers/claude/ClaudeSessionLocator.js.map +1 -1
- package/dist/providers/claude/ClaudeSessionParser.d.ts +2 -2
- package/dist/providers/claude/ClaudeSessionParser.d.ts.map +1 -1
- package/dist/providers/claude/ClaudeSessionParser.js +99 -99
- package/dist/providers/claude/ClaudeSessionParser.js.map +1 -1
- package/dist/providers/claude/durable/ClaudeCliProbe.d.ts.map +1 -1
- package/dist/providers/claude/durable/ClaudeCliProbe.js +16 -16
- package/dist/providers/claude/durable/ClaudeCliProbe.js.map +1 -1
- package/dist/providers/claude/durable/ClaudePrintAgentService.d.ts +5 -5
- package/dist/providers/claude/durable/ClaudePrintAgentService.d.ts.map +1 -1
- package/dist/providers/claude/durable/ClaudePrintAgentService.js +10 -10
- package/dist/providers/claude/durable/ClaudePrintAgentService.js.map +1 -1
- package/dist/providers/claude/durable/ClaudePrintRunner.d.ts +4 -4
- package/dist/providers/claude/durable/ClaudePrintRunner.d.ts.map +1 -1
- package/dist/providers/claude/durable/ClaudePrintRunner.js +18 -18
- package/dist/providers/claude/durable/ClaudePrintRunner.js.map +1 -1
- package/dist/providers/claude/durable/ClaudeStreamParser.d.ts +2 -2
- package/dist/providers/claude/durable/ClaudeStreamParser.d.ts.map +1 -1
- package/dist/providers/claude/durable/ClaudeStreamParser.js +17 -17
- package/dist/providers/claude/durable/ClaudeStreamParser.js.map +1 -1
- package/dist/providers/codex/CodexAdapter.d.ts +27 -0
- package/dist/providers/codex/CodexAdapter.d.ts.map +1 -0
- package/dist/providers/codex/CodexAdapter.js +162 -0
- package/dist/providers/codex/CodexAdapter.js.map +1 -0
- package/dist/providers/codex/CodexAgentMapper.d.ts +9 -0
- package/dist/providers/codex/CodexAgentMapper.d.ts.map +1 -0
- package/dist/providers/codex/CodexAgentMapper.js +35 -0
- package/dist/providers/codex/CodexAgentMapper.js.map +1 -0
- package/dist/providers/codex/CodexSessionLocator.d.ts +44 -0
- package/dist/providers/codex/CodexSessionLocator.d.ts.map +1 -0
- package/dist/providers/codex/CodexSessionLocator.js +207 -0
- package/dist/providers/codex/CodexSessionLocator.js.map +1 -0
- package/dist/providers/codex/CodexSessionMapping.d.ts +23 -0
- package/dist/providers/codex/CodexSessionMapping.d.ts.map +1 -0
- package/dist/providers/codex/CodexSessionMapping.js +68 -0
- package/dist/providers/codex/CodexSessionMapping.js.map +1 -0
- package/dist/providers/codex/CodexSessionParser.d.ts +58 -0
- package/dist/providers/codex/CodexSessionParser.d.ts.map +1 -0
- package/dist/providers/codex/CodexSessionParser.js +262 -0
- package/dist/providers/codex/CodexSessionParser.js.map +1 -0
- package/dist/providers/codex/durable/CodexCliProbe.d.ts.map +1 -1
- package/dist/providers/codex/durable/CodexCliProbe.js +22 -22
- package/dist/providers/codex/durable/CodexCliProbe.js.map +1 -1
- package/dist/providers/codex/durable/CodexPrintAgentService.d.ts +6 -6
- package/dist/providers/codex/durable/CodexPrintAgentService.d.ts.map +1 -1
- package/dist/providers/codex/durable/CodexPrintAgentService.js +10 -10
- package/dist/providers/codex/durable/CodexPrintAgentService.js.map +1 -1
- package/dist/providers/codex/durable/CodexPrintRunner.d.ts +4 -4
- package/dist/providers/codex/durable/CodexPrintRunner.d.ts.map +1 -1
- package/dist/providers/codex/durable/CodexPrintRunner.js +20 -20
- package/dist/providers/codex/durable/CodexPrintRunner.js.map +1 -1
- package/dist/providers/codex/durable/CodexStreamParser.d.ts +2 -2
- package/dist/providers/codex/durable/CodexStreamParser.d.ts.map +1 -1
- package/dist/providers/codex/durable/CodexStreamParser.js +21 -21
- package/dist/providers/codex/durable/CodexStreamParser.js.map +1 -1
- package/dist/providers/pi/PiAdapter.d.ts +28 -0
- package/dist/providers/pi/PiAdapter.d.ts.map +1 -0
- package/dist/providers/pi/PiAdapter.js +153 -0
- package/dist/providers/pi/PiAdapter.js.map +1 -0
- package/dist/providers/pi/PiAgentMapper.d.ts +9 -0
- package/dist/providers/pi/PiAgentMapper.d.ts.map +1 -0
- package/dist/providers/pi/PiAgentMapper.js +37 -0
- package/dist/providers/pi/PiAgentMapper.js.map +1 -0
- package/dist/providers/pi/PiSessionLocator.d.ts +23 -0
- package/dist/providers/pi/PiSessionLocator.d.ts.map +1 -0
- package/dist/providers/pi/PiSessionLocator.js +79 -0
- package/dist/providers/pi/PiSessionLocator.js.map +1 -0
- package/dist/providers/pi/PiSessionParser.d.ts +36 -0
- package/dist/providers/pi/PiSessionParser.d.ts.map +1 -0
- package/dist/providers/pi/PiSessionParser.js +191 -0
- package/dist/providers/pi/PiSessionParser.js.map +1 -0
- package/dist/providers/pi/PiSessionTracker.d.ts +23 -0
- package/dist/providers/pi/PiSessionTracker.d.ts.map +1 -0
- package/dist/providers/pi/PiSessionTracker.js +69 -0
- package/dist/providers/pi/PiSessionTracker.js.map +1 -0
- package/dist/providers/pi/durable/PiCliProbe.d.ts.map +1 -1
- package/dist/providers/pi/durable/PiCliProbe.js +15 -15
- package/dist/providers/pi/durable/PiCliProbe.js.map +1 -1
- package/dist/providers/pi/durable/PiPrintAgentService.d.ts +6 -6
- package/dist/providers/pi/durable/PiPrintAgentService.d.ts.map +1 -1
- package/dist/providers/pi/durable/PiPrintAgentService.js +10 -10
- package/dist/providers/pi/durable/PiPrintAgentService.js.map +1 -1
- package/dist/providers/pi/durable/PiPrintProtocol.d.ts.map +1 -1
- package/dist/providers/pi/durable/PiPrintProtocol.js +18 -18
- package/dist/providers/pi/durable/PiPrintProtocol.js.map +1 -1
- package/dist/providers/pi/durable/PiPrintRunner.d.ts +4 -4
- package/dist/providers/pi/durable/PiPrintRunner.d.ts.map +1 -1
- package/dist/providers/pi/durable/PiPrintRunner.js +15 -15
- package/dist/providers/pi/durable/PiPrintRunner.js.map +1 -1
- package/dist/providers/pi/durable/PiStreamParser.d.ts +1 -1
- package/dist/providers/pi/durable/PiStreamParser.d.ts.map +1 -1
- package/dist/providers/pi/durable/PiStreamParser.js +16 -16
- package/dist/providers/pi/durable/PiStreamParser.js.map +1 -1
- package/dist/readiness/AgentReadiness.d.ts +67 -0
- package/dist/readiness/AgentReadiness.d.ts.map +1 -0
- package/dist/readiness/AgentReadiness.js +614 -0
- package/dist/readiness/AgentReadiness.js.map +1 -0
- package/dist/runtime/ManagedAgentRuntime.d.ts +69 -0
- package/dist/runtime/ManagedAgentRuntime.d.ts.map +1 -0
- package/dist/runtime/ManagedAgentRuntime.js +156 -0
- package/dist/runtime/ManagedAgentRuntime.js.map +1 -0
- package/dist/runtime/RuntimeFactory.d.ts +4 -0
- package/dist/runtime/RuntimeFactory.d.ts.map +1 -0
- package/dist/runtime/RuntimeFactory.js +11 -0
- package/dist/runtime/RuntimeFactory.js.map +1 -0
- package/dist/runtime/errors.d.ts +27 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +42 -0
- package/dist/runtime/errors.js.map +1 -0
- package/dist/runtime/herdr/HerdrAgentRuntime.d.ts +31 -0
- package/dist/runtime/herdr/HerdrAgentRuntime.d.ts.map +1 -0
- package/dist/runtime/herdr/HerdrAgentRuntime.js +184 -0
- package/dist/runtime/herdr/HerdrAgentRuntime.js.map +1 -0
- package/dist/runtime/herdr/HerdrCliClient.d.ts +13 -0
- package/dist/runtime/herdr/HerdrCliClient.d.ts.map +1 -0
- package/dist/runtime/herdr/HerdrCliClient.js +60 -0
- package/dist/runtime/herdr/HerdrCliClient.js.map +1 -0
- package/dist/runtime/herdr/HerdrErrors.d.ts +7 -0
- package/dist/runtime/herdr/HerdrErrors.d.ts.map +1 -0
- package/dist/runtime/herdr/HerdrErrors.js +10 -0
- package/dist/runtime/herdr/HerdrErrors.js.map +1 -0
- package/dist/runtime/herdr/HerdrResponseParsers.d.ts +14 -0
- package/dist/runtime/herdr/HerdrResponseParsers.d.ts.map +1 -0
- package/dist/runtime/herdr/HerdrResponseParsers.js +96 -0
- package/dist/runtime/herdr/HerdrResponseParsers.js.map +1 -0
- package/dist/runtime/herdr/HerdrRuntimeRef.d.ts +9 -0
- package/dist/runtime/herdr/HerdrRuntimeRef.d.ts.map +1 -0
- package/dist/runtime/herdr/HerdrRuntimeRef.js +33 -0
- package/dist/runtime/herdr/HerdrRuntimeRef.js.map +1 -0
- package/dist/runtime/tmux/PidPolling.d.ts +5 -0
- package/dist/runtime/tmux/PidPolling.d.ts.map +1 -0
- package/dist/runtime/tmux/PidPolling.js +24 -0
- package/dist/runtime/tmux/PidPolling.js.map +1 -0
- package/dist/runtime/tmux/TmuxAgentRuntime.d.ts +17 -0
- package/dist/runtime/tmux/TmuxAgentRuntime.d.ts.map +1 -0
- package/dist/runtime/tmux/TmuxAgentRuntime.js +80 -0
- package/dist/runtime/tmux/TmuxAgentRuntime.js.map +1 -0
- package/dist/runtime/tmux/TmuxRuntimeRef.d.ts +5 -0
- package/dist/runtime/tmux/TmuxRuntimeRef.d.ts.map +1 -0
- package/dist/runtime/tmux/TmuxRuntimeRef.js +9 -0
- package/dist/runtime/tmux/TmuxRuntimeRef.js.map +1 -0
- package/dist/runtime/types.d.ts +54 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +3 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/terminal/TerminalFocusManager.d.ts.map +1 -1
- package/dist/terminal/TerminalFocusManager.js +50 -50
- package/dist/terminal/TerminalFocusManager.js.map +1 -1
- package/dist/terminal/TmuxManager.d.ts.map +1 -1
- package/dist/terminal/TmuxManager.js +47 -47
- package/dist/terminal/TmuxManager.js.map +1 -1
- package/dist/terminal/TtyWriter.d.ts +1 -1
- package/dist/terminal/TtyWriter.d.ts.map +1 -1
- package/dist/terminal/TtyWriter.js +89 -89
- package/dist/terminal/TtyWriter.js.map +1 -1
- package/dist/terminal/index.d.ts +4 -4
- package/dist/terminal/index.js +3 -3
- package/dist/terminal/index.js.map +1 -1
- package/dist/utils/AgentRegistry.d.ts +10 -2
- package/dist/utils/AgentRegistry.d.ts.map +1 -1
- package/dist/utils/AgentRegistry.js +57 -24
- package/dist/utils/AgentRegistry.js.map +1 -1
- package/dist/utils/agent-requests.d.ts.map +1 -1
- package/dist/utils/agent-requests.js +5 -5
- package/dist/utils/agent-requests.js.map +1 -1
- package/dist/utils/agents.d.ts +2 -2
- package/dist/utils/agents.d.ts.map +1 -1
- package/dist/utils/agents.js +15 -15
- package/dist/utils/agents.js.map +1 -1
- package/dist/utils/applescript.js +1 -1
- package/dist/utils/applescript.js.map +1 -1
- package/dist/utils/index.d.ts +6 -6
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -4
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/matching.d.ts +2 -2
- package/dist/utils/matching.d.ts.map +1 -1
- package/dist/utils/matching.js +4 -4
- package/dist/utils/matching.js.map +1 -1
- package/dist/utils/process.d.ts +1 -1
- package/dist/utils/process.d.ts.map +1 -1
- package/dist/utils/process.js +74 -74
- package/dist/utils/process.js.map +1 -1
- package/dist/utils/session.d.ts +1 -1
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +7 -7
- package/dist/utils/session.js.map +1 -1
- package/dist/utils/sortAgents.d.ts +2 -2
- package/dist/utils/sortAgents.d.ts.map +1 -1
- package/dist/utils/sortAgents.js +5 -5
- package/dist/utils/sortAgents.js.map +1 -1
- package/package.json +18 -19
- package/src/AgentManager.ts +283 -281
- package/src/__tests__/AgentManager.test.ts +917 -907
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +1657 -1368
- package/src/__tests__/adapters/CodexAdapter.test.ts +365 -1698
- package/src/__tests__/adapters/CopilotAdapter.test.ts +576 -481
- package/src/__tests__/adapters/GeminiCliAdapter.test.ts +1189 -1157
- package/src/__tests__/adapters/GrokCliAdapter.test.ts +325 -272
- package/src/__tests__/adapters/OpenCodeAdapter.test.ts +510 -455
- package/src/__tests__/capacity/codex.test.ts +178 -91
- package/src/__tests__/capacity/index.test.ts +27 -18
- package/src/__tests__/durable/CodexCliProbe.test.ts +75 -53
- package/src/__tests__/durable/CodexDurableAgentRepository.test.ts +51 -40
- package/src/__tests__/durable/CodexPrintAgent.integration.test.ts +89 -64
- package/src/__tests__/durable/CodexPrintAgentService.test.ts +196 -118
- package/src/__tests__/durable/CodexPrintRunner.test.ts +282 -162
- package/src/__tests__/fixtures/fake-claude.cjs +20 -13
- package/src/__tests__/fixtures/fake-codex.cjs +41 -30
- package/src/__tests__/print/ClaudeCliProbe.test.ts +27 -21
- package/src/__tests__/print/ClaudePrintAgent.integration.test.ts +48 -40
- package/src/__tests__/print/ClaudePrintAgentService.test.ts +118 -67
- package/src/__tests__/print/ClaudePrintRunner.test.ts +136 -83
- package/src/__tests__/print/DurableAgent.test.ts +19 -16
- package/src/__tests__/print/DurableAgentRepository.sqlite.test.ts +109 -86
- package/src/__tests__/print/DurableAgentRepository.test.ts +170 -137
- package/src/__tests__/providers/claude/ClaudeAgentMapper.test.ts +65 -65
- package/src/__tests__/providers/claude/ClaudeSessionLocator.test.ts +400 -377
- package/src/__tests__/providers/codex/CodexSessionLocator.test.ts +182 -0
- package/src/__tests__/providers/codex/CodexSessionMapping.test.ts +63 -0
- package/src/__tests__/providers/codex/CodexSessionParser.test.ts +192 -0
- package/src/__tests__/providers/pi/PiAdapter.test.ts +310 -0
- package/src/__tests__/providers/pi/PiSessionParser.test.ts +190 -0
- package/src/__tests__/providers/pi/PiSessionTracker.test.ts +79 -0
- package/src/__tests__/providers/pi/durable/PiCliProbe.test.ts +48 -32
- package/src/__tests__/providers/pi/durable/PiDurableAgentRepository.test.ts +24 -23
- package/src/__tests__/providers/pi/durable/PiPrintAgentService.test.ts +131 -59
- package/src/__tests__/providers/pi/durable/PiPrintProtocol.test.ts +66 -28
- package/src/__tests__/providers/pi/durable/PiPrintRunner.test.ts +236 -90
- package/src/__tests__/providers/pi/durable/PiStreamParser.test.ts +20 -18
- package/src/__tests__/readiness/AgentReadiness.test.ts +242 -0
- package/src/__tests__/runtime/AgentRuntime.test.ts +8 -0
- package/src/__tests__/runtime/HerdrAgentRuntime.test.ts +358 -0
- package/src/__tests__/runtime/ManagedAgentRuntime.test.ts +555 -0
- package/src/__tests__/terminal/TerminalFocusManager.test.ts +254 -253
- package/src/__tests__/terminal/TmuxManager.test.ts +157 -157
- package/src/__tests__/terminal/TtyWriter.test.ts +423 -419
- package/src/__tests__/utils/AgentRegistry.test.ts +506 -402
- package/src/__tests__/utils/ClaudeSessionParser.test.ts +276 -242
- package/src/__tests__/utils/agent-requests.test.ts +76 -56
- package/src/__tests__/utils/agents.test.ts +20 -20
- package/src/__tests__/utils/matching.test.ts +206 -171
- package/src/__tests__/utils/process.test.ts +276 -262
- package/src/__tests__/utils/session.test.ts +133 -139
- package/src/adapters/AgentAdapter.ts +134 -126
- package/src/adapters/CopilotAdapter.ts +405 -395
- package/src/adapters/GeminiCliAdapter.ts +598 -600
- package/src/adapters/GrokCliAdapter.ts +353 -342
- package/src/adapters/OpenCodeAdapter.ts +285 -259
- package/src/adapters/index.ts +15 -9
- package/src/capacity/codex.ts +137 -84
- package/src/capacity/index.ts +18 -13
- package/src/capacity/types.ts +1 -1
- package/src/database/connection.ts +73 -68
- package/src/database/index.ts +6 -6
- package/src/database/migrations/005_agent_runtime.sql +2 -0
- package/src/database/schema.ts +40 -38
- package/src/durable/DurableAgent.ts +85 -85
- package/src/durable/DurableAgentRepository.ts +409 -260
- package/src/durable/process.ts +22 -21
- package/src/durable/run.ts +61 -51
- package/src/durable/utils.ts +46 -41
- package/src/index.ts +145 -86
- package/src/providers/claude/ClaudeAgentMapper.ts +51 -50
- package/src/providers/claude/ClaudeCodeAdapter.ts +144 -136
- package/src/providers/claude/ClaudeSessionLocator.ts +291 -293
- package/src/providers/claude/ClaudeSessionParser.ts +322 -338
- package/src/providers/claude/durable/ClaudeCliProbe.ts +42 -37
- package/src/providers/claude/durable/ClaudePrintAgentService.ts +63 -50
- package/src/providers/claude/durable/ClaudePrintRunner.ts +79 -69
- package/src/providers/claude/durable/ClaudeStreamParser.ts +91 -62
- package/src/providers/codex/CodexAdapter.ts +205 -0
- package/src/providers/codex/CodexAgentMapper.ts +35 -0
- package/src/providers/codex/CodexSessionLocator.ts +279 -0
- package/src/providers/codex/CodexSessionMapping.ts +89 -0
- package/src/providers/codex/CodexSessionParser.ts +385 -0
- package/src/providers/codex/durable/CodexCliProbe.ts +50 -42
- package/src/providers/codex/durable/CodexPrintAgentService.ts +63 -49
- package/src/providers/codex/durable/CodexPrintRunner.ts +79 -69
- package/src/providers/codex/durable/CodexStreamParser.ts +97 -84
- package/src/providers/pi/PiAdapter.ts +183 -0
- package/src/providers/pi/PiAgentMapper.ts +36 -0
- package/src/providers/pi/PiSessionLocator.ts +95 -0
- package/src/providers/pi/PiSessionParser.ts +284 -0
- package/src/providers/pi/PiSessionTracker.ts +87 -0
- package/src/providers/pi/durable/PiCliProbe.ts +42 -34
- package/src/providers/pi/durable/PiPrintAgentService.ts +61 -53
- package/src/providers/pi/durable/PiPrintProtocol.ts +31 -29
- package/src/providers/pi/durable/PiPrintRunner.ts +75 -68
- package/src/providers/pi/durable/PiStreamParser.ts +82 -72
- package/src/readiness/AgentReadiness.ts +727 -0
- package/src/runtime/ManagedAgentRuntime.ts +266 -0
- package/src/runtime/RuntimeFactory.ts +13 -0
- package/src/runtime/errors.ts +50 -0
- package/src/runtime/herdr/HerdrAgentRuntime.ts +190 -0
- package/src/runtime/herdr/HerdrCliClient.ts +78 -0
- package/src/runtime/herdr/HerdrErrors.ts +9 -0
- package/src/runtime/herdr/HerdrResponseParsers.ts +76 -0
- package/src/runtime/herdr/HerdrRuntimeRef.ts +41 -0
- package/src/runtime/tmux/PidPolling.ts +34 -0
- package/src/runtime/tmux/TmuxAgentRuntime.ts +113 -0
- package/src/runtime/tmux/TmuxRuntimeRef.ts +9 -0
- package/src/runtime/types.ts +56 -0
- package/src/terminal/TerminalFocusManager.ts +215 -212
- package/src/terminal/TmuxManager.ts +99 -92
- package/src/terminal/TtyWriter.ts +203 -194
- package/src/terminal/index.ts +4 -4
- package/src/utils/AgentRegistry.ts +299 -238
- package/src/utils/agent-requests.ts +16 -16
- package/src/utils/agents.ts +41 -38
- package/src/utils/applescript.ts +4 -4
- package/src/utils/index.ts +12 -6
- package/src/utils/matching.ts +43 -43
- package/src/utils/process.ts +261 -258
- package/src/utils/session.ts +66 -66
- package/src/utils/sortAgents.ts +35 -33
- package/tsconfig.json +2 -8
- package/vitest.config.ts +16 -16
- package/.eslintrc.json +0 -31
- package/dist/__tests__/adapters/PiAdapter.test.js +0 -618
- package/dist/__tests__/adapters/PiAdapter.test.js.map +0 -1
- package/dist/__tests__/database/DurableAgentsDatabase.test.js +0 -128
- package/dist/__tests__/database/DurableAgentsDatabase.test.js.map +0 -1
- package/dist/adapters/CodexAdapter.d.ts +0 -109
- package/dist/adapters/CodexAdapter.d.ts.map +0 -1
- package/dist/adapters/CodexAdapter.js +0 -710
- package/dist/adapters/CodexAdapter.js.map +0 -1
- package/dist/adapters/PiAdapter.d.ts +0 -63
- package/dist/adapters/PiAdapter.d.ts.map +0 -1
- package/dist/adapters/PiAdapter.js +0 -453
- package/dist/adapters/PiAdapter.js.map +0 -1
- package/src/__tests__/adapters/PiAdapter.test.ts +0 -458
- package/src/__tests__/database/DurableAgentsDatabase.test.ts +0 -102
- package/src/adapters/CodexAdapter.ts +0 -923
- package/src/adapters/PiAdapter.ts +0 -598
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for GeminiCliAdapter
|
|
3
|
-
*/ import * as fs from
|
|
4
|
-
import * as os from
|
|
5
|
-
import * as path from
|
|
6
|
-
import { GeminiCliAdapter } from
|
|
7
|
-
import { AgentStatus } from
|
|
8
|
-
import { AgentRegistry } from
|
|
9
|
-
import { listAgentProcesses, enrichProcesses, captureProcessSnapshot } from
|
|
10
|
-
import { matchProcessesToSessions, generateAgentName } from
|
|
11
|
-
import * as crypto from
|
|
12
|
-
vi.mock(
|
|
3
|
+
*/ import * as fs from "fs";
|
|
4
|
+
import * as os from "os";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { GeminiCliAdapter } from "../../adapters/GeminiCliAdapter.js";
|
|
7
|
+
import { AgentStatus } from "../../adapters/AgentAdapter.js";
|
|
8
|
+
import { AgentRegistry } from "../../utils/AgentRegistry.js";
|
|
9
|
+
import { listAgentProcesses, enrichProcesses, captureProcessSnapshot } from "../../utils/process.js";
|
|
10
|
+
import { matchProcessesToSessions, generateAgentName } from "../../utils/matching.js";
|
|
11
|
+
import * as crypto from "crypto";
|
|
12
|
+
vi.mock("../../utils/process.js", async (importOriginal)=>{
|
|
13
13
|
const actual = await importOriginal();
|
|
14
14
|
return {
|
|
15
15
|
...actual,
|
|
@@ -18,7 +18,7 @@ vi.mock('../../utils/process.js', async (importOriginal)=>{
|
|
|
18
18
|
captureProcessSnapshot: vi.fn()
|
|
19
19
|
};
|
|
20
20
|
});
|
|
21
|
-
vi.mock(
|
|
21
|
+
vi.mock("../../utils/matching.js", ()=>({
|
|
22
22
|
matchProcessesToSessions: vi.fn(),
|
|
23
23
|
generateAgentName: vi.fn()
|
|
24
24
|
}));
|
|
@@ -27,13 +27,13 @@ const mockedEnrichProcesses = enrichProcesses;
|
|
|
27
27
|
const mockedCaptureProcessSnapshot = captureProcessSnapshot;
|
|
28
28
|
const mockedMatchProcessesToSessions = matchProcessesToSessions;
|
|
29
29
|
const mockedGenerateAgentName = generateAgentName;
|
|
30
|
-
describe(
|
|
30
|
+
describe("GeminiCliAdapter", ()=>{
|
|
31
31
|
let adapter;
|
|
32
32
|
let tmpHome;
|
|
33
33
|
beforeEach(()=>{
|
|
34
|
-
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(),
|
|
34
|
+
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "gemini-adapter-test-"));
|
|
35
35
|
process.env.HOME = tmpHome;
|
|
36
|
-
adapter = new GeminiCliAdapter(new AgentRegistry(path.join(tmpHome,
|
|
36
|
+
adapter = new GeminiCliAdapter(new AgentRegistry(path.join(tmpHome, "agents.json")));
|
|
37
37
|
mockedListAgentProcesses.mockReset();
|
|
38
38
|
mockedEnrichProcesses.mockReset();
|
|
39
39
|
mockedCaptureProcessSnapshot.mockReset();
|
|
@@ -44,7 +44,7 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
44
44
|
mockedCaptureProcessSnapshot.mockImplementation(async (names)=>enrichProcesses(names.flatMap((name)=>listAgentProcesses(name))));
|
|
45
45
|
mockedMatchProcessesToSessions.mockReturnValue([]);
|
|
46
46
|
mockedGenerateAgentName.mockImplementation((cwd, pid)=>{
|
|
47
|
-
const folder = path.basename(cwd) ||
|
|
47
|
+
const folder = path.basename(cwd) || "unknown";
|
|
48
48
|
return `${folder} (${pid})`;
|
|
49
49
|
});
|
|
50
50
|
});
|
|
@@ -54,89 +54,89 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
54
54
|
force: true
|
|
55
55
|
});
|
|
56
56
|
});
|
|
57
|
-
describe(
|
|
58
|
-
it(
|
|
59
|
-
expect(adapter.type).toBe(
|
|
57
|
+
describe("initialization", ()=>{
|
|
58
|
+
it("should expose gemini_cli type", ()=>{
|
|
59
|
+
expect(adapter.type).toBe("gemini_cli");
|
|
60
60
|
});
|
|
61
61
|
});
|
|
62
|
-
describe(
|
|
63
|
-
it(
|
|
62
|
+
describe("canHandle", ()=>{
|
|
63
|
+
it("should return true for plain gemini command", ()=>{
|
|
64
64
|
expect(adapter.canHandle({
|
|
65
65
|
pid: 1,
|
|
66
|
-
command:
|
|
67
|
-
cwd:
|
|
68
|
-
tty:
|
|
66
|
+
command: "gemini",
|
|
67
|
+
cwd: "/repo",
|
|
68
|
+
tty: "ttys001"
|
|
69
69
|
})).toBe(true);
|
|
70
70
|
});
|
|
71
|
-
it(
|
|
71
|
+
it("should return true for gemini with full path (case-insensitive)", ()=>{
|
|
72
72
|
expect(adapter.canHandle({
|
|
73
73
|
pid: 2,
|
|
74
|
-
command:
|
|
75
|
-
cwd:
|
|
76
|
-
tty:
|
|
74
|
+
command: "/usr/local/bin/GEMINI --yolo",
|
|
75
|
+
cwd: "/repo",
|
|
76
|
+
tty: "ttys002"
|
|
77
77
|
})).toBe(true);
|
|
78
78
|
});
|
|
79
|
-
it(
|
|
79
|
+
it("should return false for non-gemini processes", ()=>{
|
|
80
80
|
expect(adapter.canHandle({
|
|
81
81
|
pid: 3,
|
|
82
|
-
command:
|
|
83
|
-
cwd:
|
|
84
|
-
tty:
|
|
82
|
+
command: "node app.js",
|
|
83
|
+
cwd: "/repo",
|
|
84
|
+
tty: "ttys003"
|
|
85
85
|
})).toBe(false);
|
|
86
86
|
});
|
|
87
87
|
it('should return false when "gemini" appears only in path arguments', ()=>{
|
|
88
88
|
expect(adapter.canHandle({
|
|
89
89
|
pid: 4,
|
|
90
|
-
command:
|
|
91
|
-
cwd:
|
|
92
|
-
tty:
|
|
90
|
+
command: "node /path/to/gemini-runner.js",
|
|
91
|
+
cwd: "/repo",
|
|
92
|
+
tty: "ttys004"
|
|
93
93
|
})).toBe(false);
|
|
94
94
|
});
|
|
95
|
-
it(
|
|
95
|
+
it("should return true for Node-invoked gemini script (real install layout)", ()=>{
|
|
96
96
|
expect(adapter.canHandle({
|
|
97
97
|
pid: 5,
|
|
98
|
-
command:
|
|
99
|
-
cwd:
|
|
100
|
-
tty:
|
|
98
|
+
command: "node /Users/foo/.volta/tools/image/node/24.14.0/bin/gemini --help",
|
|
99
|
+
cwd: "/repo",
|
|
100
|
+
tty: "ttys005"
|
|
101
101
|
})).toBe(true);
|
|
102
102
|
});
|
|
103
|
-
it(
|
|
103
|
+
it("should return true for Node-invoked gemini.js bundle entrypoint", ()=>{
|
|
104
104
|
expect(adapter.canHandle({
|
|
105
105
|
pid: 6,
|
|
106
|
-
command:
|
|
107
|
-
cwd:
|
|
108
|
-
tty:
|
|
106
|
+
command: "node /opt/homebrew/lib/node_modules/@google/gemini-cli/bundle/gemini.js",
|
|
107
|
+
cwd: "/repo",
|
|
108
|
+
tty: "ttys006"
|
|
109
109
|
})).toBe(true);
|
|
110
110
|
});
|
|
111
|
-
it(
|
|
111
|
+
it("should recognize Windows executable and entrypoint paths", ()=>{
|
|
112
112
|
expect(adapter.canHandle({
|
|
113
113
|
pid: 7,
|
|
114
|
-
command:
|
|
115
|
-
cwd:
|
|
116
|
-
tty:
|
|
114
|
+
command: "C:\\tools\\node.exe C:\\lib\\gemini.js",
|
|
115
|
+
cwd: "C:\\repo",
|
|
116
|
+
tty: ""
|
|
117
117
|
})).toBe(true);
|
|
118
118
|
});
|
|
119
119
|
});
|
|
120
|
-
describe(
|
|
121
|
-
it(
|
|
120
|
+
describe("detectAgents", ()=>{
|
|
121
|
+
it("should return empty array when no gemini processes are running", async ()=>{
|
|
122
122
|
mockedListAgentProcesses.mockReturnValue([]);
|
|
123
123
|
const agents = await adapter.detectAgents();
|
|
124
124
|
expect(agents).toEqual([]);
|
|
125
125
|
});
|
|
126
|
-
it(
|
|
126
|
+
it("should filter non-gemini Node processes out of the node process pool", async ()=>{
|
|
127
127
|
const geminiProc = {
|
|
128
128
|
pid: 100,
|
|
129
|
-
command:
|
|
130
|
-
cwd:
|
|
131
|
-
tty:
|
|
132
|
-
startTime: new Date(
|
|
129
|
+
command: "node /Users/foo/.volta/tools/image/node/24.14.0/bin/gemini --help",
|
|
130
|
+
cwd: "/repo",
|
|
131
|
+
tty: "ttys001",
|
|
132
|
+
startTime: new Date("2026-04-18T00:00:00Z")
|
|
133
133
|
};
|
|
134
134
|
const unrelatedNodeProc = {
|
|
135
135
|
pid: 200,
|
|
136
|
-
command:
|
|
137
|
-
cwd:
|
|
138
|
-
tty:
|
|
139
|
-
startTime: new Date(
|
|
136
|
+
command: "node /usr/local/bin/eslint src/",
|
|
137
|
+
cwd: "/other-repo",
|
|
138
|
+
tty: "ttys002",
|
|
139
|
+
startTime: new Date("2026-04-18T00:00:00Z")
|
|
140
140
|
};
|
|
141
141
|
mockedListAgentProcesses.mockReturnValue([
|
|
142
142
|
geminiProc,
|
|
@@ -146,13 +146,13 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
146
146
|
expect(agents).toHaveLength(1);
|
|
147
147
|
expect(agents[0].pid).toBe(100);
|
|
148
148
|
});
|
|
149
|
-
it(
|
|
149
|
+
it("should return process-only agents when no session files exist for the process", async ()=>{
|
|
150
150
|
const proc = {
|
|
151
151
|
pid: 1234,
|
|
152
|
-
command:
|
|
153
|
-
cwd:
|
|
154
|
-
tty:
|
|
155
|
-
startTime: new Date(
|
|
152
|
+
command: "node /usr/local/bin/gemini",
|
|
153
|
+
cwd: "/repo",
|
|
154
|
+
tty: "ttys001",
|
|
155
|
+
startTime: new Date("2026-04-18T00:00:00Z")
|
|
156
156
|
};
|
|
157
157
|
mockedListAgentProcesses.mockReturnValue([
|
|
158
158
|
proc
|
|
@@ -160,29 +160,29 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
160
160
|
const agents = await adapter.detectAgents();
|
|
161
161
|
expect(agents).toHaveLength(1);
|
|
162
162
|
expect(agents[0]).toMatchObject({
|
|
163
|
-
type:
|
|
163
|
+
type: "gemini_cli",
|
|
164
164
|
pid: 1234,
|
|
165
|
-
projectPath:
|
|
165
|
+
projectPath: "/repo",
|
|
166
166
|
status: AgentStatus.RUNNING,
|
|
167
|
-
sessionId:
|
|
167
|
+
sessionId: "pid-1234"
|
|
168
168
|
});
|
|
169
169
|
});
|
|
170
|
-
it(
|
|
170
|
+
it("should suppress Gemini wrapper process-only agents before a session file exists", async ()=>{
|
|
171
171
|
const wrapperProc = {
|
|
172
172
|
pid: 20452,
|
|
173
173
|
ppid: 17530,
|
|
174
|
-
command:
|
|
175
|
-
cwd:
|
|
176
|
-
tty:
|
|
177
|
-
startTime: new Date(
|
|
174
|
+
command: "/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini",
|
|
175
|
+
cwd: "/repo",
|
|
176
|
+
tty: "ttys007",
|
|
177
|
+
startTime: new Date("2026-06-13T08:25:21Z")
|
|
178
178
|
};
|
|
179
179
|
const childProc = {
|
|
180
180
|
pid: 21373,
|
|
181
181
|
ppid: 20452,
|
|
182
|
-
command:
|
|
183
|
-
cwd:
|
|
184
|
-
tty:
|
|
185
|
-
startTime: new Date(
|
|
182
|
+
command: "/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini",
|
|
183
|
+
cwd: "/repo",
|
|
184
|
+
tty: "ttys007",
|
|
185
|
+
startTime: new Date("2026-06-13T08:25:26Z")
|
|
186
186
|
};
|
|
187
187
|
mockedListAgentProcesses.mockReturnValue([
|
|
188
188
|
wrapperProc,
|
|
@@ -192,39 +192,42 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
192
192
|
expect(agents).toHaveLength(1);
|
|
193
193
|
expect(agents[0]).toMatchObject({
|
|
194
194
|
pid: 21373,
|
|
195
|
-
sessionId:
|
|
196
|
-
summary:
|
|
195
|
+
sessionId: "pid-21373",
|
|
196
|
+
summary: "Gemini CLI process running"
|
|
197
197
|
});
|
|
198
198
|
});
|
|
199
|
-
it(
|
|
200
|
-
const regPath = path.join(tmpHome,
|
|
199
|
+
it("should carry the managed wrapper name to a process-only child before a session file exists", async ()=>{
|
|
200
|
+
const regPath = path.join(tmpHome, "agents.json");
|
|
201
201
|
const registry = new AgentRegistry(regPath);
|
|
202
202
|
const namedAdapter = new GeminiCliAdapter(registry);
|
|
203
203
|
const wrapperProc = {
|
|
204
204
|
pid: 35792,
|
|
205
205
|
ppid: 33068,
|
|
206
|
-
command:
|
|
207
|
-
cwd:
|
|
208
|
-
tty:
|
|
209
|
-
startTime: new Date(
|
|
206
|
+
command: "/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini",
|
|
207
|
+
cwd: "/repo",
|
|
208
|
+
tty: "ttys002",
|
|
209
|
+
startTime: new Date("2026-06-13T19:15:16Z")
|
|
210
210
|
};
|
|
211
211
|
const childProc = {
|
|
212
212
|
pid: 36514,
|
|
213
213
|
ppid: 35792,
|
|
214
|
-
command:
|
|
215
|
-
cwd:
|
|
216
|
-
tty:
|
|
217
|
-
startTime: new Date(
|
|
214
|
+
command: "/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini",
|
|
215
|
+
cwd: "/repo",
|
|
216
|
+
tty: "ttys002",
|
|
217
|
+
startTime: new Date("2026-06-13T19:15:18Z")
|
|
218
218
|
};
|
|
219
219
|
registry.register({
|
|
220
|
-
name:
|
|
221
|
-
type:
|
|
220
|
+
name: "cli-mqcqj469",
|
|
221
|
+
type: "gemini_cli",
|
|
222
222
|
pid: wrapperProc.pid,
|
|
223
|
-
|
|
223
|
+
runtime: "tmux",
|
|
224
|
+
runtimeRef: {
|
|
225
|
+
session: "cli-mqcqj469"
|
|
226
|
+
},
|
|
224
227
|
cwd: wrapperProc.cwd,
|
|
225
|
-
startedAt:
|
|
228
|
+
startedAt: "2026-06-13T19:15:16.211Z",
|
|
226
229
|
sessionId: `pid-${wrapperProc.pid}`,
|
|
227
|
-
sessionFilePath:
|
|
230
|
+
sessionFilePath: ""
|
|
228
231
|
});
|
|
229
232
|
mockedListAgentProcesses.mockReturnValue([
|
|
230
233
|
wrapperProc,
|
|
@@ -233,42 +236,42 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
233
236
|
const agents = await namedAdapter.detectAgents();
|
|
234
237
|
expect(agents).toHaveLength(1);
|
|
235
238
|
expect(agents[0]).toMatchObject({
|
|
236
|
-
name:
|
|
239
|
+
name: "cli-mqcqj469",
|
|
237
240
|
pid: childProc.pid,
|
|
238
241
|
sessionId: `pid-${childProc.pid}`
|
|
239
242
|
});
|
|
240
243
|
});
|
|
241
|
-
it(
|
|
242
|
-
const cwd =
|
|
244
|
+
it("should map a process to its matching session file via projectHash", async ()=>{
|
|
245
|
+
const cwd = "/repo/project-a";
|
|
243
246
|
const projectHash = hashProjectRoot(cwd);
|
|
244
|
-
const shortId =
|
|
245
|
-
const chatsDir = path.join(tmpHome,
|
|
247
|
+
const shortId = "abc123";
|
|
248
|
+
const chatsDir = path.join(tmpHome, ".gemini", "tmp", shortId, "chats");
|
|
246
249
|
fs.mkdirSync(chatsDir, {
|
|
247
250
|
recursive: true
|
|
248
251
|
});
|
|
249
|
-
const sessionPath = path.join(chatsDir,
|
|
250
|
-
const sessionStart = new Date(
|
|
252
|
+
const sessionPath = path.join(chatsDir, "session-2026-04-18T00-00-session1.json");
|
|
253
|
+
const sessionStart = new Date("2026-04-18T00:00:00Z").toISOString();
|
|
251
254
|
fs.writeFileSync(sessionPath, JSON.stringify({
|
|
252
|
-
sessionId:
|
|
255
|
+
sessionId: "session1",
|
|
253
256
|
projectHash,
|
|
254
257
|
startTime: sessionStart,
|
|
255
258
|
lastUpdated: sessionStart,
|
|
256
|
-
kind:
|
|
259
|
+
kind: "main",
|
|
257
260
|
messages: [
|
|
258
261
|
{
|
|
259
|
-
id:
|
|
262
|
+
id: "m1",
|
|
260
263
|
timestamp: sessionStart,
|
|
261
|
-
type:
|
|
262
|
-
content:
|
|
264
|
+
type: "user",
|
|
265
|
+
content: "hello gemini"
|
|
263
266
|
}
|
|
264
267
|
]
|
|
265
268
|
}));
|
|
266
269
|
const proc = {
|
|
267
270
|
pid: 42,
|
|
268
|
-
command:
|
|
271
|
+
command: "node /usr/local/bin/gemini",
|
|
269
272
|
cwd,
|
|
270
|
-
tty:
|
|
271
|
-
startTime: new Date(
|
|
273
|
+
tty: "ttys001",
|
|
274
|
+
startTime: new Date("2026-04-18T00:00:00Z")
|
|
272
275
|
};
|
|
273
276
|
mockedListAgentProcesses.mockReturnValue([
|
|
274
277
|
proc
|
|
@@ -277,7 +280,7 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
277
280
|
{
|
|
278
281
|
process: proc,
|
|
279
282
|
session: {
|
|
280
|
-
sessionId:
|
|
283
|
+
sessionId: "session1",
|
|
281
284
|
filePath: sessionPath,
|
|
282
285
|
projectDir: chatsDir,
|
|
283
286
|
birthtimeMs: Date.now(),
|
|
@@ -289,55 +292,55 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
289
292
|
const agents = await adapter.detectAgents();
|
|
290
293
|
expect(agents).toHaveLength(1);
|
|
291
294
|
expect(agents[0]).toMatchObject({
|
|
292
|
-
type:
|
|
295
|
+
type: "gemini_cli",
|
|
293
296
|
pid: 42,
|
|
294
297
|
projectPath: cwd,
|
|
295
|
-
sessionId:
|
|
298
|
+
sessionId: "session1",
|
|
296
299
|
sessionFilePath: sessionPath
|
|
297
300
|
});
|
|
298
|
-
expect(agents[0].summary).toContain(
|
|
301
|
+
expect(agents[0].summary).toContain("hello gemini");
|
|
299
302
|
});
|
|
300
|
-
it(
|
|
301
|
-
const cwd =
|
|
303
|
+
it("should collapse Gemini wrapper and child processes matched to the same session", async ()=>{
|
|
304
|
+
const cwd = "/repo/project-a";
|
|
302
305
|
const projectHash = hashProjectRoot(cwd);
|
|
303
|
-
const shortId =
|
|
304
|
-
const chatsDir = path.join(tmpHome,
|
|
306
|
+
const shortId = "abc123";
|
|
307
|
+
const chatsDir = path.join(tmpHome, ".gemini", "tmp", shortId, "chats");
|
|
305
308
|
fs.mkdirSync(chatsDir, {
|
|
306
309
|
recursive: true
|
|
307
310
|
});
|
|
308
|
-
const sessionPath = path.join(chatsDir,
|
|
309
|
-
const sessionStart = new Date(
|
|
311
|
+
const sessionPath = path.join(chatsDir, "session-2026-04-18T00-00-session1.json");
|
|
312
|
+
const sessionStart = new Date("2026-04-18T00:00:00Z").toISOString();
|
|
310
313
|
fs.writeFileSync(sessionPath, JSON.stringify({
|
|
311
|
-
sessionId:
|
|
314
|
+
sessionId: "session1",
|
|
312
315
|
projectHash,
|
|
313
316
|
startTime: sessionStart,
|
|
314
317
|
lastUpdated: sessionStart,
|
|
315
|
-
kind:
|
|
318
|
+
kind: "main",
|
|
316
319
|
directories: [
|
|
317
320
|
cwd
|
|
318
321
|
],
|
|
319
322
|
messages: [
|
|
320
323
|
{
|
|
321
|
-
id:
|
|
324
|
+
id: "m1",
|
|
322
325
|
timestamp: sessionStart,
|
|
323
|
-
type:
|
|
324
|
-
content:
|
|
326
|
+
type: "user",
|
|
327
|
+
content: "hello gemini"
|
|
325
328
|
}
|
|
326
329
|
]
|
|
327
330
|
}));
|
|
328
331
|
const wrapperProc = {
|
|
329
332
|
pid: 88394,
|
|
330
|
-
command:
|
|
333
|
+
command: "node /Users/foo/.nvm/versions/node/v23.9.0/bin/gemini",
|
|
331
334
|
cwd,
|
|
332
|
-
tty:
|
|
333
|
-
startTime: new Date(
|
|
335
|
+
tty: "ttys015",
|
|
336
|
+
startTime: new Date("2026-04-18T00:00:00Z")
|
|
334
337
|
};
|
|
335
338
|
const childProc = {
|
|
336
339
|
pid: 88460,
|
|
337
|
-
command:
|
|
340
|
+
command: "/Users/foo/.nvm/versions/node/v24.13.1/bin/node /Users/foo/.nvm/versions/node/v23.9.0/bin/gemini",
|
|
338
341
|
cwd,
|
|
339
|
-
tty:
|
|
340
|
-
startTime: new Date(
|
|
342
|
+
tty: "ttys015",
|
|
343
|
+
startTime: new Date("2026-04-18T00:00:01Z")
|
|
341
344
|
};
|
|
342
345
|
mockedListAgentProcesses.mockReturnValue([
|
|
343
346
|
wrapperProc,
|
|
@@ -347,7 +350,7 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
347
350
|
{
|
|
348
351
|
process: wrapperProc,
|
|
349
352
|
session: {
|
|
350
|
-
sessionId:
|
|
353
|
+
sessionId: "session1",
|
|
351
354
|
filePath: sessionPath,
|
|
352
355
|
projectDir: chatsDir,
|
|
353
356
|
birthtimeMs: Date.now(),
|
|
@@ -358,7 +361,7 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
358
361
|
{
|
|
359
362
|
process: childProc,
|
|
360
363
|
session: {
|
|
361
|
-
sessionId:
|
|
364
|
+
sessionId: "session1",
|
|
362
365
|
filePath: sessionPath,
|
|
363
366
|
projectDir: chatsDir,
|
|
364
367
|
birthtimeMs: Date.now(),
|
|
@@ -371,32 +374,32 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
371
374
|
expect(agents).toHaveLength(1);
|
|
372
375
|
expect(agents[0]).toMatchObject({
|
|
373
376
|
pid: 88460,
|
|
374
|
-
sessionId:
|
|
377
|
+
sessionId: "session1",
|
|
375
378
|
sessionFilePath: sessionPath
|
|
376
379
|
});
|
|
377
380
|
});
|
|
378
|
-
it(
|
|
379
|
-
const procCwd =
|
|
380
|
-
const otherCwd =
|
|
381
|
+
it("should not match sessions from other projects", async ()=>{
|
|
382
|
+
const procCwd = "/repo/project-a";
|
|
383
|
+
const otherCwd = "/repo/project-b";
|
|
381
384
|
const otherHash = hashProjectRoot(otherCwd);
|
|
382
|
-
const chatsDir = path.join(tmpHome,
|
|
385
|
+
const chatsDir = path.join(tmpHome, ".gemini", "tmp", "other", "chats");
|
|
383
386
|
fs.mkdirSync(chatsDir, {
|
|
384
387
|
recursive: true
|
|
385
388
|
});
|
|
386
|
-
const sessionPath = path.join(chatsDir,
|
|
389
|
+
const sessionPath = path.join(chatsDir, "session-2026-04-18T00-00-other.json");
|
|
387
390
|
fs.writeFileSync(sessionPath, JSON.stringify({
|
|
388
|
-
sessionId:
|
|
391
|
+
sessionId: "other-session",
|
|
389
392
|
projectHash: otherHash,
|
|
390
393
|
startTime: new Date().toISOString(),
|
|
391
394
|
lastUpdated: new Date().toISOString(),
|
|
392
|
-
kind:
|
|
395
|
+
kind: "main",
|
|
393
396
|
messages: []
|
|
394
397
|
}));
|
|
395
398
|
const proc = {
|
|
396
399
|
pid: 7,
|
|
397
|
-
command:
|
|
400
|
+
command: "node /usr/local/bin/gemini",
|
|
398
401
|
cwd: procCwd,
|
|
399
|
-
tty:
|
|
402
|
+
tty: "ttys001",
|
|
400
403
|
startTime: new Date()
|
|
401
404
|
};
|
|
402
405
|
mockedListAgentProcesses.mockReturnValue([
|
|
@@ -409,55 +412,56 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
409
412
|
expect(agents[0].sessionId).toBe(`pid-${proc.pid}`);
|
|
410
413
|
});
|
|
411
414
|
});
|
|
412
|
-
describe(
|
|
415
|
+
describe("detectAgents — registry cache short-circuit", ()=>{
|
|
413
416
|
let regPath;
|
|
414
417
|
let registry;
|
|
415
418
|
let cachedAdapter;
|
|
416
419
|
let sessionFilePath;
|
|
417
420
|
function registerEntry(over = {}) {
|
|
418
421
|
registry.register({
|
|
419
|
-
name:
|
|
420
|
-
type:
|
|
422
|
+
name: "gemini-100",
|
|
423
|
+
type: "gemini_cli",
|
|
421
424
|
pid: 100,
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
425
|
+
runtime: "tmux",
|
|
426
|
+
runtimeRef: null,
|
|
427
|
+
cwd: "/repo-a",
|
|
428
|
+
startedAt: "2026-05-30T00:00:00.000Z",
|
|
429
|
+
sessionId: "s-cached",
|
|
426
430
|
sessionFilePath,
|
|
427
431
|
...over
|
|
428
432
|
});
|
|
429
433
|
}
|
|
430
434
|
beforeEach(()=>{
|
|
431
|
-
regPath = path.join(tmpHome,
|
|
435
|
+
regPath = path.join(tmpHome, "agents.json");
|
|
432
436
|
registry = new AgentRegistry(regPath);
|
|
433
437
|
cachedAdapter = new GeminiCliAdapter(registry);
|
|
434
438
|
const now = new Date().toISOString();
|
|
435
|
-
sessionFilePath = path.join(tmpHome,
|
|
439
|
+
sessionFilePath = path.join(tmpHome, "gemini-session.json");
|
|
436
440
|
fs.writeFileSync(sessionFilePath, JSON.stringify({
|
|
437
|
-
sessionId:
|
|
438
|
-
projectHash:
|
|
441
|
+
sessionId: "s-cached",
|
|
442
|
+
projectHash: "h",
|
|
439
443
|
startTime: now,
|
|
440
444
|
lastUpdated: now,
|
|
441
445
|
directories: [
|
|
442
|
-
|
|
446
|
+
"/repo-a"
|
|
443
447
|
],
|
|
444
448
|
messages: [
|
|
445
449
|
{
|
|
446
|
-
id:
|
|
450
|
+
id: "m1",
|
|
447
451
|
timestamp: now,
|
|
448
|
-
type:
|
|
449
|
-
content:
|
|
452
|
+
type: "user",
|
|
453
|
+
content: "Hello from gemini cache"
|
|
450
454
|
}
|
|
451
455
|
]
|
|
452
456
|
}));
|
|
453
457
|
});
|
|
454
|
-
it(
|
|
458
|
+
it("short-circuits matching when registry has a valid entry", async ()=>{
|
|
455
459
|
registerEntry();
|
|
456
460
|
const proc = {
|
|
457
461
|
pid: 100,
|
|
458
|
-
command:
|
|
459
|
-
cwd:
|
|
460
|
-
tty:
|
|
462
|
+
command: "node /path/to/gemini --help",
|
|
463
|
+
cwd: "/repo-a",
|
|
464
|
+
tty: "ttys001",
|
|
461
465
|
startTime: new Date()
|
|
462
466
|
};
|
|
463
467
|
mockedListAgentProcesses.mockReturnValue([
|
|
@@ -466,101 +470,103 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
466
470
|
const agents = await cachedAdapter.detectAgents();
|
|
467
471
|
expect(agents).toHaveLength(1);
|
|
468
472
|
expect(agents[0]).toMatchObject({
|
|
469
|
-
type:
|
|
473
|
+
type: "gemini_cli",
|
|
470
474
|
pid: 100,
|
|
471
|
-
sessionId:
|
|
472
|
-
summary:
|
|
475
|
+
sessionId: "s-cached",
|
|
476
|
+
summary: "Hello from gemini cache"
|
|
473
477
|
});
|
|
474
478
|
expect(mockedMatchProcessesToSessions).not.toHaveBeenCalled();
|
|
475
479
|
});
|
|
476
|
-
it(
|
|
480
|
+
it("falls through when no registry entry exists for the pid", async ()=>{
|
|
477
481
|
const proc = {
|
|
478
482
|
pid: 100,
|
|
479
|
-
command:
|
|
480
|
-
cwd:
|
|
481
|
-
tty:
|
|
483
|
+
command: "node /path/to/gemini --help",
|
|
484
|
+
cwd: "/repo-a",
|
|
485
|
+
tty: "ttys001",
|
|
482
486
|
startTime: new Date()
|
|
483
487
|
};
|
|
484
488
|
mockedListAgentProcesses.mockReturnValue([
|
|
485
489
|
proc
|
|
486
490
|
]);
|
|
487
491
|
const agents = await cachedAdapter.detectAgents();
|
|
488
|
-
expect(agents[0].sessionId).toBe(
|
|
492
|
+
expect(agents[0].sessionId).toBe("pid-100");
|
|
489
493
|
});
|
|
490
|
-
it(
|
|
494
|
+
it("falls through when registry entry type does not match", async ()=>{
|
|
491
495
|
registerEntry({
|
|
492
|
-
type:
|
|
496
|
+
type: "claude"
|
|
493
497
|
});
|
|
494
498
|
const proc = {
|
|
495
499
|
pid: 100,
|
|
496
|
-
command:
|
|
497
|
-
cwd:
|
|
498
|
-
tty:
|
|
500
|
+
command: "node /path/to/gemini --help",
|
|
501
|
+
cwd: "/repo-a",
|
|
502
|
+
tty: "ttys001",
|
|
499
503
|
startTime: new Date()
|
|
500
504
|
};
|
|
501
505
|
mockedListAgentProcesses.mockReturnValue([
|
|
502
506
|
proc
|
|
503
507
|
]);
|
|
504
508
|
const agents = await cachedAdapter.detectAgents();
|
|
505
|
-
expect(agents[0].sessionId).toBe(
|
|
509
|
+
expect(agents[0].sessionId).toBe("pid-100");
|
|
506
510
|
});
|
|
507
|
-
it(
|
|
511
|
+
it("falls through when the cached session file no longer exists", async ()=>{
|
|
508
512
|
registerEntry({
|
|
509
|
-
sessionFilePath: path.join(tmpHome,
|
|
513
|
+
sessionFilePath: path.join(tmpHome, "deleted.json")
|
|
510
514
|
});
|
|
511
515
|
const proc = {
|
|
512
516
|
pid: 100,
|
|
513
|
-
command:
|
|
514
|
-
cwd:
|
|
515
|
-
tty:
|
|
517
|
+
command: "node /path/to/gemini --help",
|
|
518
|
+
cwd: "/repo-a",
|
|
519
|
+
tty: "ttys001",
|
|
516
520
|
startTime: new Date()
|
|
517
521
|
};
|
|
518
522
|
mockedListAgentProcesses.mockReturnValue([
|
|
519
523
|
proc
|
|
520
524
|
]);
|
|
521
525
|
const agents = await cachedAdapter.detectAgents();
|
|
522
|
-
expect(agents[0].sessionId).toBe(
|
|
526
|
+
expect(agents[0].sessionId).toBe("pid-100");
|
|
523
527
|
});
|
|
524
|
-
it(
|
|
528
|
+
it("carries the managed wrapper name to the detected child process", async ()=>{
|
|
525
529
|
const wrapperProc = {
|
|
526
530
|
pid: 20339,
|
|
527
531
|
ppid: 17570,
|
|
528
|
-
command:
|
|
529
|
-
cwd:
|
|
530
|
-
tty:
|
|
531
|
-
startTime: new Date(
|
|
532
|
+
command: "/opt/homebrew/opt/node/bin/node /opt/homebrew/bin/gemini",
|
|
533
|
+
cwd: "/repo-a",
|
|
534
|
+
tty: "ttys002",
|
|
535
|
+
startTime: new Date("2026-06-13T19:00:53Z")
|
|
532
536
|
};
|
|
533
537
|
const childProc = {
|
|
534
538
|
pid: 21038,
|
|
535
539
|
ppid: 20339,
|
|
536
|
-
command:
|
|
537
|
-
cwd:
|
|
538
|
-
tty:
|
|
539
|
-
startTime: new Date(
|
|
540
|
+
command: "/opt/homebrew/Cellar/node/26.0.0/bin/node --max-old-space-size=8192 /opt/homebrew/bin/gemini",
|
|
541
|
+
cwd: "/repo-a",
|
|
542
|
+
tty: "ttys002",
|
|
543
|
+
startTime: new Date("2026-06-13T19:00:57Z")
|
|
540
544
|
};
|
|
541
545
|
const now = new Date().toISOString();
|
|
542
|
-
sessionFilePath = writeSession(tmpHome,
|
|
543
|
-
sessionId:
|
|
544
|
-
projectHash: hashProjectRoot(
|
|
546
|
+
sessionFilePath = writeSession(tmpHome, "cli-2", "session-2026-06-13T19-00-s-cached", {
|
|
547
|
+
sessionId: "s-cached",
|
|
548
|
+
projectHash: hashProjectRoot("/repo-a"),
|
|
545
549
|
startTime: now,
|
|
546
550
|
lastUpdated: now,
|
|
547
551
|
directories: [
|
|
548
|
-
|
|
552
|
+
"/repo-a"
|
|
549
553
|
],
|
|
550
554
|
messages: [
|
|
551
555
|
{
|
|
552
|
-
id:
|
|
556
|
+
id: "m1",
|
|
553
557
|
timestamp: now,
|
|
554
|
-
type:
|
|
555
|
-
content:
|
|
558
|
+
type: "user",
|
|
559
|
+
content: "Hello from child process"
|
|
556
560
|
}
|
|
557
561
|
]
|
|
558
562
|
});
|
|
559
563
|
registerEntry({
|
|
560
|
-
name:
|
|
564
|
+
name: "cli-mqcq0mg5",
|
|
561
565
|
pid: wrapperProc.pid,
|
|
562
|
-
|
|
563
|
-
|
|
566
|
+
runtimeRef: {
|
|
567
|
+
session: "cli-mqcq0mg5"
|
|
568
|
+
},
|
|
569
|
+
sessionId: "s-cached",
|
|
564
570
|
sessionFilePath
|
|
565
571
|
});
|
|
566
572
|
mockedListAgentProcesses.mockReturnValue([
|
|
@@ -571,11 +577,11 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
571
577
|
{
|
|
572
578
|
process: childProc,
|
|
573
579
|
session: {
|
|
574
|
-
sessionId:
|
|
580
|
+
sessionId: "s-cached",
|
|
575
581
|
filePath: sessionFilePath,
|
|
576
582
|
projectDir: path.dirname(sessionFilePath),
|
|
577
583
|
birthtimeMs: Date.now(),
|
|
578
|
-
resolvedCwd:
|
|
584
|
+
resolvedCwd: "/repo-a"
|
|
579
585
|
},
|
|
580
586
|
deltaMs: 0
|
|
581
587
|
}
|
|
@@ -583,19 +589,19 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
583
589
|
const agents = await cachedAdapter.detectAgents();
|
|
584
590
|
expect(agents).toHaveLength(1);
|
|
585
591
|
expect(agents[0]).toMatchObject({
|
|
586
|
-
name:
|
|
592
|
+
name: "cli-mqcq0mg5",
|
|
587
593
|
pid: childProc.pid,
|
|
588
|
-
sessionId:
|
|
594
|
+
sessionId: "s-cached"
|
|
589
595
|
});
|
|
590
596
|
});
|
|
591
597
|
});
|
|
592
|
-
describe(
|
|
593
|
-
it(
|
|
598
|
+
describe("discoverSessions", ()=>{
|
|
599
|
+
it("should return empty when ~/.gemini/tmp does not exist", ()=>{
|
|
594
600
|
const proc = {
|
|
595
601
|
pid: 1,
|
|
596
|
-
command:
|
|
597
|
-
cwd:
|
|
598
|
-
tty:
|
|
602
|
+
command: "gemini",
|
|
603
|
+
cwd: "/repo",
|
|
604
|
+
tty: "ttys001",
|
|
599
605
|
startTime: new Date()
|
|
600
606
|
};
|
|
601
607
|
// tmp dir absent by default
|
|
@@ -605,20 +611,20 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
605
611
|
expect(result.sessions).toEqual([]);
|
|
606
612
|
expect(result.contentCache.size).toBe(0);
|
|
607
613
|
});
|
|
608
|
-
it(
|
|
614
|
+
it("should skip processes with empty cwd when building the hash map", ()=>{
|
|
609
615
|
const proc = {
|
|
610
616
|
pid: 1,
|
|
611
|
-
command:
|
|
612
|
-
cwd:
|
|
613
|
-
tty:
|
|
617
|
+
command: "gemini",
|
|
618
|
+
cwd: "",
|
|
619
|
+
tty: "ttys001",
|
|
614
620
|
startTime: new Date()
|
|
615
621
|
};
|
|
616
|
-
writeSession(tmpHome,
|
|
617
|
-
sessionId:
|
|
618
|
-
projectHash: hashProjectRoot(
|
|
622
|
+
writeSession(tmpHome, "abc", "session-x", {
|
|
623
|
+
sessionId: "s1",
|
|
624
|
+
projectHash: hashProjectRoot("/some/where"),
|
|
619
625
|
startTime: new Date().toISOString(),
|
|
620
626
|
lastUpdated: new Date().toISOString(),
|
|
621
|
-
kind:
|
|
627
|
+
kind: "main",
|
|
622
628
|
messages: []
|
|
623
629
|
});
|
|
624
630
|
const result = adapter.discoverSessions([
|
|
@@ -626,20 +632,20 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
626
632
|
]);
|
|
627
633
|
expect(result.sessions).toEqual([]);
|
|
628
634
|
});
|
|
629
|
-
it(
|
|
635
|
+
it("should ignore sessions whose projectHash does not match any process cwd", ()=>{
|
|
630
636
|
const proc = {
|
|
631
637
|
pid: 1,
|
|
632
|
-
command:
|
|
633
|
-
cwd:
|
|
634
|
-
tty:
|
|
638
|
+
command: "gemini",
|
|
639
|
+
cwd: "/repo/a",
|
|
640
|
+
tty: "ttys001",
|
|
635
641
|
startTime: new Date()
|
|
636
642
|
};
|
|
637
|
-
writeSession(tmpHome,
|
|
638
|
-
sessionId:
|
|
639
|
-
projectHash: hashProjectRoot(
|
|
643
|
+
writeSession(tmpHome, "other", "session-other", {
|
|
644
|
+
sessionId: "s-other",
|
|
645
|
+
projectHash: hashProjectRoot("/repo/different"),
|
|
640
646
|
startTime: new Date().toISOString(),
|
|
641
647
|
lastUpdated: new Date().toISOString(),
|
|
642
|
-
kind:
|
|
648
|
+
kind: "main",
|
|
643
649
|
messages: []
|
|
644
650
|
});
|
|
645
651
|
const result = adapter.discoverSessions([
|
|
@@ -647,52 +653,52 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
647
653
|
]);
|
|
648
654
|
expect(result.sessions).toEqual([]);
|
|
649
655
|
});
|
|
650
|
-
it(
|
|
651
|
-
const cwd =
|
|
656
|
+
it("should skip malformed JSON files and still return valid ones", ()=>{
|
|
657
|
+
const cwd = "/repo/valid";
|
|
652
658
|
const proc = {
|
|
653
659
|
pid: 1,
|
|
654
|
-
command:
|
|
660
|
+
command: "gemini",
|
|
655
661
|
cwd,
|
|
656
|
-
tty:
|
|
662
|
+
tty: "ttys001",
|
|
657
663
|
startTime: new Date()
|
|
658
664
|
};
|
|
659
|
-
const chatsDir = path.join(tmpHome,
|
|
665
|
+
const chatsDir = path.join(tmpHome, ".gemini", "tmp", "abc", "chats");
|
|
660
666
|
fs.mkdirSync(chatsDir, {
|
|
661
667
|
recursive: true
|
|
662
668
|
});
|
|
663
|
-
fs.writeFileSync(path.join(chatsDir,
|
|
664
|
-
writeSession(tmpHome,
|
|
665
|
-
sessionId:
|
|
669
|
+
fs.writeFileSync(path.join(chatsDir, "session-bad.json"), "{ not valid");
|
|
670
|
+
writeSession(tmpHome, "abc", "session-good", {
|
|
671
|
+
sessionId: "s-good",
|
|
666
672
|
projectHash: hashProjectRoot(cwd),
|
|
667
673
|
startTime: new Date().toISOString(),
|
|
668
674
|
lastUpdated: new Date().toISOString(),
|
|
669
|
-
kind:
|
|
675
|
+
kind: "main",
|
|
670
676
|
messages: []
|
|
671
677
|
});
|
|
672
678
|
const result = adapter.discoverSessions([
|
|
673
679
|
proc
|
|
674
680
|
]);
|
|
675
681
|
expect(result.sessions).toHaveLength(1);
|
|
676
|
-
expect(result.sessions[0].sessionId).toBe(
|
|
682
|
+
expect(result.sessions[0].sessionId).toBe("s-good");
|
|
677
683
|
});
|
|
678
|
-
it(
|
|
679
|
-
const gitRoot =
|
|
680
|
-
const procCwd =
|
|
684
|
+
it("should match sessions whose projectHash is a parent of the process cwd (git root case)", ()=>{
|
|
685
|
+
const gitRoot = "/repo/monorepo";
|
|
686
|
+
const procCwd = "/repo/monorepo/packages/inner";
|
|
681
687
|
const proc = {
|
|
682
688
|
pid: 1,
|
|
683
|
-
command:
|
|
689
|
+
command: "gemini",
|
|
684
690
|
cwd: procCwd,
|
|
685
|
-
tty:
|
|
691
|
+
tty: "ttys001",
|
|
686
692
|
startTime: new Date()
|
|
687
693
|
};
|
|
688
|
-
writeSession(tmpHome,
|
|
689
|
-
sessionId:
|
|
694
|
+
writeSession(tmpHome, "abc", "session-rootmatch", {
|
|
695
|
+
sessionId: "s-root",
|
|
690
696
|
// Gemini CLI stores the hash of the walked-up project root,
|
|
691
697
|
// not the process CWD.
|
|
692
698
|
projectHash: hashProjectRoot(gitRoot),
|
|
693
699
|
startTime: new Date().toISOString(),
|
|
694
700
|
lastUpdated: new Date().toISOString(),
|
|
695
|
-
kind:
|
|
701
|
+
kind: "main",
|
|
696
702
|
messages: []
|
|
697
703
|
});
|
|
698
704
|
const result = adapter.discoverSessions([
|
|
@@ -702,20 +708,20 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
702
708
|
expect(result.sessions[0].resolvedCwd).toBe(procCwd);
|
|
703
709
|
});
|
|
704
710
|
it('should skip files that do not start with "session-"', ()=>{
|
|
705
|
-
const cwd =
|
|
711
|
+
const cwd = "/repo/keep";
|
|
706
712
|
const proc = {
|
|
707
713
|
pid: 1,
|
|
708
|
-
command:
|
|
714
|
+
command: "gemini",
|
|
709
715
|
cwd,
|
|
710
|
-
tty:
|
|
716
|
+
tty: "ttys001",
|
|
711
717
|
startTime: new Date()
|
|
712
718
|
};
|
|
713
|
-
const chatsDir = path.join(tmpHome,
|
|
719
|
+
const chatsDir = path.join(tmpHome, ".gemini", "tmp", "abc", "chats");
|
|
714
720
|
fs.mkdirSync(chatsDir, {
|
|
715
721
|
recursive: true
|
|
716
722
|
});
|
|
717
|
-
fs.writeFileSync(path.join(chatsDir,
|
|
718
|
-
sessionId:
|
|
723
|
+
fs.writeFileSync(path.join(chatsDir, "notsession.json"), JSON.stringify({
|
|
724
|
+
sessionId: "skip",
|
|
719
725
|
projectHash: hashProjectRoot(cwd),
|
|
720
726
|
messages: []
|
|
721
727
|
}));
|
|
@@ -725,183 +731,183 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
725
731
|
expect(result.sessions).toEqual([]);
|
|
726
732
|
});
|
|
727
733
|
});
|
|
728
|
-
describe(
|
|
729
|
-
describe(
|
|
734
|
+
describe("helper methods", ()=>{
|
|
735
|
+
describe("determineStatus", ()=>{
|
|
730
736
|
it('should return "waiting" when the last message is from gemini', ()=>{
|
|
731
737
|
const session = {
|
|
732
|
-
sessionId:
|
|
733
|
-
projectPath:
|
|
734
|
-
summary:
|
|
738
|
+
sessionId: "s",
|
|
739
|
+
projectPath: "",
|
|
740
|
+
summary: "",
|
|
735
741
|
sessionStart: new Date(),
|
|
736
742
|
lastActive: new Date(),
|
|
737
|
-
lastMessageType:
|
|
743
|
+
lastMessageType: "gemini"
|
|
738
744
|
};
|
|
739
745
|
expect(adapter.determineStatus(session)).toBe(AgentStatus.WAITING);
|
|
740
746
|
});
|
|
741
747
|
it('should return "waiting" when the last message is from assistant', ()=>{
|
|
742
748
|
const session = {
|
|
743
|
-
sessionId:
|
|
744
|
-
projectPath:
|
|
745
|
-
summary:
|
|
749
|
+
sessionId: "s",
|
|
750
|
+
projectPath: "",
|
|
751
|
+
summary: "",
|
|
746
752
|
sessionStart: new Date(),
|
|
747
753
|
lastActive: new Date(),
|
|
748
|
-
lastMessageType:
|
|
754
|
+
lastMessageType: "assistant"
|
|
749
755
|
};
|
|
750
756
|
expect(adapter.determineStatus(session)).toBe(AgentStatus.WAITING);
|
|
751
757
|
});
|
|
752
758
|
it('should return "running" when the last message is from the user', ()=>{
|
|
753
759
|
const session = {
|
|
754
|
-
sessionId:
|
|
755
|
-
projectPath:
|
|
756
|
-
summary:
|
|
760
|
+
sessionId: "s",
|
|
761
|
+
projectPath: "",
|
|
762
|
+
summary: "",
|
|
757
763
|
sessionStart: new Date(),
|
|
758
764
|
lastActive: new Date(),
|
|
759
|
-
lastMessageType:
|
|
765
|
+
lastMessageType: "user"
|
|
760
766
|
};
|
|
761
767
|
expect(adapter.determineStatus(session)).toBe(AgentStatus.RUNNING);
|
|
762
768
|
});
|
|
763
769
|
it('should return "idle" when last activity is older than the threshold', ()=>{
|
|
764
770
|
const session = {
|
|
765
|
-
sessionId:
|
|
766
|
-
projectPath:
|
|
767
|
-
summary:
|
|
771
|
+
sessionId: "s",
|
|
772
|
+
projectPath: "",
|
|
773
|
+
summary: "",
|
|
768
774
|
sessionStart: new Date(),
|
|
769
775
|
lastActive: new Date(Date.now() - 10 * 60 * 1000),
|
|
770
|
-
lastMessageType:
|
|
776
|
+
lastMessageType: "gemini"
|
|
771
777
|
};
|
|
772
778
|
expect(adapter.determineStatus(session)).toBe(AgentStatus.IDLE);
|
|
773
779
|
});
|
|
774
780
|
});
|
|
775
|
-
describe(
|
|
776
|
-
it(
|
|
777
|
-
const filePath = writeSession(tmpHome,
|
|
778
|
-
sessionId:
|
|
779
|
-
projectHash:
|
|
780
|
-
startTime:
|
|
781
|
-
lastUpdated:
|
|
782
|
-
kind:
|
|
781
|
+
describe("parseSession", ()=>{
|
|
782
|
+
it("should parse a valid session file", ()=>{
|
|
783
|
+
const filePath = writeSession(tmpHome, "p", "session-a", {
|
|
784
|
+
sessionId: "s1",
|
|
785
|
+
projectHash: "h",
|
|
786
|
+
startTime: "2026-04-18T00:00:00Z",
|
|
787
|
+
lastUpdated: "2026-04-18T00:05:00Z",
|
|
788
|
+
kind: "main",
|
|
783
789
|
directories: [
|
|
784
|
-
|
|
790
|
+
"/repo"
|
|
785
791
|
],
|
|
786
792
|
messages: [
|
|
787
793
|
{
|
|
788
|
-
id:
|
|
789
|
-
timestamp:
|
|
790
|
-
type:
|
|
791
|
-
content:
|
|
794
|
+
id: "m1",
|
|
795
|
+
timestamp: "2026-04-18T00:00:01Z",
|
|
796
|
+
type: "user",
|
|
797
|
+
content: "hello"
|
|
792
798
|
}
|
|
793
799
|
]
|
|
794
800
|
});
|
|
795
801
|
const result = adapter.parseSession(undefined, filePath);
|
|
796
802
|
expect(result).toMatchObject({
|
|
797
|
-
sessionId:
|
|
798
|
-
projectPath:
|
|
799
|
-
summary:
|
|
803
|
+
sessionId: "s1",
|
|
804
|
+
projectPath: "/repo",
|
|
805
|
+
summary: "hello"
|
|
800
806
|
});
|
|
801
807
|
});
|
|
802
|
-
it(
|
|
808
|
+
it("should parse from cached content without reading disk", ()=>{
|
|
803
809
|
const content = JSON.stringify({
|
|
804
|
-
sessionId:
|
|
805
|
-
projectHash:
|
|
806
|
-
startTime:
|
|
807
|
-
lastUpdated:
|
|
810
|
+
sessionId: "s2",
|
|
811
|
+
projectHash: "h",
|
|
812
|
+
startTime: "2026-04-18T00:00:00Z",
|
|
813
|
+
lastUpdated: "2026-04-18T00:00:00Z",
|
|
808
814
|
messages: []
|
|
809
815
|
});
|
|
810
|
-
const result = adapter.parseSession(content,
|
|
811
|
-
expect(result?.sessionId).toBe(
|
|
816
|
+
const result = adapter.parseSession(content, "/does/not/exist.json");
|
|
817
|
+
expect(result?.sessionId).toBe("s2");
|
|
812
818
|
});
|
|
813
|
-
it(
|
|
814
|
-
expect(adapter.parseSession(undefined,
|
|
819
|
+
it("should return null for a missing file with no cached content", ()=>{
|
|
820
|
+
expect(adapter.parseSession(undefined, "/missing.json")).toBeNull();
|
|
815
821
|
});
|
|
816
|
-
it(
|
|
817
|
-
const filePath = path.join(tmpHome,
|
|
818
|
-
fs.writeFileSync(filePath,
|
|
822
|
+
it("should return null when the file is not valid JSON", ()=>{
|
|
823
|
+
const filePath = path.join(tmpHome, "broken.json");
|
|
824
|
+
fs.writeFileSync(filePath, "not json");
|
|
819
825
|
expect(adapter.parseSession(undefined, filePath)).toBeNull();
|
|
820
826
|
});
|
|
821
|
-
it(
|
|
822
|
-
const filePath = path.join(tmpHome,
|
|
827
|
+
it("should return null when sessionId is missing", ()=>{
|
|
828
|
+
const filePath = path.join(tmpHome, "no-id.json");
|
|
823
829
|
fs.writeFileSync(filePath, JSON.stringify({
|
|
824
830
|
messages: []
|
|
825
831
|
}));
|
|
826
832
|
expect(adapter.parseSession(undefined, filePath)).toBeNull();
|
|
827
833
|
});
|
|
828
|
-
it(
|
|
829
|
-
const filePath = writeSession(tmpHome,
|
|
830
|
-
sessionId:
|
|
831
|
-
projectHash:
|
|
832
|
-
startTime:
|
|
833
|
-
lastUpdated:
|
|
834
|
+
it("should default the summary when no user message has content", ()=>{
|
|
835
|
+
const filePath = writeSession(tmpHome, "p", "session-empty", {
|
|
836
|
+
sessionId: "s3",
|
|
837
|
+
projectHash: "h",
|
|
838
|
+
startTime: "2026-04-18T00:00:00Z",
|
|
839
|
+
lastUpdated: "2026-04-18T00:00:00Z",
|
|
834
840
|
messages: [
|
|
835
841
|
{
|
|
836
|
-
id:
|
|
837
|
-
timestamp:
|
|
838
|
-
type:
|
|
839
|
-
content:
|
|
842
|
+
id: "m1",
|
|
843
|
+
timestamp: "2026-04-18T00:00:01Z",
|
|
844
|
+
type: "gemini",
|
|
845
|
+
content: "only assistant"
|
|
840
846
|
}
|
|
841
847
|
]
|
|
842
848
|
});
|
|
843
849
|
const result = adapter.parseSession(undefined, filePath);
|
|
844
|
-
expect(result?.summary).toBe(
|
|
850
|
+
expect(result?.summary).toBe("Gemini CLI session active");
|
|
845
851
|
});
|
|
846
|
-
it(
|
|
847
|
-
const longContent =
|
|
848
|
-
const filePath = writeSession(tmpHome,
|
|
849
|
-
sessionId:
|
|
850
|
-
projectHash:
|
|
851
|
-
startTime:
|
|
852
|
-
lastUpdated:
|
|
852
|
+
it("should truncate long summaries to 120 characters", ()=>{
|
|
853
|
+
const longContent = "x".repeat(200);
|
|
854
|
+
const filePath = writeSession(tmpHome, "p", "session-long", {
|
|
855
|
+
sessionId: "s4",
|
|
856
|
+
projectHash: "h",
|
|
857
|
+
startTime: "2026-04-18T00:00:00Z",
|
|
858
|
+
lastUpdated: "2026-04-18T00:00:00Z",
|
|
853
859
|
messages: [
|
|
854
860
|
{
|
|
855
|
-
id:
|
|
856
|
-
timestamp:
|
|
857
|
-
type:
|
|
861
|
+
id: "m1",
|
|
862
|
+
timestamp: "2026-04-18T00:00:01Z",
|
|
863
|
+
type: "user",
|
|
858
864
|
content: longContent
|
|
859
865
|
}
|
|
860
866
|
]
|
|
861
867
|
});
|
|
862
868
|
const result = adapter.parseSession(undefined, filePath);
|
|
863
869
|
expect(result?.summary.length).toBe(120);
|
|
864
|
-
expect(result?.summary.endsWith(
|
|
870
|
+
expect(result?.summary.endsWith("...")).toBe(true);
|
|
865
871
|
});
|
|
866
|
-
it(
|
|
867
|
-
const filePath = writeSession(tmpHome,
|
|
868
|
-
sessionId:
|
|
869
|
-
projectHash:
|
|
870
|
-
startTime:
|
|
871
|
-
lastUpdated:
|
|
872
|
+
it("should extract summary when user content is an array of parts (real Gemini shape)", ()=>{
|
|
873
|
+
const filePath = writeSession(tmpHome, "p", "session-parts", {
|
|
874
|
+
sessionId: "s-parts",
|
|
875
|
+
projectHash: "h",
|
|
876
|
+
startTime: "2026-04-18T00:00:00Z",
|
|
877
|
+
lastUpdated: "2026-04-18T00:00:00Z",
|
|
872
878
|
messages: [
|
|
873
879
|
{
|
|
874
|
-
id:
|
|
875
|
-
timestamp:
|
|
876
|
-
type:
|
|
880
|
+
id: "m1",
|
|
881
|
+
timestamp: "2026-04-18T00:00:01Z",
|
|
882
|
+
type: "user",
|
|
877
883
|
content: [
|
|
878
884
|
{
|
|
879
|
-
text:
|
|
885
|
+
text: "hello from part"
|
|
880
886
|
},
|
|
881
887
|
{
|
|
882
|
-
text:
|
|
888
|
+
text: " continued"
|
|
883
889
|
}
|
|
884
890
|
]
|
|
885
891
|
}
|
|
886
892
|
]
|
|
887
893
|
});
|
|
888
894
|
const result = adapter.parseSession(undefined, filePath);
|
|
889
|
-
expect(result?.summary).toBe(
|
|
895
|
+
expect(result?.summary).toBe("hello from part continued");
|
|
890
896
|
});
|
|
891
|
-
it(
|
|
892
|
-
const filePath = writeSession(tmpHome,
|
|
893
|
-
sessionId:
|
|
894
|
-
projectHash:
|
|
895
|
-
startTime:
|
|
896
|
-
lastUpdated:
|
|
897
|
+
it("should not throw when user content is an array and there is no displayContent", ()=>{
|
|
898
|
+
const filePath = writeSession(tmpHome, "p", "session-parts-only", {
|
|
899
|
+
sessionId: "s-parts-only",
|
|
900
|
+
projectHash: "h",
|
|
901
|
+
startTime: "2026-04-18T00:00:00Z",
|
|
902
|
+
lastUpdated: "2026-04-18T00:00:00Z",
|
|
897
903
|
messages: [
|
|
898
904
|
{
|
|
899
|
-
id:
|
|
900
|
-
timestamp:
|
|
901
|
-
type:
|
|
905
|
+
id: "m1",
|
|
906
|
+
timestamp: "2026-04-18T00:00:01Z",
|
|
907
|
+
type: "user",
|
|
902
908
|
content: [
|
|
903
909
|
{
|
|
904
|
-
text:
|
|
910
|
+
text: "only via parts"
|
|
905
911
|
}
|
|
906
912
|
]
|
|
907
913
|
}
|
|
@@ -909,115 +915,115 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
909
915
|
});
|
|
910
916
|
expect(()=>adapter.parseSession(undefined, filePath)).not.toThrow();
|
|
911
917
|
});
|
|
912
|
-
it(
|
|
913
|
-
const filePath = writeSession(tmpHome,
|
|
914
|
-
sessionId:
|
|
915
|
-
projectHash:
|
|
916
|
-
startTime:
|
|
917
|
-
lastUpdated:
|
|
918
|
+
it("should drop non-text parts (data/file) when resolving user content", ()=>{
|
|
919
|
+
const filePath = writeSession(tmpHome, "p", "session-mixed-parts", {
|
|
920
|
+
sessionId: "s-mixed",
|
|
921
|
+
projectHash: "h",
|
|
922
|
+
startTime: "2026-04-18T00:00:00Z",
|
|
923
|
+
lastUpdated: "2026-04-18T00:00:00Z",
|
|
918
924
|
messages: [
|
|
919
925
|
{
|
|
920
|
-
id:
|
|
921
|
-
timestamp:
|
|
922
|
-
type:
|
|
926
|
+
id: "m1",
|
|
927
|
+
timestamp: "2026-04-18T00:00:01Z",
|
|
928
|
+
type: "user",
|
|
923
929
|
content: [
|
|
924
930
|
{
|
|
925
|
-
text:
|
|
931
|
+
text: "readable text"
|
|
926
932
|
},
|
|
927
933
|
{
|
|
928
934
|
inlineData: {
|
|
929
|
-
mimeType:
|
|
930
|
-
data:
|
|
935
|
+
mimeType: "image/png",
|
|
936
|
+
data: "base64..."
|
|
931
937
|
}
|
|
932
938
|
},
|
|
933
939
|
{
|
|
934
|
-
text:
|
|
940
|
+
text: " + more"
|
|
935
941
|
}
|
|
936
942
|
]
|
|
937
943
|
}
|
|
938
944
|
]
|
|
939
945
|
});
|
|
940
946
|
const result = adapter.parseSession(undefined, filePath);
|
|
941
|
-
expect(result?.summary).toBe(
|
|
947
|
+
expect(result?.summary).toBe("readable text + more");
|
|
942
948
|
});
|
|
943
|
-
it(
|
|
944
|
-
const filePath = writeSession(tmpHome,
|
|
945
|
-
sessionId:
|
|
946
|
-
projectHash:
|
|
947
|
-
startTime:
|
|
948
|
-
lastUpdated:
|
|
949
|
+
it("should prefer lastUpdated over entry timestamp for lastActive", ()=>{
|
|
950
|
+
const filePath = writeSession(tmpHome, "p", "session-last", {
|
|
951
|
+
sessionId: "s5",
|
|
952
|
+
projectHash: "h",
|
|
953
|
+
startTime: "2026-04-18T00:00:00Z",
|
|
954
|
+
lastUpdated: "2026-04-18T00:10:00Z",
|
|
949
955
|
messages: [
|
|
950
956
|
{
|
|
951
|
-
id:
|
|
952
|
-
timestamp:
|
|
953
|
-
type:
|
|
954
|
-
content:
|
|
957
|
+
id: "m1",
|
|
958
|
+
timestamp: "2026-04-18T00:00:01Z",
|
|
959
|
+
type: "user",
|
|
960
|
+
content: "hi"
|
|
955
961
|
}
|
|
956
962
|
]
|
|
957
963
|
});
|
|
958
964
|
const result = adapter.parseSession(undefined, filePath);
|
|
959
|
-
expect(result?.lastActive.toISOString()).toBe(
|
|
965
|
+
expect(result?.lastActive.toISOString()).toBe("2026-04-18T00:10:00.000Z");
|
|
960
966
|
});
|
|
961
967
|
});
|
|
962
968
|
});
|
|
963
|
-
describe(
|
|
964
|
-
it(
|
|
965
|
-
const sessionPath = path.join(tmpHome,
|
|
969
|
+
describe("getConversation", ()=>{
|
|
970
|
+
it("should return messages from a valid Gemini session file", ()=>{
|
|
971
|
+
const sessionPath = path.join(tmpHome, "session-2026-04-18T00-00-id.json");
|
|
966
972
|
fs.writeFileSync(sessionPath, JSON.stringify({
|
|
967
|
-
sessionId:
|
|
968
|
-
projectHash:
|
|
969
|
-
startTime:
|
|
970
|
-
lastUpdated:
|
|
971
|
-
kind:
|
|
973
|
+
sessionId: "abc",
|
|
974
|
+
projectHash: "hash",
|
|
975
|
+
startTime: "2026-04-18T00:00:00Z",
|
|
976
|
+
lastUpdated: "2026-04-18T00:00:00Z",
|
|
977
|
+
kind: "main",
|
|
972
978
|
messages: [
|
|
973
979
|
{
|
|
974
|
-
id:
|
|
975
|
-
timestamp:
|
|
976
|
-
type:
|
|
977
|
-
content:
|
|
980
|
+
id: "m1",
|
|
981
|
+
timestamp: "2026-04-18T00:00:01Z",
|
|
982
|
+
type: "user",
|
|
983
|
+
content: "hi"
|
|
978
984
|
},
|
|
979
985
|
{
|
|
980
|
-
id:
|
|
981
|
-
timestamp:
|
|
982
|
-
type:
|
|
983
|
-
content:
|
|
986
|
+
id: "m2",
|
|
987
|
+
timestamp: "2026-04-18T00:00:02Z",
|
|
988
|
+
type: "gemini",
|
|
989
|
+
content: "hello"
|
|
984
990
|
},
|
|
985
991
|
{
|
|
986
|
-
id:
|
|
987
|
-
timestamp:
|
|
988
|
-
type:
|
|
989
|
-
content:
|
|
992
|
+
id: "m3",
|
|
993
|
+
timestamp: "2026-04-18T00:00:03Z",
|
|
994
|
+
type: "tool",
|
|
995
|
+
content: "unused"
|
|
990
996
|
}
|
|
991
997
|
]
|
|
992
998
|
}));
|
|
993
999
|
const messages = adapter.getConversation(sessionPath);
|
|
994
1000
|
expect(messages).toEqual([
|
|
995
1001
|
{
|
|
996
|
-
role:
|
|
997
|
-
content:
|
|
998
|
-
timestamp:
|
|
1002
|
+
role: "user",
|
|
1003
|
+
content: "hi",
|
|
1004
|
+
timestamp: "2026-04-18T00:00:01Z"
|
|
999
1005
|
},
|
|
1000
1006
|
{
|
|
1001
|
-
role:
|
|
1002
|
-
content:
|
|
1003
|
-
timestamp:
|
|
1007
|
+
role: "assistant",
|
|
1008
|
+
content: "hello",
|
|
1009
|
+
timestamp: "2026-04-18T00:00:02Z"
|
|
1004
1010
|
}
|
|
1005
1011
|
]);
|
|
1006
1012
|
});
|
|
1007
|
-
it(
|
|
1008
|
-
const sessionPath = path.join(tmpHome,
|
|
1013
|
+
it("should include tool entries when verbose is true", ()=>{
|
|
1014
|
+
const sessionPath = path.join(tmpHome, "session-verbose.json");
|
|
1009
1015
|
fs.writeFileSync(sessionPath, JSON.stringify({
|
|
1010
|
-
sessionId:
|
|
1011
|
-
projectHash:
|
|
1012
|
-
startTime:
|
|
1013
|
-
lastUpdated:
|
|
1014
|
-
kind:
|
|
1016
|
+
sessionId: "abc",
|
|
1017
|
+
projectHash: "hash",
|
|
1018
|
+
startTime: "2026-04-18T00:00:00Z",
|
|
1019
|
+
lastUpdated: "2026-04-18T00:00:00Z",
|
|
1020
|
+
kind: "main",
|
|
1015
1021
|
messages: [
|
|
1016
1022
|
{
|
|
1017
|
-
id:
|
|
1018
|
-
timestamp:
|
|
1019
|
-
type:
|
|
1020
|
-
content:
|
|
1023
|
+
id: "m1",
|
|
1024
|
+
timestamp: "2026-04-18T00:00:01Z",
|
|
1025
|
+
type: "tool",
|
|
1026
|
+
content: "tool call"
|
|
1021
1027
|
}
|
|
1022
1028
|
]
|
|
1023
1029
|
}));
|
|
@@ -1026,125 +1032,125 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
1026
1032
|
});
|
|
1027
1033
|
expect(messages).toEqual([
|
|
1028
1034
|
{
|
|
1029
|
-
role:
|
|
1030
|
-
content:
|
|
1031
|
-
timestamp:
|
|
1035
|
+
role: "system",
|
|
1036
|
+
content: "tool call",
|
|
1037
|
+
timestamp: "2026-04-18T00:00:01Z"
|
|
1032
1038
|
}
|
|
1033
1039
|
]);
|
|
1034
1040
|
});
|
|
1035
|
-
it(
|
|
1036
|
-
expect(adapter.getConversation(
|
|
1037
|
-
const brokenPath = path.join(tmpHome,
|
|
1038
|
-
fs.writeFileSync(brokenPath,
|
|
1041
|
+
it("should return empty array for missing or malformed files", ()=>{
|
|
1042
|
+
expect(adapter.getConversation("/nonexistent/file.json")).toEqual([]);
|
|
1043
|
+
const brokenPath = path.join(tmpHome, "broken.json");
|
|
1044
|
+
fs.writeFileSync(brokenPath, "{ not valid json");
|
|
1039
1045
|
expect(adapter.getConversation(brokenPath)).toEqual([]);
|
|
1040
1046
|
});
|
|
1041
|
-
it(
|
|
1042
|
-
const sessionPath = path.join(tmpHome,
|
|
1047
|
+
it("should prefer displayContent over content when both are present", ()=>{
|
|
1048
|
+
const sessionPath = path.join(tmpHome, "session-display.json");
|
|
1043
1049
|
fs.writeFileSync(sessionPath, JSON.stringify({
|
|
1044
|
-
sessionId:
|
|
1050
|
+
sessionId: "abc",
|
|
1045
1051
|
messages: [
|
|
1046
1052
|
{
|
|
1047
|
-
id:
|
|
1048
|
-
timestamp:
|
|
1049
|
-
type:
|
|
1050
|
-
content:
|
|
1051
|
-
displayContent:
|
|
1053
|
+
id: "m1",
|
|
1054
|
+
timestamp: "2026-04-18T00:00:01Z",
|
|
1055
|
+
type: "user",
|
|
1056
|
+
content: "raw",
|
|
1057
|
+
displayContent: "rendered"
|
|
1052
1058
|
}
|
|
1053
1059
|
]
|
|
1054
1060
|
}));
|
|
1055
1061
|
const messages = adapter.getConversation(sessionPath);
|
|
1056
|
-
expect(messages[0].content).toBe(
|
|
1062
|
+
expect(messages[0].content).toBe("rendered");
|
|
1057
1063
|
});
|
|
1058
|
-
it(
|
|
1059
|
-
const sessionPath = path.join(tmpHome,
|
|
1064
|
+
it("should skip entries with empty content", ()=>{
|
|
1065
|
+
const sessionPath = path.join(tmpHome, "session-empty.json");
|
|
1060
1066
|
fs.writeFileSync(sessionPath, JSON.stringify({
|
|
1061
|
-
sessionId:
|
|
1067
|
+
sessionId: "abc",
|
|
1062
1068
|
messages: [
|
|
1063
1069
|
{
|
|
1064
|
-
id:
|
|
1065
|
-
timestamp:
|
|
1066
|
-
type:
|
|
1067
|
-
content:
|
|
1070
|
+
id: "m1",
|
|
1071
|
+
timestamp: "2026-04-18T00:00:01Z",
|
|
1072
|
+
type: "user",
|
|
1073
|
+
content: ""
|
|
1068
1074
|
},
|
|
1069
1075
|
{
|
|
1070
|
-
id:
|
|
1071
|
-
timestamp:
|
|
1072
|
-
type:
|
|
1073
|
-
content:
|
|
1076
|
+
id: "m2",
|
|
1077
|
+
timestamp: "2026-04-18T00:00:02Z",
|
|
1078
|
+
type: "user",
|
|
1079
|
+
content: "real"
|
|
1074
1080
|
}
|
|
1075
1081
|
]
|
|
1076
1082
|
}));
|
|
1077
1083
|
const messages = adapter.getConversation(sessionPath);
|
|
1078
1084
|
expect(messages).toHaveLength(1);
|
|
1079
|
-
expect(messages[0].content).toBe(
|
|
1085
|
+
expect(messages[0].content).toBe("real");
|
|
1080
1086
|
});
|
|
1081
|
-
it(
|
|
1082
|
-
const sessionPath = path.join(tmpHome,
|
|
1087
|
+
it("should skip entries without a type", ()=>{
|
|
1088
|
+
const sessionPath = path.join(tmpHome, "session-no-type.json");
|
|
1083
1089
|
fs.writeFileSync(sessionPath, JSON.stringify({
|
|
1084
|
-
sessionId:
|
|
1090
|
+
sessionId: "abc",
|
|
1085
1091
|
messages: [
|
|
1086
1092
|
{
|
|
1087
|
-
id:
|
|
1088
|
-
timestamp:
|
|
1089
|
-
content:
|
|
1093
|
+
id: "m1",
|
|
1094
|
+
timestamp: "2026-04-18T00:00:01Z",
|
|
1095
|
+
content: "typeless"
|
|
1090
1096
|
}
|
|
1091
1097
|
]
|
|
1092
1098
|
}));
|
|
1093
1099
|
expect(adapter.getConversation(sessionPath)).toEqual([]);
|
|
1094
1100
|
});
|
|
1095
|
-
it(
|
|
1096
|
-
const sessionPath = path.join(tmpHome,
|
|
1101
|
+
it("should resolve user messages whose content is an array of text parts", ()=>{
|
|
1102
|
+
const sessionPath = path.join(tmpHome, "session-user-parts.json");
|
|
1097
1103
|
fs.writeFileSync(sessionPath, JSON.stringify({
|
|
1098
|
-
sessionId:
|
|
1104
|
+
sessionId: "abc",
|
|
1099
1105
|
messages: [
|
|
1100
1106
|
{
|
|
1101
|
-
id:
|
|
1102
|
-
timestamp:
|
|
1103
|
-
type:
|
|
1107
|
+
id: "m1",
|
|
1108
|
+
timestamp: "2026-04-18T00:00:01Z",
|
|
1109
|
+
type: "user",
|
|
1104
1110
|
content: [
|
|
1105
1111
|
{
|
|
1106
|
-
text:
|
|
1112
|
+
text: "hello"
|
|
1107
1113
|
},
|
|
1108
1114
|
{
|
|
1109
|
-
text:
|
|
1115
|
+
text: " world"
|
|
1110
1116
|
}
|
|
1111
1117
|
]
|
|
1112
1118
|
},
|
|
1113
1119
|
{
|
|
1114
|
-
id:
|
|
1115
|
-
timestamp:
|
|
1116
|
-
type:
|
|
1117
|
-
content:
|
|
1120
|
+
id: "m2",
|
|
1121
|
+
timestamp: "2026-04-18T00:00:02Z",
|
|
1122
|
+
type: "gemini",
|
|
1123
|
+
content: "hi there"
|
|
1118
1124
|
}
|
|
1119
1125
|
]
|
|
1120
1126
|
}));
|
|
1121
1127
|
const messages = adapter.getConversation(sessionPath);
|
|
1122
1128
|
expect(messages).toEqual([
|
|
1123
1129
|
{
|
|
1124
|
-
role:
|
|
1125
|
-
content:
|
|
1126
|
-
timestamp:
|
|
1130
|
+
role: "user",
|
|
1131
|
+
content: "hello world",
|
|
1132
|
+
timestamp: "2026-04-18T00:00:01Z"
|
|
1127
1133
|
},
|
|
1128
1134
|
{
|
|
1129
|
-
role:
|
|
1130
|
-
content:
|
|
1131
|
-
timestamp:
|
|
1135
|
+
role: "assistant",
|
|
1136
|
+
content: "hi there",
|
|
1137
|
+
timestamp: "2026-04-18T00:00:02Z"
|
|
1132
1138
|
}
|
|
1133
1139
|
]);
|
|
1134
1140
|
});
|
|
1135
|
-
it(
|
|
1136
|
-
const sessionPath = path.join(tmpHome,
|
|
1141
|
+
it("should not throw when content is an array but no part carries text", ()=>{
|
|
1142
|
+
const sessionPath = path.join(tmpHome, "session-no-text-parts.json");
|
|
1137
1143
|
fs.writeFileSync(sessionPath, JSON.stringify({
|
|
1138
|
-
sessionId:
|
|
1144
|
+
sessionId: "abc",
|
|
1139
1145
|
messages: [
|
|
1140
1146
|
{
|
|
1141
|
-
id:
|
|
1142
|
-
timestamp:
|
|
1143
|
-
type:
|
|
1147
|
+
id: "m1",
|
|
1148
|
+
timestamp: "2026-04-18T00:00:01Z",
|
|
1149
|
+
type: "user",
|
|
1144
1150
|
content: [
|
|
1145
1151
|
{
|
|
1146
1152
|
inlineData: {
|
|
1147
|
-
mimeType:
|
|
1153
|
+
mimeType: "image/png"
|
|
1148
1154
|
}
|
|
1149
1155
|
}
|
|
1150
1156
|
]
|
|
@@ -1154,57 +1160,57 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
1154
1160
|
expect(()=>adapter.getConversation(sessionPath)).not.toThrow();
|
|
1155
1161
|
expect(adapter.getConversation(sessionPath)).toEqual([]);
|
|
1156
1162
|
});
|
|
1157
|
-
it(
|
|
1158
|
-
const sessionPath = path.join(tmpHome,
|
|
1163
|
+
it("should return empty array when messages is not an array", ()=>{
|
|
1164
|
+
const sessionPath = path.join(tmpHome, "session-bad-messages.json");
|
|
1159
1165
|
fs.writeFileSync(sessionPath, JSON.stringify({
|
|
1160
|
-
sessionId:
|
|
1161
|
-
messages:
|
|
1166
|
+
sessionId: "abc",
|
|
1167
|
+
messages: "not-an-array"
|
|
1162
1168
|
}));
|
|
1163
1169
|
expect(adapter.getConversation(sessionPath)).toEqual([]);
|
|
1164
1170
|
});
|
|
1165
1171
|
});
|
|
1166
|
-
describe(
|
|
1167
|
-
it(
|
|
1172
|
+
describe("listSessions", ()=>{
|
|
1173
|
+
it("returns empty when ~/.gemini/tmp does not exist", async ()=>{
|
|
1168
1174
|
// tmpHome has no .gemini dir by default
|
|
1169
1175
|
const result = await adapter.listSessions();
|
|
1170
1176
|
expect(result).toEqual([]);
|
|
1171
1177
|
});
|
|
1172
|
-
it(
|
|
1173
|
-
writeSession(tmpHome,
|
|
1174
|
-
sessionId:
|
|
1175
|
-
projectHash: hashProjectRoot(
|
|
1176
|
-
startTime:
|
|
1177
|
-
lastUpdated:
|
|
1178
|
+
it("walks every shortId/chats dir and returns sessions", async ()=>{
|
|
1179
|
+
writeSession(tmpHome, "aaa", "session-1", {
|
|
1180
|
+
sessionId: "s-1",
|
|
1181
|
+
projectHash: hashProjectRoot("/repo-a"),
|
|
1182
|
+
startTime: "2025-01-01T00:00:00Z",
|
|
1183
|
+
lastUpdated: "2025-01-01T00:01:00Z",
|
|
1178
1184
|
directories: [
|
|
1179
|
-
|
|
1185
|
+
"/repo-a"
|
|
1180
1186
|
],
|
|
1181
1187
|
messages: [
|
|
1182
1188
|
{
|
|
1183
|
-
type:
|
|
1184
|
-
timestamp:
|
|
1189
|
+
type: "user",
|
|
1190
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
1185
1191
|
content: [
|
|
1186
1192
|
{
|
|
1187
|
-
text:
|
|
1193
|
+
text: "hello a"
|
|
1188
1194
|
}
|
|
1189
1195
|
]
|
|
1190
1196
|
}
|
|
1191
1197
|
]
|
|
1192
1198
|
});
|
|
1193
|
-
writeSession(tmpHome,
|
|
1194
|
-
sessionId:
|
|
1195
|
-
projectHash: hashProjectRoot(
|
|
1196
|
-
startTime:
|
|
1197
|
-
lastUpdated:
|
|
1199
|
+
writeSession(tmpHome, "bbb", "session-2", {
|
|
1200
|
+
sessionId: "s-2",
|
|
1201
|
+
projectHash: hashProjectRoot("/repo-b"),
|
|
1202
|
+
startTime: "2025-01-02T00:00:00Z",
|
|
1203
|
+
lastUpdated: "2025-01-02T00:01:00Z",
|
|
1198
1204
|
directories: [
|
|
1199
|
-
|
|
1205
|
+
"/repo-b"
|
|
1200
1206
|
],
|
|
1201
1207
|
messages: [
|
|
1202
1208
|
{
|
|
1203
|
-
type:
|
|
1204
|
-
timestamp:
|
|
1209
|
+
type: "user",
|
|
1210
|
+
timestamp: "2025-01-02T00:00:00Z",
|
|
1205
1211
|
content: [
|
|
1206
1212
|
{
|
|
1207
|
-
text:
|
|
1213
|
+
text: "hello b"
|
|
1208
1214
|
}
|
|
1209
1215
|
]
|
|
1210
1216
|
}
|
|
@@ -1216,148 +1222,148 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
1216
1222
|
r.sessionId,
|
|
1217
1223
|
r
|
|
1218
1224
|
]));
|
|
1219
|
-
expect(byId[
|
|
1220
|
-
type:
|
|
1221
|
-
cwd:
|
|
1222
|
-
firstUserMessage:
|
|
1225
|
+
expect(byId["s-1"]).toMatchObject({
|
|
1226
|
+
type: "gemini_cli",
|
|
1227
|
+
cwd: "/repo-a",
|
|
1228
|
+
firstUserMessage: "hello a"
|
|
1223
1229
|
});
|
|
1224
|
-
expect(byId[
|
|
1225
|
-
type:
|
|
1226
|
-
cwd:
|
|
1227
|
-
firstUserMessage:
|
|
1230
|
+
expect(byId["s-2"]).toMatchObject({
|
|
1231
|
+
type: "gemini_cli",
|
|
1232
|
+
cwd: "/repo-b",
|
|
1233
|
+
firstUserMessage: "hello b"
|
|
1228
1234
|
});
|
|
1229
1235
|
});
|
|
1230
|
-
it(
|
|
1231
|
-
writeSession(tmpHome,
|
|
1232
|
-
sessionId:
|
|
1233
|
-
projectHash: hashProjectRoot(
|
|
1234
|
-
startTime:
|
|
1236
|
+
it("applies strict-equality cwd filter against directories[0]", async ()=>{
|
|
1237
|
+
writeSession(tmpHome, "aaa", "session-keep", {
|
|
1238
|
+
sessionId: "keep",
|
|
1239
|
+
projectHash: hashProjectRoot("/repo"),
|
|
1240
|
+
startTime: "2025-01-01T00:00:00Z",
|
|
1235
1241
|
directories: [
|
|
1236
|
-
|
|
1242
|
+
"/repo"
|
|
1237
1243
|
],
|
|
1238
1244
|
messages: [
|
|
1239
1245
|
{
|
|
1240
|
-
type:
|
|
1241
|
-
timestamp:
|
|
1242
|
-
content:
|
|
1246
|
+
type: "user",
|
|
1247
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
1248
|
+
content: "yes"
|
|
1243
1249
|
}
|
|
1244
1250
|
]
|
|
1245
1251
|
});
|
|
1246
|
-
writeSession(tmpHome,
|
|
1247
|
-
sessionId:
|
|
1248
|
-
projectHash: hashProjectRoot(
|
|
1249
|
-
startTime:
|
|
1252
|
+
writeSession(tmpHome, "bbb", "session-drop", {
|
|
1253
|
+
sessionId: "drop",
|
|
1254
|
+
projectHash: hashProjectRoot("/other"),
|
|
1255
|
+
startTime: "2025-01-01T00:00:00Z",
|
|
1250
1256
|
directories: [
|
|
1251
|
-
|
|
1257
|
+
"/other"
|
|
1252
1258
|
],
|
|
1253
1259
|
messages: [
|
|
1254
1260
|
{
|
|
1255
|
-
type:
|
|
1256
|
-
timestamp:
|
|
1257
|
-
content:
|
|
1261
|
+
type: "user",
|
|
1262
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
1263
|
+
content: "no"
|
|
1258
1264
|
}
|
|
1259
1265
|
]
|
|
1260
1266
|
});
|
|
1261
1267
|
const result = await adapter.listSessions({
|
|
1262
|
-
cwd:
|
|
1268
|
+
cwd: "/repo"
|
|
1263
1269
|
});
|
|
1264
1270
|
expect(result).toHaveLength(1);
|
|
1265
|
-
expect(result[0].sessionId).toBe(
|
|
1271
|
+
expect(result[0].sessionId).toBe("keep");
|
|
1266
1272
|
});
|
|
1267
|
-
it(
|
|
1268
|
-
const chatsDir = path.join(tmpHome,
|
|
1273
|
+
it("skips malformed JSON files", async ()=>{
|
|
1274
|
+
const chatsDir = path.join(tmpHome, ".gemini", "tmp", "aaa", "chats");
|
|
1269
1275
|
fs.mkdirSync(chatsDir, {
|
|
1270
1276
|
recursive: true
|
|
1271
1277
|
});
|
|
1272
|
-
fs.writeFileSync(path.join(chatsDir,
|
|
1273
|
-
writeSession(tmpHome,
|
|
1274
|
-
sessionId:
|
|
1275
|
-
projectHash: hashProjectRoot(
|
|
1276
|
-
startTime:
|
|
1278
|
+
fs.writeFileSync(path.join(chatsDir, "session-bad.json"), "{ not json");
|
|
1279
|
+
writeSession(tmpHome, "aaa", "session-good", {
|
|
1280
|
+
sessionId: "good",
|
|
1281
|
+
projectHash: hashProjectRoot("/repo"),
|
|
1282
|
+
startTime: "2025-01-01T00:00:00Z",
|
|
1277
1283
|
directories: [
|
|
1278
|
-
|
|
1284
|
+
"/repo"
|
|
1279
1285
|
],
|
|
1280
1286
|
messages: [
|
|
1281
1287
|
{
|
|
1282
|
-
type:
|
|
1283
|
-
timestamp:
|
|
1284
|
-
content:
|
|
1288
|
+
type: "user",
|
|
1289
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
1290
|
+
content: "ok"
|
|
1285
1291
|
}
|
|
1286
1292
|
]
|
|
1287
1293
|
});
|
|
1288
1294
|
const result = await adapter.listSessions();
|
|
1289
1295
|
expect(result).toHaveLength(1);
|
|
1290
|
-
expect(result[0].sessionId).toBe(
|
|
1296
|
+
expect(result[0].sessionId).toBe("good");
|
|
1291
1297
|
});
|
|
1292
|
-
it(
|
|
1293
|
-
writeSession(tmpHome,
|
|
1294
|
-
projectHash: hashProjectRoot(
|
|
1295
|
-
startTime:
|
|
1298
|
+
it("skips files missing sessionId", async ()=>{
|
|
1299
|
+
writeSession(tmpHome, "aaa", "session-no-id", {
|
|
1300
|
+
projectHash: hashProjectRoot("/repo"),
|
|
1301
|
+
startTime: "2025-01-01T00:00:00Z",
|
|
1296
1302
|
directories: [
|
|
1297
|
-
|
|
1303
|
+
"/repo"
|
|
1298
1304
|
],
|
|
1299
1305
|
messages: []
|
|
1300
1306
|
});
|
|
1301
1307
|
const result = await adapter.listSessions();
|
|
1302
1308
|
expect(result).toEqual([]);
|
|
1303
1309
|
});
|
|
1304
|
-
it(
|
|
1305
|
-
writeSession(tmpHome,
|
|
1306
|
-
sessionId:
|
|
1307
|
-
projectHash: hashProjectRoot(
|
|
1308
|
-
startTime:
|
|
1310
|
+
it("captures the first user-typed message as firstUserMessage", async ()=>{
|
|
1311
|
+
writeSession(tmpHome, "aaa", "session-x", {
|
|
1312
|
+
sessionId: "s",
|
|
1313
|
+
projectHash: hashProjectRoot("/repo"),
|
|
1314
|
+
startTime: "2025-01-01T00:00:00Z",
|
|
1309
1315
|
directories: [
|
|
1310
|
-
|
|
1316
|
+
"/repo"
|
|
1311
1317
|
],
|
|
1312
1318
|
messages: [
|
|
1313
1319
|
{
|
|
1314
|
-
type:
|
|
1315
|
-
timestamp:
|
|
1316
|
-
content:
|
|
1320
|
+
type: "gemini",
|
|
1321
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
1322
|
+
content: "preamble"
|
|
1317
1323
|
},
|
|
1318
1324
|
{
|
|
1319
|
-
type:
|
|
1320
|
-
timestamp:
|
|
1325
|
+
type: "user",
|
|
1326
|
+
timestamp: "2025-01-01T00:00:01Z",
|
|
1321
1327
|
content: [
|
|
1322
1328
|
{
|
|
1323
|
-
text:
|
|
1329
|
+
text: "first user"
|
|
1324
1330
|
}
|
|
1325
1331
|
]
|
|
1326
1332
|
},
|
|
1327
1333
|
{
|
|
1328
|
-
type:
|
|
1329
|
-
timestamp:
|
|
1330
|
-
content:
|
|
1334
|
+
type: "user",
|
|
1335
|
+
timestamp: "2025-01-01T00:00:02Z",
|
|
1336
|
+
content: "second user"
|
|
1331
1337
|
}
|
|
1332
1338
|
]
|
|
1333
1339
|
});
|
|
1334
1340
|
const result = await adapter.listSessions({
|
|
1335
|
-
cwd:
|
|
1341
|
+
cwd: "/repo"
|
|
1336
1342
|
});
|
|
1337
1343
|
expect(result).toHaveLength(1);
|
|
1338
|
-
expect(result[0].firstUserMessage).toBe(
|
|
1344
|
+
expect(result[0].firstUserMessage).toBe("first user");
|
|
1339
1345
|
});
|
|
1340
|
-
it(
|
|
1341
|
-
writeSession(tmpHome,
|
|
1342
|
-
sessionId:
|
|
1343
|
-
projectHash: hashProjectRoot(
|
|
1344
|
-
startTime:
|
|
1346
|
+
it("returns empty firstUserMessage when no user message exists", async ()=>{
|
|
1347
|
+
writeSession(tmpHome, "aaa", "session-x", {
|
|
1348
|
+
sessionId: "s",
|
|
1349
|
+
projectHash: hashProjectRoot("/repo"),
|
|
1350
|
+
startTime: "2025-01-01T00:00:00Z",
|
|
1345
1351
|
directories: [
|
|
1346
|
-
|
|
1352
|
+
"/repo"
|
|
1347
1353
|
],
|
|
1348
1354
|
messages: [
|
|
1349
1355
|
{
|
|
1350
|
-
type:
|
|
1351
|
-
timestamp:
|
|
1352
|
-
content:
|
|
1356
|
+
type: "gemini",
|
|
1357
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
1358
|
+
content: "agent only"
|
|
1353
1359
|
}
|
|
1354
1360
|
]
|
|
1355
1361
|
});
|
|
1356
1362
|
const result = await adapter.listSessions({
|
|
1357
|
-
cwd:
|
|
1363
|
+
cwd: "/repo"
|
|
1358
1364
|
});
|
|
1359
1365
|
expect(result).toHaveLength(1);
|
|
1360
|
-
expect(result[0].firstUserMessage).toBe(
|
|
1366
|
+
expect(result[0].firstUserMessage).toBe("");
|
|
1361
1367
|
});
|
|
1362
1368
|
});
|
|
1363
1369
|
});
|
|
@@ -1365,7 +1371,7 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
1365
1371
|
* Write a Gemini session JSON to the temporary home under the expected
|
|
1366
1372
|
* ~/.gemini/tmp/<shortId>/chats/<fileName>.json layout. Returns the full path.
|
|
1367
1373
|
*/ function writeSession(home, shortId, fileName, body) {
|
|
1368
|
-
const chatsDir = path.join(home,
|
|
1374
|
+
const chatsDir = path.join(home, ".gemini", "tmp", shortId, "chats");
|
|
1369
1375
|
fs.mkdirSync(chatsDir, {
|
|
1370
1376
|
recursive: true
|
|
1371
1377
|
});
|
|
@@ -1378,7 +1384,7 @@ describe('GeminiCliAdapter', ()=>{
|
|
|
1378
1384
|
* sha256(projectRoot) as hex.
|
|
1379
1385
|
*/ function hashProjectRoot(projectRoot) {
|
|
1380
1386
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1381
|
-
return crypto.createHash(
|
|
1387
|
+
return crypto.createHash("sha256").update(projectRoot).digest("hex");
|
|
1382
1388
|
}
|
|
1383
1389
|
|
|
1384
1390
|
//# sourceMappingURL=GeminiCliAdapter.test.js.map
|