@ai-devkit/agent-manager 0.32.0 → 0.33.1
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 +13 -4
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +116 -84
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/AgentManager.test.js +623 -391
- 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/GrokCliAdapter.test.js +114 -114
- package/dist/__tests__/adapters/GrokCliAdapter.test.js.map +1 -1
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js +170 -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/copilot/CopilotAdapter.test.js +641 -0
- package/dist/__tests__/providers/copilot/CopilotAdapter.test.js.map +1 -0
- package/dist/__tests__/providers/gemini/GeminiCliAdapter.test.js +1398 -0
- package/dist/__tests__/providers/gemini/GeminiCliAdapter.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 +134 -134
- package/dist/__tests__/readiness/AgentReadiness.test.js.map +1 -1
- package/dist/__tests__/runtime/AgentRuntime.test.js +13 -0
- package/dist/__tests__/runtime/AgentRuntime.test.js.map +1 -0
- package/dist/__tests__/runtime/HerdrAgentDiscovery.test.js +164 -0
- package/dist/__tests__/runtime/HerdrAgentDiscovery.test.js.map +1 -0
- package/dist/__tests__/runtime/HerdrAgentRuntime.test.js +488 -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 +343 -218
- 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/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 +2 -1
- package/dist/adapters/OpenCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/OpenCodeAdapter.js +41 -37
- 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 +48 -43
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -26
- 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/{adapters → providers/copilot}/CopilotAdapter.d.ts +11 -25
- package/dist/providers/copilot/CopilotAdapter.d.ts.map +1 -0
- package/dist/providers/copilot/CopilotAdapter.js +108 -0
- package/dist/providers/copilot/CopilotAdapter.js.map +1 -0
- package/dist/providers/copilot/CopilotAgentMapper.d.ts +9 -0
- package/dist/providers/copilot/CopilotAgentMapper.d.ts.map +1 -0
- package/dist/providers/copilot/CopilotAgentMapper.js +37 -0
- package/dist/providers/copilot/CopilotAgentMapper.js.map +1 -0
- package/dist/providers/copilot/CopilotSessionLocator.d.ts +19 -0
- package/dist/providers/copilot/CopilotSessionLocator.d.ts.map +1 -0
- package/dist/providers/copilot/CopilotSessionLocator.js +41 -0
- package/dist/providers/copilot/CopilotSessionLocator.js.map +1 -0
- package/dist/providers/copilot/CopilotSessionParser.d.ts +30 -0
- package/dist/providers/copilot/CopilotSessionParser.d.ts.map +1 -0
- package/dist/providers/copilot/CopilotSessionParser.js +181 -0
- package/dist/providers/copilot/CopilotSessionParser.js.map +1 -0
- package/dist/providers/gemini/GeminiAgentMapper.d.ts +9 -0
- package/dist/providers/gemini/GeminiAgentMapper.d.ts.map +1 -0
- package/dist/providers/gemini/GeminiAgentMapper.js +37 -0
- package/dist/providers/gemini/GeminiAgentMapper.js.map +1 -0
- package/dist/providers/gemini/GeminiCliAdapter.d.ts +52 -0
- package/dist/providers/gemini/GeminiCliAdapter.d.ts.map +1 -0
- package/dist/providers/gemini/GeminiCliAdapter.js +188 -0
- package/dist/providers/gemini/GeminiCliAdapter.js.map +1 -0
- package/dist/providers/gemini/GeminiSessionLocator.d.ts +30 -0
- package/dist/providers/gemini/GeminiSessionLocator.d.ts.map +1 -0
- package/dist/providers/gemini/GeminiSessionLocator.js +142 -0
- package/dist/providers/gemini/GeminiSessionLocator.js.map +1 -0
- package/dist/providers/gemini/GeminiSessionParser.d.ts +94 -0
- package/dist/providers/gemini/GeminiSessionParser.d.ts.map +1 -0
- package/dist/providers/gemini/GeminiSessionParser.js +166 -0
- package/dist/providers/gemini/GeminiSessionParser.js.map +1 -0
- 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 +6 -6
- package/dist/readiness/AgentReadiness.d.ts.map +1 -1
- package/dist/readiness/AgentReadiness.js +130 -130
- package/dist/readiness/AgentReadiness.js.map +1 -1
- 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/HerdrAgentDiscovery.d.ts +18 -0
- package/dist/runtime/herdr/HerdrAgentDiscovery.d.ts.map +1 -0
- package/dist/runtime/herdr/HerdrAgentDiscovery.js +183 -0
- package/dist/runtime/herdr/HerdrAgentDiscovery.js.map +1 -0
- package/dist/runtime/herdr/HerdrAgentRuntime.d.ts +36 -0
- package/dist/runtime/herdr/HerdrAgentRuntime.d.ts.map +1 -0
- package/dist/runtime/herdr/HerdrAgentRuntime.js +202 -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 +16 -0
- package/dist/runtime/herdr/HerdrResponseParsers.d.ts.map +1 -0
- package/dist/runtime/herdr/HerdrResponseParsers.js +107 -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 +59 -26
- 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 +338 -274
- package/src/__tests__/AgentManager.test.ts +1143 -907
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +1657 -1368
- package/src/__tests__/adapters/CodexAdapter.test.ts +365 -1698
- package/src/__tests__/adapters/GrokCliAdapter.test.ts +325 -272
- package/src/__tests__/adapters/OpenCodeAdapter.test.ts +511 -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/copilot/CopilotAdapter.test.ts +602 -0
- package/src/__tests__/providers/gemini/GeminiCliAdapter.test.ts +1299 -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 +217 -169
- package/src/__tests__/runtime/AgentRuntime.test.ts +8 -0
- package/src/__tests__/runtime/HerdrAgentDiscovery.test.ts +131 -0
- package/src/__tests__/runtime/HerdrAgentRuntime.test.ts +377 -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 +516 -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/GrokCliAdapter.ts +353 -342
- package/src/adapters/OpenCodeAdapter.ts +289 -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 +151 -103
- 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/copilot/CopilotAdapter.ts +145 -0
- package/src/providers/copilot/CopilotAgentMapper.ts +36 -0
- package/src/providers/copilot/CopilotSessionLocator.ts +57 -0
- package/src/providers/copilot/CopilotSessionParser.ts +289 -0
- package/src/providers/gemini/GeminiAgentMapper.ts +36 -0
- package/src/providers/gemini/GeminiCliAdapter.ts +266 -0
- package/src/providers/gemini/GeminiSessionLocator.ts +183 -0
- package/src/providers/gemini/GeminiSessionParser.ts +271 -0
- 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 +585 -513
- 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/HerdrAgentDiscovery.ts +178 -0
- package/src/runtime/herdr/HerdrAgentRuntime.ts +216 -0
- package/src/runtime/herdr/HerdrCliClient.ts +78 -0
- package/src/runtime/herdr/HerdrErrors.ts +9 -0
- package/src/runtime/herdr/HerdrResponseParsers.ts +86 -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 +301 -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/CopilotAdapter.test.js +0 -630
- package/dist/__tests__/adapters/CopilotAdapter.test.js.map +0 -1
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js +0 -1384
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +0 -1
- 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/CopilotAdapter.d.ts.map +0 -1
- package/dist/adapters/CopilotAdapter.js +0 -329
- package/dist/adapters/CopilotAdapter.js.map +0 -1
- package/dist/adapters/GeminiCliAdapter.d.ts +0 -98
- package/dist/adapters/GeminiCliAdapter.d.ts.map +0 -1
- package/dist/adapters/GeminiCliAdapter.js +0 -513
- package/dist/adapters/GeminiCliAdapter.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/CopilotAdapter.test.ts +0 -510
- package/src/__tests__/adapters/GeminiCliAdapter.test.ts +0 -1259
- 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/CopilotAdapter.ts +0 -473
- package/src/adapters/GeminiCliAdapter.ts +0 -701
- package/src/adapters/PiAdapter.ts +0 -598
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for AgentManager
|
|
3
|
-
*/ import fs from
|
|
4
|
-
import os from
|
|
5
|
-
import path from
|
|
6
|
-
import { AgentManager } from
|
|
7
|
-
import { AgentStatus } from
|
|
8
|
-
import { AgentRegistry } from
|
|
3
|
+
*/ import fs from "fs";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { AgentManager } from "../AgentManager.js";
|
|
7
|
+
import { AgentStatus } from "../adapters/AgentAdapter.js";
|
|
8
|
+
import { AgentRegistry } from "../utils/AgentRegistry.js";
|
|
9
9
|
// Mock adapter for testing
|
|
10
10
|
let MockAdapter = class MockAdapter {
|
|
11
11
|
type;
|
|
@@ -56,23 +56,23 @@ let MockAdapter = class MockAdapter {
|
|
|
56
56
|
// Helper to create mock agent
|
|
57
57
|
function createMockAgent(overrides = {}) {
|
|
58
58
|
return {
|
|
59
|
-
name:
|
|
60
|
-
type:
|
|
59
|
+
name: "test-agent",
|
|
60
|
+
type: "claude",
|
|
61
61
|
status: AgentStatus.RUNNING,
|
|
62
|
-
summary:
|
|
62
|
+
summary: "Test summary",
|
|
63
63
|
pid: 12345,
|
|
64
|
-
projectPath:
|
|
65
|
-
sessionId:
|
|
64
|
+
projectPath: "/test/path",
|
|
65
|
+
sessionId: "test-session-id",
|
|
66
66
|
lastActive: new Date(),
|
|
67
67
|
...overrides
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
|
-
describe(
|
|
70
|
+
describe("AgentManager", ()=>{
|
|
71
71
|
let manager;
|
|
72
72
|
let tmpDir;
|
|
73
73
|
beforeEach(()=>{
|
|
74
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(),
|
|
75
|
-
manager = new AgentManager(new AgentRegistry(path.join(tmpDir,
|
|
74
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-manager-"));
|
|
75
|
+
manager = new AgentManager(new AgentRegistry(path.join(tmpDir, "agents.json")));
|
|
76
76
|
});
|
|
77
77
|
afterEach(()=>{
|
|
78
78
|
fs.rmSync(tmpDir, {
|
|
@@ -80,51 +80,51 @@ describe('AgentManager', ()=>{
|
|
|
80
80
|
force: true
|
|
81
81
|
});
|
|
82
82
|
});
|
|
83
|
-
describe(
|
|
84
|
-
it(
|
|
85
|
-
const adapter = new MockAdapter(
|
|
83
|
+
describe("registerAdapter", ()=>{
|
|
84
|
+
it("should register a new adapter", ()=>{
|
|
85
|
+
const adapter = new MockAdapter("claude");
|
|
86
86
|
manager.registerAdapter(adapter);
|
|
87
|
-
expect(manager.hasAdapter(
|
|
87
|
+
expect(manager.hasAdapter("claude")).toBe(true);
|
|
88
88
|
expect(manager.getAdapterCount()).toBe(1);
|
|
89
89
|
});
|
|
90
|
-
it(
|
|
91
|
-
const adapter1 = new MockAdapter(
|
|
92
|
-
const adapter2 = new MockAdapter(
|
|
90
|
+
it("should throw error when registering duplicate adapter type", ()=>{
|
|
91
|
+
const adapter1 = new MockAdapter("claude");
|
|
92
|
+
const adapter2 = new MockAdapter("claude");
|
|
93
93
|
manager.registerAdapter(adapter1);
|
|
94
94
|
expect(()=>manager.registerAdapter(adapter2)).toThrow('Adapter for type "claude" is already registered');
|
|
95
95
|
});
|
|
96
|
-
it(
|
|
97
|
-
const adapter1 = new MockAdapter(
|
|
98
|
-
const adapter2 = new MockAdapter(
|
|
96
|
+
it("should allow registering multiple different adapter types", ()=>{
|
|
97
|
+
const adapter1 = new MockAdapter("claude");
|
|
98
|
+
const adapter2 = new MockAdapter("gemini_cli");
|
|
99
99
|
manager.registerAdapter(adapter1);
|
|
100
100
|
manager.registerAdapter(adapter2);
|
|
101
101
|
expect(manager.getAdapterCount()).toBe(2);
|
|
102
|
-
expect(manager.hasAdapter(
|
|
103
|
-
expect(manager.hasAdapter(
|
|
102
|
+
expect(manager.hasAdapter("claude")).toBe(true);
|
|
103
|
+
expect(manager.hasAdapter("gemini_cli")).toBe(true);
|
|
104
104
|
});
|
|
105
105
|
});
|
|
106
|
-
describe(
|
|
107
|
-
it(
|
|
108
|
-
const adapter = new MockAdapter(
|
|
106
|
+
describe("unregisterAdapter", ()=>{
|
|
107
|
+
it("should unregister an existing adapter", ()=>{
|
|
108
|
+
const adapter = new MockAdapter("claude");
|
|
109
109
|
manager.registerAdapter(adapter);
|
|
110
|
-
const removed = manager.unregisterAdapter(
|
|
110
|
+
const removed = manager.unregisterAdapter("claude");
|
|
111
111
|
expect(removed).toBe(true);
|
|
112
|
-
expect(manager.hasAdapter(
|
|
112
|
+
expect(manager.hasAdapter("claude")).toBe(false);
|
|
113
113
|
expect(manager.getAdapterCount()).toBe(0);
|
|
114
114
|
});
|
|
115
|
-
it(
|
|
116
|
-
const removed = manager.unregisterAdapter(
|
|
115
|
+
it("should return false when unregistering non-existent adapter", ()=>{
|
|
116
|
+
const removed = manager.unregisterAdapter("NonExistent");
|
|
117
117
|
expect(removed).toBe(false);
|
|
118
118
|
});
|
|
119
119
|
});
|
|
120
|
-
describe(
|
|
121
|
-
it(
|
|
120
|
+
describe("getAdapters", ()=>{
|
|
121
|
+
it("should return empty array when no adapters registered", ()=>{
|
|
122
122
|
const adapters = manager.getAdapters();
|
|
123
123
|
expect(adapters).toEqual([]);
|
|
124
124
|
});
|
|
125
|
-
it(
|
|
126
|
-
const adapter1 = new MockAdapter(
|
|
127
|
-
const adapter2 = new MockAdapter(
|
|
125
|
+
it("should return all registered adapters", ()=>{
|
|
126
|
+
const adapter1 = new MockAdapter("claude");
|
|
127
|
+
const adapter2 = new MockAdapter("gemini_cli");
|
|
128
128
|
manager.registerAdapter(adapter1);
|
|
129
129
|
manager.registerAdapter(adapter2);
|
|
130
130
|
const adapters = manager.getAdapters();
|
|
@@ -133,20 +133,20 @@ describe('AgentManager', ()=>{
|
|
|
133
133
|
expect(adapters).toContain(adapter2);
|
|
134
134
|
});
|
|
135
135
|
});
|
|
136
|
-
describe(
|
|
137
|
-
it(
|
|
136
|
+
describe("listAgents", ()=>{
|
|
137
|
+
it("shares one process capture while giving each adapter only its declared executables", async ()=>{
|
|
138
138
|
const processes = [
|
|
139
139
|
{
|
|
140
140
|
pid: 101,
|
|
141
|
-
command:
|
|
142
|
-
cwd:
|
|
143
|
-
tty:
|
|
141
|
+
command: "claude",
|
|
142
|
+
cwd: "/claude",
|
|
143
|
+
tty: "s001"
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
pid: 202,
|
|
147
|
-
command:
|
|
148
|
-
cwd:
|
|
149
|
-
tty:
|
|
147
|
+
command: "node /bin/pi",
|
|
148
|
+
cwd: "/pi",
|
|
149
|
+
tty: "s002"
|
|
150
150
|
}
|
|
151
151
|
];
|
|
152
152
|
const captureSnapshot = vi.fn(async ()=>processes);
|
|
@@ -158,22 +158,22 @@ describe('AgentManager', ()=>{
|
|
|
158
158
|
getConversation: ()=>[],
|
|
159
159
|
listSessions: async ()=>[]
|
|
160
160
|
});
|
|
161
|
-
const claude = createSnapshotAdapter(
|
|
162
|
-
|
|
161
|
+
const claude = createSnapshotAdapter("claude", [
|
|
162
|
+
"claude"
|
|
163
163
|
]);
|
|
164
|
-
const pi = createSnapshotAdapter(
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
const pi = createSnapshotAdapter("pi", [
|
|
165
|
+
"pi",
|
|
166
|
+
"node"
|
|
167
167
|
]);
|
|
168
|
-
const snapshotManager = new AgentManager(new AgentRegistry(path.join(tmpDir,
|
|
168
|
+
const snapshotManager = new AgentManager(new AgentRegistry(path.join(tmpDir, "snapshot-agents.json")), captureSnapshot);
|
|
169
169
|
snapshotManager.registerAdapter(claude);
|
|
170
170
|
snapshotManager.registerAdapter(pi);
|
|
171
171
|
await snapshotManager.listAgents();
|
|
172
172
|
expect(captureSnapshot).toHaveBeenCalledTimes(1);
|
|
173
173
|
expect(captureSnapshot).toHaveBeenCalledWith([
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
"claude",
|
|
175
|
+
"pi",
|
|
176
|
+
"node"
|
|
177
177
|
]);
|
|
178
178
|
expect(claude.detectAgents).toHaveBeenCalledWith({
|
|
179
179
|
processes: [
|
|
@@ -186,31 +186,31 @@ describe('AgentManager', ()=>{
|
|
|
186
186
|
]
|
|
187
187
|
});
|
|
188
188
|
});
|
|
189
|
-
it(
|
|
189
|
+
it("does not expose foreign command arguments to broad Pi and Gemini matchers", async ()=>{
|
|
190
190
|
const processes = [
|
|
191
191
|
{
|
|
192
192
|
pid: 100,
|
|
193
|
-
command:
|
|
194
|
-
cwd:
|
|
195
|
-
tty:
|
|
193
|
+
command: "node /usr/local/lib/gemini.js",
|
|
194
|
+
cwd: "/g",
|
|
195
|
+
tty: "s001"
|
|
196
196
|
},
|
|
197
197
|
{
|
|
198
198
|
pid: 200,
|
|
199
|
-
command:
|
|
200
|
-
cwd:
|
|
201
|
-
tty:
|
|
199
|
+
command: "codex exec --cd /Users/x/repos/gemini",
|
|
200
|
+
cwd: "/c",
|
|
201
|
+
tty: "s002"
|
|
202
202
|
},
|
|
203
203
|
{
|
|
204
204
|
pid: 300,
|
|
205
|
-
command:
|
|
206
|
-
cwd:
|
|
207
|
-
tty:
|
|
205
|
+
command: "node /usr/local/lib/pi.js",
|
|
206
|
+
cwd: "/p",
|
|
207
|
+
tty: "s003"
|
|
208
208
|
},
|
|
209
209
|
{
|
|
210
210
|
pid: 400,
|
|
211
|
-
command:
|
|
212
|
-
cwd:
|
|
213
|
-
tty:
|
|
211
|
+
command: "claude --resume /Users/x/pi/session.jsonl",
|
|
212
|
+
cwd: "/a",
|
|
213
|
+
tty: "s004"
|
|
214
214
|
}
|
|
215
215
|
];
|
|
216
216
|
const captureSnapshot = vi.fn(async ()=>processes);
|
|
@@ -222,20 +222,20 @@ describe('AgentManager', ()=>{
|
|
|
222
222
|
getConversation: ()=>[],
|
|
223
223
|
listSessions: async ()=>[]
|
|
224
224
|
});
|
|
225
|
-
const gemini = createSnapshotAdapter(
|
|
226
|
-
|
|
225
|
+
const gemini = createSnapshotAdapter("gemini_cli", [
|
|
226
|
+
"node"
|
|
227
227
|
]);
|
|
228
|
-
const codex = createSnapshotAdapter(
|
|
229
|
-
|
|
228
|
+
const codex = createSnapshotAdapter("codex", [
|
|
229
|
+
"codex"
|
|
230
230
|
]);
|
|
231
|
-
const pi = createSnapshotAdapter(
|
|
232
|
-
|
|
233
|
-
|
|
231
|
+
const pi = createSnapshotAdapter("pi", [
|
|
232
|
+
"pi",
|
|
233
|
+
"node"
|
|
234
234
|
]);
|
|
235
|
-
const claude = createSnapshotAdapter(
|
|
236
|
-
|
|
235
|
+
const claude = createSnapshotAdapter("claude", [
|
|
236
|
+
"claude"
|
|
237
237
|
]);
|
|
238
|
-
const snapshotManager = new AgentManager(new AgentRegistry(path.join(tmpDir,
|
|
238
|
+
const snapshotManager = new AgentManager(new AgentRegistry(path.join(tmpDir, "filtered-snapshot-agents.json")), captureSnapshot);
|
|
239
239
|
snapshotManager.registerAdapter(gemini);
|
|
240
240
|
snapshotManager.registerAdapter(codex);
|
|
241
241
|
snapshotManager.registerAdapter(pi);
|
|
@@ -243,10 +243,10 @@ describe('AgentManager', ()=>{
|
|
|
243
243
|
await snapshotManager.listAgents();
|
|
244
244
|
expect(captureSnapshot).toHaveBeenCalledTimes(1);
|
|
245
245
|
expect(captureSnapshot).toHaveBeenCalledWith([
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
246
|
+
"node",
|
|
247
|
+
"codex",
|
|
248
|
+
"pi",
|
|
249
|
+
"claude"
|
|
250
250
|
]);
|
|
251
251
|
expect(gemini.detectAgents).toHaveBeenCalledWith({
|
|
252
252
|
processes: [
|
|
@@ -271,107 +271,107 @@ describe('AgentManager', ()=>{
|
|
|
271
271
|
]
|
|
272
272
|
});
|
|
273
273
|
});
|
|
274
|
-
it(
|
|
274
|
+
it("does not pass a snapshot context to legacy adapters", async ()=>{
|
|
275
275
|
const captureSnapshot = vi.fn(async ()=>[]);
|
|
276
|
-
const legacy = new MockAdapter(
|
|
277
|
-
const detect = vi.spyOn(legacy,
|
|
278
|
-
const snapshotManager = new AgentManager(new AgentRegistry(path.join(tmpDir,
|
|
276
|
+
const legacy = new MockAdapter("claude");
|
|
277
|
+
const detect = vi.spyOn(legacy, "detectAgents");
|
|
278
|
+
const snapshotManager = new AgentManager(new AgentRegistry(path.join(tmpDir, "legacy-agents.json")), captureSnapshot);
|
|
279
279
|
snapshotManager.registerAdapter(legacy);
|
|
280
280
|
await snapshotManager.listAgents();
|
|
281
281
|
expect(captureSnapshot).not.toHaveBeenCalled();
|
|
282
282
|
expect(detect).toHaveBeenCalledWith();
|
|
283
283
|
});
|
|
284
|
-
it(
|
|
284
|
+
it("should return empty array when no adapters registered", async ()=>{
|
|
285
285
|
const agents = await manager.listAgents();
|
|
286
286
|
expect(agents).toEqual([]);
|
|
287
287
|
});
|
|
288
|
-
it(
|
|
288
|
+
it("should return agents from single adapter", async ()=>{
|
|
289
289
|
const mockAgents = [
|
|
290
290
|
createMockAgent({
|
|
291
|
-
name:
|
|
291
|
+
name: "agent1"
|
|
292
292
|
}),
|
|
293
293
|
createMockAgent({
|
|
294
|
-
name:
|
|
294
|
+
name: "agent2"
|
|
295
295
|
})
|
|
296
296
|
];
|
|
297
|
-
const adapter = new MockAdapter(
|
|
297
|
+
const adapter = new MockAdapter("claude", mockAgents);
|
|
298
298
|
manager.registerAdapter(adapter);
|
|
299
299
|
const agents = await manager.listAgents();
|
|
300
300
|
expect(agents).toHaveLength(2);
|
|
301
|
-
expect(agents[0].name).toBe(
|
|
302
|
-
expect(agents[1].name).toBe(
|
|
301
|
+
expect(agents[0].name).toBe("agent1");
|
|
302
|
+
expect(agents[1].name).toBe("agent2");
|
|
303
303
|
});
|
|
304
|
-
it(
|
|
304
|
+
it("should aggregate agents from multiple adapters", async ()=>{
|
|
305
305
|
const claudeAgents = [
|
|
306
306
|
createMockAgent({
|
|
307
|
-
name:
|
|
308
|
-
type:
|
|
307
|
+
name: "claude-agent",
|
|
308
|
+
type: "claude"
|
|
309
309
|
})
|
|
310
310
|
];
|
|
311
311
|
const geminiAgents = [
|
|
312
312
|
createMockAgent({
|
|
313
|
-
name:
|
|
314
|
-
type:
|
|
313
|
+
name: "gemini-agent",
|
|
314
|
+
type: "gemini_cli"
|
|
315
315
|
})
|
|
316
316
|
];
|
|
317
|
-
manager.registerAdapter(new MockAdapter(
|
|
318
|
-
manager.registerAdapter(new MockAdapter(
|
|
317
|
+
manager.registerAdapter(new MockAdapter("claude", claudeAgents));
|
|
318
|
+
manager.registerAdapter(new MockAdapter("gemini_cli", geminiAgents));
|
|
319
319
|
const agents = await manager.listAgents();
|
|
320
320
|
expect(agents).toHaveLength(2);
|
|
321
|
-
expect(agents.find((a)=>a.name ===
|
|
322
|
-
expect(agents.find((a)=>a.name ===
|
|
321
|
+
expect(agents.find((a)=>a.name === "claude-agent")).toBeDefined();
|
|
322
|
+
expect(agents.find((a)=>a.name === "gemini-agent")).toBeDefined();
|
|
323
323
|
});
|
|
324
|
-
it(
|
|
324
|
+
it("should sort agents by status priority (waiting first)", async ()=>{
|
|
325
325
|
const mockAgents = [
|
|
326
326
|
createMockAgent({
|
|
327
|
-
name:
|
|
327
|
+
name: "idle-agent",
|
|
328
328
|
status: AgentStatus.IDLE
|
|
329
329
|
}),
|
|
330
330
|
createMockAgent({
|
|
331
|
-
name:
|
|
331
|
+
name: "waiting-agent",
|
|
332
332
|
status: AgentStatus.WAITING
|
|
333
333
|
}),
|
|
334
334
|
createMockAgent({
|
|
335
|
-
name:
|
|
335
|
+
name: "running-agent",
|
|
336
336
|
status: AgentStatus.RUNNING
|
|
337
337
|
}),
|
|
338
338
|
createMockAgent({
|
|
339
|
-
name:
|
|
339
|
+
name: "unknown-agent",
|
|
340
340
|
status: AgentStatus.UNKNOWN
|
|
341
341
|
})
|
|
342
342
|
];
|
|
343
|
-
const adapter = new MockAdapter(
|
|
343
|
+
const adapter = new MockAdapter("claude", mockAgents);
|
|
344
344
|
manager.registerAdapter(adapter);
|
|
345
345
|
const agents = await manager.listAgents();
|
|
346
|
-
expect(agents[0].name).toBe(
|
|
347
|
-
expect(agents[1].name).toBe(
|
|
348
|
-
expect(agents[2].name).toBe(
|
|
349
|
-
expect(agents[3].name).toBe(
|
|
346
|
+
expect(agents[0].name).toBe("waiting-agent");
|
|
347
|
+
expect(agents[1].name).toBe("running-agent");
|
|
348
|
+
expect(agents[2].name).toBe("idle-agent");
|
|
349
|
+
expect(agents[3].name).toBe("unknown-agent");
|
|
350
350
|
});
|
|
351
|
-
it(
|
|
352
|
-
const goodAdapter = new MockAdapter(
|
|
351
|
+
it("should handle adapter errors gracefully", async ()=>{
|
|
352
|
+
const goodAdapter = new MockAdapter("claude", [
|
|
353
353
|
createMockAgent({
|
|
354
|
-
name:
|
|
354
|
+
name: "good-agent"
|
|
355
355
|
})
|
|
356
356
|
]);
|
|
357
|
-
const badAdapter = new MockAdapter(
|
|
357
|
+
const badAdapter = new MockAdapter("gemini_cli", [], true); // Will fail
|
|
358
358
|
manager.registerAdapter(goodAdapter);
|
|
359
359
|
manager.registerAdapter(badAdapter);
|
|
360
360
|
// Should not throw, should return results from working adapter
|
|
361
361
|
const agents = await manager.listAgents();
|
|
362
362
|
expect(agents).toHaveLength(1);
|
|
363
|
-
expect(agents[0].name).toBe(
|
|
363
|
+
expect(agents[0].name).toBe("good-agent");
|
|
364
364
|
});
|
|
365
|
-
it(
|
|
366
|
-
const adapter1 = new MockAdapter(
|
|
367
|
-
const adapter2 = new MockAdapter(
|
|
365
|
+
it("should return empty array when all adapters fail", async ()=>{
|
|
366
|
+
const adapter1 = new MockAdapter("claude", [], true);
|
|
367
|
+
const adapter2 = new MockAdapter("gemini_cli", [], true);
|
|
368
368
|
manager.registerAdapter(adapter1);
|
|
369
369
|
manager.registerAdapter(adapter2);
|
|
370
370
|
const agents = await manager.listAgents();
|
|
371
371
|
expect(agents).toEqual([]);
|
|
372
372
|
});
|
|
373
373
|
});
|
|
374
|
-
describe(
|
|
374
|
+
describe("listAgents — registry persistence", ()=>{
|
|
375
375
|
let tmpDir;
|
|
376
376
|
let regPath;
|
|
377
377
|
let registry;
|
|
@@ -379,9 +379,9 @@ describe('AgentManager', ()=>{
|
|
|
379
379
|
let nowMs;
|
|
380
380
|
let databaseOperations;
|
|
381
381
|
beforeEach(()=>{
|
|
382
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(),
|
|
383
|
-
regPath = path.join(tmpDir,
|
|
384
|
-
nowMs = Date.parse(
|
|
382
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-manager-"));
|
|
383
|
+
regPath = path.join(tmpDir, "agents.json");
|
|
384
|
+
nowMs = Date.parse("2026-08-14T10:00:00.000Z");
|
|
385
385
|
databaseOperations = [];
|
|
386
386
|
registry = new AgentRegistry(regPath, {
|
|
387
387
|
now: ()=>new Date(nowMs),
|
|
@@ -398,149 +398,375 @@ describe('AgentManager', ()=>{
|
|
|
398
398
|
force: true
|
|
399
399
|
});
|
|
400
400
|
});
|
|
401
|
-
|
|
402
|
-
|
|
401
|
+
function useHerdrPanes(panes) {
|
|
402
|
+
const fetchHerdrPanes = vi.fn().mockResolvedValue(panes);
|
|
403
|
+
scopedManager = new AgentManager(registry, undefined, {
|
|
404
|
+
runtimeProvider: "herdr",
|
|
405
|
+
fetchHerdrAgentPanes: fetchHerdrPanes
|
|
406
|
+
});
|
|
407
|
+
return fetchHerdrPanes;
|
|
408
|
+
}
|
|
409
|
+
it("persists every detected agent to the registry", async ()=>{
|
|
410
|
+
scopedManager.registerAdapter(new MockAdapter("claude", [
|
|
403
411
|
createMockAgent({
|
|
404
|
-
name:
|
|
412
|
+
name: "a",
|
|
405
413
|
pid: process.pid,
|
|
406
|
-
sessionId:
|
|
407
|
-
sessionFilePath:
|
|
408
|
-
projectPath:
|
|
414
|
+
sessionId: "sid-a",
|
|
415
|
+
sessionFilePath: "/path/a.jsonl",
|
|
416
|
+
projectPath: "/cwd/a"
|
|
409
417
|
})
|
|
410
418
|
]));
|
|
411
419
|
await scopedManager.listAgents();
|
|
412
420
|
const entries = registry.list();
|
|
413
421
|
expect(entries).toHaveLength(1);
|
|
414
422
|
expect(entries[0]).toMatchObject({
|
|
415
|
-
name:
|
|
416
|
-
type:
|
|
423
|
+
name: "a",
|
|
424
|
+
type: "claude",
|
|
417
425
|
pid: process.pid,
|
|
418
|
-
cwd:
|
|
419
|
-
sessionId:
|
|
420
|
-
sessionFilePath:
|
|
421
|
-
|
|
426
|
+
cwd: "/cwd/a",
|
|
427
|
+
sessionId: "sid-a",
|
|
428
|
+
sessionFilePath: "/path/a.jsonl",
|
|
429
|
+
runtime: "tmux",
|
|
430
|
+
runtimeRef: null
|
|
422
431
|
});
|
|
423
432
|
expect(entries[0].startedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
|
|
424
433
|
});
|
|
425
|
-
it(
|
|
434
|
+
it("persists a Herdr runtime ref for manually discovered agents matched by session id", async ()=>{
|
|
435
|
+
useHerdrPanes([
|
|
436
|
+
{
|
|
437
|
+
agent: "codex",
|
|
438
|
+
agentSessionId: "01a0a05e-751f-7850-9fd7-247e297f9ac5",
|
|
439
|
+
cwd: "/cwd/ai-devkit",
|
|
440
|
+
paneId: "w31:p1",
|
|
441
|
+
workspaceId: "w31",
|
|
442
|
+
tabId: "w31:t1"
|
|
443
|
+
}
|
|
444
|
+
]);
|
|
445
|
+
scopedManager.registerAdapter(new MockAdapter("codex", [
|
|
446
|
+
createMockAgent({
|
|
447
|
+
name: "ai-devkit-44290",
|
|
448
|
+
type: "codex",
|
|
449
|
+
pid: process.pid,
|
|
450
|
+
sessionId: "01a0a05e-751f-7850-9fd7-247e297f9ac5",
|
|
451
|
+
sessionFilePath: "/path/codex.jsonl",
|
|
452
|
+
projectPath: "/cwd/ai-devkit"
|
|
453
|
+
})
|
|
454
|
+
]));
|
|
455
|
+
await scopedManager.listAgents();
|
|
456
|
+
expect(registry.list()[0]).toMatchObject({
|
|
457
|
+
name: "ai-devkit-44290",
|
|
458
|
+
type: "codex",
|
|
459
|
+
pid: process.pid,
|
|
460
|
+
runtime: "herdr",
|
|
461
|
+
runtimeRef: {
|
|
462
|
+
session: "default",
|
|
463
|
+
paneId: "w31:p1",
|
|
464
|
+
workspaceId: "w31",
|
|
465
|
+
tabId: "w31:t1",
|
|
466
|
+
agentName: "ai-devkit-44290"
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
it("upgrades an existing tmux fallback row when Herdr later matches by session id", async ()=>{
|
|
471
|
+
useHerdrPanes([
|
|
472
|
+
{
|
|
473
|
+
agent: "codex",
|
|
474
|
+
agentSessionId: "01a0a05e-751f-7850-9fd7-247e297f9ac5",
|
|
475
|
+
cwd: "/cwd/ai-devkit",
|
|
476
|
+
paneId: "w31:p1"
|
|
477
|
+
}
|
|
478
|
+
]);
|
|
426
479
|
registry.register({
|
|
427
|
-
name:
|
|
428
|
-
type:
|
|
480
|
+
name: "ai-devkit-44290",
|
|
481
|
+
type: "codex",
|
|
482
|
+
pid: process.pid,
|
|
483
|
+
runtime: "tmux",
|
|
484
|
+
runtimeRef: null,
|
|
485
|
+
cwd: "/cwd/ai-devkit",
|
|
486
|
+
startedAt: "2026-09-14T00:00:00.000Z",
|
|
487
|
+
sessionId: "01a0a05e-751f-7850-9fd7-247e297f9ac5",
|
|
488
|
+
sessionFilePath: "/path/codex.jsonl"
|
|
489
|
+
});
|
|
490
|
+
scopedManager.registerAdapter(new MockAdapter("codex", [
|
|
491
|
+
createMockAgent({
|
|
492
|
+
name: "ai-devkit-44290",
|
|
493
|
+
type: "codex",
|
|
494
|
+
pid: process.pid,
|
|
495
|
+
sessionId: "01a0a05e-751f-7850-9fd7-247e297f9ac5",
|
|
496
|
+
sessionFilePath: "/path/codex.jsonl",
|
|
497
|
+
projectPath: "/cwd/ai-devkit"
|
|
498
|
+
})
|
|
499
|
+
]));
|
|
500
|
+
await scopedManager.listAgents();
|
|
501
|
+
expect(registry.lookup("ai-devkit-44290")).toMatchObject({
|
|
502
|
+
runtime: "herdr",
|
|
503
|
+
runtimeRef: {
|
|
504
|
+
session: "default",
|
|
505
|
+
paneId: "w31:p1",
|
|
506
|
+
agentName: "ai-devkit-44290"
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
it("upgrades an existing tmux fallback row when Herdr matches by foreground pid", async ()=>{
|
|
511
|
+
useHerdrPanes([
|
|
512
|
+
{
|
|
513
|
+
agent: "codex",
|
|
514
|
+
cwd: "/cwd/ai-devkit",
|
|
515
|
+
paneId: "w31:p1",
|
|
516
|
+
foregroundPids: [
|
|
517
|
+
44290
|
|
518
|
+
]
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
agent: "codex",
|
|
522
|
+
cwd: "/cwd/ai-devkit",
|
|
523
|
+
paneId: "w32:p1",
|
|
524
|
+
foregroundPids: [
|
|
525
|
+
process.pid
|
|
526
|
+
]
|
|
527
|
+
}
|
|
528
|
+
]);
|
|
529
|
+
registry.register({
|
|
530
|
+
name: "ai-devkit-64904",
|
|
531
|
+
type: "codex",
|
|
532
|
+
pid: process.pid,
|
|
533
|
+
runtime: "tmux",
|
|
534
|
+
runtimeRef: null,
|
|
535
|
+
cwd: "/cwd/ai-devkit",
|
|
536
|
+
startedAt: "2026-09-14T00:00:00.000Z",
|
|
537
|
+
sessionId: `pid-${process.pid}`,
|
|
538
|
+
sessionFilePath: ""
|
|
539
|
+
});
|
|
540
|
+
scopedManager.registerAdapter(new MockAdapter("codex", [
|
|
541
|
+
createMockAgent({
|
|
542
|
+
name: "ai-devkit-64904",
|
|
543
|
+
type: "codex",
|
|
544
|
+
pid: process.pid,
|
|
545
|
+
sessionId: `pid-${process.pid}`,
|
|
546
|
+
sessionFilePath: undefined,
|
|
547
|
+
projectPath: "/cwd/ai-devkit"
|
|
548
|
+
})
|
|
549
|
+
]));
|
|
550
|
+
await scopedManager.listAgents();
|
|
551
|
+
expect(registry.lookup("ai-devkit-64904")).toMatchObject({
|
|
552
|
+
runtime: "herdr",
|
|
553
|
+
runtimeRef: {
|
|
554
|
+
session: "default",
|
|
555
|
+
paneId: "w32:p1",
|
|
556
|
+
agentName: "ai-devkit-64904"
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
it("fetches Herdr panes automatically when the configured runtime is Herdr", async ()=>{
|
|
561
|
+
const fetchHerdrPanes = useHerdrPanes([
|
|
562
|
+
{
|
|
563
|
+
agent: "codex",
|
|
564
|
+
cwd: "/cwd/ai-devkit",
|
|
565
|
+
paneId: "w32:p1",
|
|
566
|
+
foregroundPids: [
|
|
567
|
+
process.pid
|
|
568
|
+
]
|
|
569
|
+
}
|
|
570
|
+
]);
|
|
571
|
+
scopedManager.registerAdapter(new MockAdapter("codex", [
|
|
572
|
+
createMockAgent({
|
|
573
|
+
name: "ai-devkit-64904",
|
|
574
|
+
type: "codex",
|
|
575
|
+
pid: process.pid,
|
|
576
|
+
sessionId: `pid-${process.pid}`,
|
|
577
|
+
sessionFilePath: undefined,
|
|
578
|
+
projectPath: "/cwd/ai-devkit"
|
|
579
|
+
})
|
|
580
|
+
]));
|
|
581
|
+
await scopedManager.listAgents();
|
|
582
|
+
expect(fetchHerdrPanes).toHaveBeenCalledOnce();
|
|
583
|
+
expect(registry.lookup("ai-devkit-64904")).toMatchObject({
|
|
584
|
+
runtime: "herdr",
|
|
585
|
+
runtimeRef: {
|
|
586
|
+
session: "default",
|
|
587
|
+
paneId: "w32:p1",
|
|
588
|
+
agentName: "ai-devkit-64904"
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
it("upgrades an existing tmux row with a malformed runtime ref when Herdr matches", async ()=>{
|
|
593
|
+
useHerdrPanes([
|
|
594
|
+
{
|
|
595
|
+
agent: "codex",
|
|
596
|
+
cwd: "/cwd/ai-devkit",
|
|
597
|
+
paneId: "w32:p1",
|
|
598
|
+
foregroundPids: [
|
|
599
|
+
process.pid
|
|
600
|
+
]
|
|
601
|
+
}
|
|
602
|
+
]);
|
|
603
|
+
registry.register({
|
|
604
|
+
name: "ai-devkit-64904",
|
|
605
|
+
type: "codex",
|
|
606
|
+
pid: process.pid,
|
|
607
|
+
runtime: "tmux",
|
|
608
|
+
runtimeRef: {},
|
|
609
|
+
cwd: "/cwd/ai-devkit",
|
|
610
|
+
startedAt: "2026-09-14T00:00:00.000Z",
|
|
611
|
+
sessionId: `pid-${process.pid}`,
|
|
612
|
+
sessionFilePath: ""
|
|
613
|
+
});
|
|
614
|
+
scopedManager.registerAdapter(new MockAdapter("codex", [
|
|
615
|
+
createMockAgent({
|
|
616
|
+
name: "ai-devkit-64904",
|
|
617
|
+
type: "codex",
|
|
618
|
+
pid: process.pid,
|
|
619
|
+
sessionId: `pid-${process.pid}`,
|
|
620
|
+
sessionFilePath: undefined,
|
|
621
|
+
projectPath: "/cwd/ai-devkit"
|
|
622
|
+
})
|
|
623
|
+
]));
|
|
624
|
+
await scopedManager.listAgents();
|
|
625
|
+
expect(registry.lookup("ai-devkit-64904")).toMatchObject({
|
|
626
|
+
runtime: "herdr",
|
|
627
|
+
runtimeRef: {
|
|
628
|
+
session: "default",
|
|
629
|
+
paneId: "w32:p1",
|
|
630
|
+
agentName: "ai-devkit-64904"
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
it("prunes entries for dead pids", async ()=>{
|
|
635
|
+
registry.register({
|
|
636
|
+
name: "dead",
|
|
637
|
+
type: "claude",
|
|
429
638
|
pid: 999999,
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
639
|
+
runtime: "tmux",
|
|
640
|
+
runtimeRef: null,
|
|
641
|
+
cwd: "/cwd/dead",
|
|
642
|
+
startedAt: "2026-05-30T00:00:00.000Z",
|
|
643
|
+
sessionId: "sid-dead",
|
|
644
|
+
sessionFilePath: "/path/dead.jsonl"
|
|
435
645
|
});
|
|
436
|
-
scopedManager.registerAdapter(new MockAdapter(
|
|
646
|
+
scopedManager.registerAdapter(new MockAdapter("claude", [
|
|
437
647
|
createMockAgent({
|
|
438
|
-
name:
|
|
648
|
+
name: "live",
|
|
439
649
|
pid: process.pid
|
|
440
650
|
})
|
|
441
651
|
]));
|
|
442
652
|
await scopedManager.listAgents();
|
|
443
653
|
const entries = registry.list();
|
|
444
654
|
expect(entries.map((e)=>e.name)).toEqual([
|
|
445
|
-
|
|
655
|
+
"live"
|
|
446
656
|
]);
|
|
447
657
|
});
|
|
448
658
|
it('preserves an existing name (e.g. user-set "merry") across cycles', async ()=>{
|
|
449
659
|
registry.register({
|
|
450
|
-
name:
|
|
451
|
-
type:
|
|
660
|
+
name: "merry",
|
|
661
|
+
type: "claude",
|
|
452
662
|
pid: process.pid,
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
663
|
+
runtime: "tmux",
|
|
664
|
+
runtimeRef: {
|
|
665
|
+
session: "merry"
|
|
666
|
+
},
|
|
667
|
+
cwd: "/cwd/merry",
|
|
668
|
+
startedAt: "2026-05-30T00:00:00.000Z",
|
|
669
|
+
sessionId: "sid-merry",
|
|
670
|
+
sessionFilePath: "/path/merry.jsonl"
|
|
458
671
|
});
|
|
459
|
-
scopedManager.registerAdapter(new MockAdapter(
|
|
672
|
+
scopedManager.registerAdapter(new MockAdapter("claude", [
|
|
460
673
|
createMockAgent({
|
|
461
|
-
name:
|
|
674
|
+
name: "default-name",
|
|
462
675
|
pid: process.pid
|
|
463
676
|
})
|
|
464
677
|
]));
|
|
465
678
|
const agents = await scopedManager.listAgents();
|
|
466
|
-
expect(agents[0].name).toBe(
|
|
467
|
-
expect(registry.list()[0].name).toBe(
|
|
468
|
-
expect(registry.list()[0].
|
|
469
|
-
|
|
679
|
+
expect(agents[0].name).toBe("merry");
|
|
680
|
+
expect(registry.list()[0].name).toBe("merry");
|
|
681
|
+
expect(registry.list()[0].runtimeRef).toEqual({
|
|
682
|
+
session: "merry"
|
|
683
|
+
});
|
|
684
|
+
expect(registry.list()[0].startedAt).toBe("2026-05-30T00:00:00.000Z");
|
|
470
685
|
});
|
|
471
|
-
it(
|
|
686
|
+
it("preserves custom name and tmux runtime ref across two EPERM refresh cycles", async ()=>{
|
|
472
687
|
registry.register({
|
|
473
|
-
name:
|
|
474
|
-
type:
|
|
688
|
+
name: "merry",
|
|
689
|
+
type: "claude",
|
|
475
690
|
pid: process.pid,
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
691
|
+
runtime: "tmux",
|
|
692
|
+
runtimeRef: {
|
|
693
|
+
session: "merry-tmux"
|
|
694
|
+
},
|
|
695
|
+
cwd: "/cwd/merry",
|
|
696
|
+
startedAt: "2026-05-30T00:00:00.000Z",
|
|
697
|
+
sessionId: "sid-merry",
|
|
698
|
+
sessionFilePath: "/path/merry.jsonl"
|
|
481
699
|
});
|
|
482
|
-
scopedManager.registerAdapter(new MockAdapter(
|
|
700
|
+
scopedManager.registerAdapter(new MockAdapter("claude", [
|
|
483
701
|
createMockAgent({
|
|
484
702
|
name: `ai-devkit-${process.pid}`,
|
|
485
703
|
pid: process.pid
|
|
486
704
|
})
|
|
487
705
|
]));
|
|
488
|
-
vi.spyOn(process,
|
|
489
|
-
throw Object.assign(new Error(
|
|
490
|
-
code:
|
|
706
|
+
vi.spyOn(process, "kill").mockImplementation(()=>{
|
|
707
|
+
throw Object.assign(new Error("operation not permitted"), {
|
|
708
|
+
code: "EPERM"
|
|
491
709
|
});
|
|
492
710
|
});
|
|
493
711
|
const firstRefresh = await scopedManager.listAgents();
|
|
494
712
|
const secondRefresh = await scopedManager.listAgents();
|
|
495
|
-
expect(firstRefresh[0].name).toBe(
|
|
496
|
-
expect(secondRefresh[0].name).toBe(
|
|
497
|
-
expect(registry.lookup(
|
|
498
|
-
name:
|
|
499
|
-
|
|
713
|
+
expect(firstRefresh[0].name).toBe("merry");
|
|
714
|
+
expect(secondRefresh[0].name).toBe("merry");
|
|
715
|
+
expect(registry.lookup("merry")).toMatchObject({
|
|
716
|
+
name: "merry",
|
|
717
|
+
runtimeRef: {
|
|
718
|
+
session: "merry-tmux"
|
|
719
|
+
}
|
|
500
720
|
});
|
|
501
721
|
});
|
|
502
|
-
it(
|
|
722
|
+
it("preserves a user-managed name when a fallback row was written later for the same pid", async ()=>{
|
|
503
723
|
registry.register({
|
|
504
|
-
name:
|
|
505
|
-
type:
|
|
724
|
+
name: "agent-list-debug",
|
|
725
|
+
type: "codex",
|
|
506
726
|
pid: process.pid,
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
727
|
+
runtime: "tmux",
|
|
728
|
+
runtimeRef: {
|
|
729
|
+
session: "agent-list-debug"
|
|
730
|
+
},
|
|
731
|
+
cwd: "/cwd/debug",
|
|
732
|
+
startedAt: "2026-05-30T00:00:00.000Z",
|
|
733
|
+
sessionId: "pid-debug",
|
|
734
|
+
sessionFilePath: ""
|
|
512
735
|
});
|
|
513
736
|
registry.register({
|
|
514
737
|
name: `ai-devkit-${process.pid}`,
|
|
515
|
-
type:
|
|
738
|
+
type: "codex",
|
|
516
739
|
pid: process.pid,
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
740
|
+
runtime: "tmux",
|
|
741
|
+
runtimeRef: null,
|
|
742
|
+
cwd: "/cwd/debug",
|
|
743
|
+
startedAt: "2026-05-31T00:00:00.000Z",
|
|
744
|
+
sessionId: "pid-debug",
|
|
745
|
+
sessionFilePath: ""
|
|
522
746
|
});
|
|
523
|
-
scopedManager.registerAdapter(new MockAdapter(
|
|
747
|
+
scopedManager.registerAdapter(new MockAdapter("codex", [
|
|
524
748
|
createMockAgent({
|
|
525
749
|
name: `ai-devkit-${process.pid}`,
|
|
526
|
-
type:
|
|
750
|
+
type: "codex",
|
|
527
751
|
pid: process.pid
|
|
528
752
|
})
|
|
529
753
|
]));
|
|
530
754
|
const agents = await scopedManager.listAgents();
|
|
531
|
-
expect(agents[0].name).toBe(
|
|
755
|
+
expect(agents[0].name).toBe("agent-list-debug");
|
|
532
756
|
expect(registry.list()).toHaveLength(1);
|
|
533
757
|
expect(registry.list()[0]).toMatchObject({
|
|
534
|
-
name:
|
|
758
|
+
name: "agent-list-debug",
|
|
535
759
|
pid: process.pid,
|
|
536
|
-
|
|
760
|
+
runtimeRef: {
|
|
761
|
+
session: "agent-list-debug"
|
|
762
|
+
}
|
|
537
763
|
});
|
|
538
764
|
});
|
|
539
|
-
it(
|
|
765
|
+
it("writes a fresh startedAt for new entries", async ()=>{
|
|
540
766
|
const before = new Date().toISOString();
|
|
541
|
-
scopedManager.registerAdapter(new MockAdapter(
|
|
767
|
+
scopedManager.registerAdapter(new MockAdapter("claude", [
|
|
542
768
|
createMockAgent({
|
|
543
|
-
name:
|
|
769
|
+
name: "new",
|
|
544
770
|
pid: process.pid
|
|
545
771
|
})
|
|
546
772
|
]));
|
|
@@ -548,36 +774,36 @@ describe('AgentManager', ()=>{
|
|
|
548
774
|
const entry = registry.list()[0];
|
|
549
775
|
expect(entry.startedAt >= before).toBe(true);
|
|
550
776
|
});
|
|
551
|
-
it(
|
|
552
|
-
const spy = vi.spyOn(registry,
|
|
553
|
-
scopedManager.registerAdapter(new MockAdapter(
|
|
777
|
+
it("batches the write — a single registerBatch call per listAgents", async ()=>{
|
|
778
|
+
const spy = vi.spyOn(registry, "registerBatch");
|
|
779
|
+
scopedManager.registerAdapter(new MockAdapter("claude", [
|
|
554
780
|
createMockAgent({
|
|
555
|
-
name:
|
|
781
|
+
name: "a",
|
|
556
782
|
pid: process.pid
|
|
557
783
|
})
|
|
558
784
|
]));
|
|
559
|
-
scopedManager.registerAdapter(new MockAdapter(
|
|
785
|
+
scopedManager.registerAdapter(new MockAdapter("codex", [
|
|
560
786
|
createMockAgent({
|
|
561
|
-
name:
|
|
562
|
-
type:
|
|
787
|
+
name: "b",
|
|
788
|
+
type: "codex",
|
|
563
789
|
pid: process.pid + 1
|
|
564
790
|
})
|
|
565
791
|
]));
|
|
566
792
|
await scopedManager.listAgents();
|
|
567
793
|
expect(spy).toHaveBeenCalledTimes(1);
|
|
568
794
|
expect(spy.mock.calls[0][0].map((e)=>e.name).sort()).toEqual([
|
|
569
|
-
|
|
570
|
-
|
|
795
|
+
"a",
|
|
796
|
+
"b"
|
|
571
797
|
]);
|
|
572
798
|
});
|
|
573
|
-
it(
|
|
574
|
-
const adapter = new MockAdapter(
|
|
799
|
+
it("performs zero database writes on an unchanged refresh", async ()=>{
|
|
800
|
+
const adapter = new MockAdapter("claude", [
|
|
575
801
|
createMockAgent({
|
|
576
|
-
name:
|
|
802
|
+
name: "stable",
|
|
577
803
|
pid: process.pid,
|
|
578
|
-
projectPath:
|
|
579
|
-
sessionId:
|
|
580
|
-
sessionFilePath:
|
|
804
|
+
projectPath: "/cwd/stable",
|
|
805
|
+
sessionId: "stable-session",
|
|
806
|
+
sessionFilePath: "/sessions/stable.jsonl"
|
|
581
807
|
})
|
|
582
808
|
]);
|
|
583
809
|
scopedManager.registerAdapter(adapter);
|
|
@@ -587,66 +813,66 @@ describe('AgentManager', ()=>{
|
|
|
587
813
|
const writes = databaseOperations.filter((sql)=>/^\s*(BEGIN|COMMIT|INSERT|UPDATE|DELETE)/i.test(sql));
|
|
588
814
|
expect(writes).toEqual([]);
|
|
589
815
|
});
|
|
590
|
-
it(
|
|
591
|
-
const adapter = new MockAdapter(
|
|
816
|
+
it("exposes a persisted pin and preserves it across a changed poll refresh", async ()=>{
|
|
817
|
+
const adapter = new MockAdapter("claude", [
|
|
592
818
|
createMockAgent({
|
|
593
|
-
name:
|
|
819
|
+
name: "pinned",
|
|
594
820
|
pid: process.pid,
|
|
595
|
-
sessionId:
|
|
821
|
+
sessionId: "before"
|
|
596
822
|
})
|
|
597
823
|
]);
|
|
598
824
|
scopedManager.registerAdapter(adapter);
|
|
599
825
|
await scopedManager.listAgents();
|
|
600
|
-
registry.togglePin(
|
|
826
|
+
registry.togglePin("claude", process.pid);
|
|
601
827
|
adapter.setAgents([
|
|
602
828
|
createMockAgent({
|
|
603
|
-
name:
|
|
829
|
+
name: "pinned",
|
|
604
830
|
pid: process.pid,
|
|
605
|
-
sessionId:
|
|
831
|
+
sessionId: "after"
|
|
606
832
|
})
|
|
607
833
|
]);
|
|
608
834
|
const agents = await scopedManager.listAgents();
|
|
609
835
|
expect(agents[0].pinned).toBe(true);
|
|
610
|
-
expect(registry.lookup(
|
|
611
|
-
sessionId:
|
|
836
|
+
expect(registry.lookup("pinned")).toMatchObject({
|
|
837
|
+
sessionId: "after",
|
|
612
838
|
pinned: true
|
|
613
839
|
});
|
|
614
840
|
});
|
|
615
|
-
it(
|
|
616
|
-
const adapter = new MockAdapter(
|
|
841
|
+
it("uses registry updated_at as lastActive for pinned recency ordering", async ()=>{
|
|
842
|
+
const adapter = new MockAdapter("claude", [
|
|
617
843
|
createMockAgent({
|
|
618
|
-
name:
|
|
844
|
+
name: "recently-pinned",
|
|
619
845
|
pid: process.pid,
|
|
620
|
-
lastActive: new Date(
|
|
846
|
+
lastActive: new Date("2026-01-01T00:00:00.000Z")
|
|
621
847
|
})
|
|
622
848
|
]);
|
|
623
849
|
scopedManager.registerAdapter(adapter);
|
|
624
850
|
await scopedManager.listAgents();
|
|
625
851
|
nowMs += 60_000;
|
|
626
|
-
scopedManager.togglePin(
|
|
852
|
+
scopedManager.togglePin("recently-pinned");
|
|
627
853
|
const agents = await scopedManager.listAgents();
|
|
628
854
|
expect(agents[0].pinned).toBe(true);
|
|
629
|
-
expect(agents[0].lastActive.toISOString()).toBe(
|
|
855
|
+
expect(agents[0].lastActive.toISOString()).toBe("2026-08-14T10:01:00.000Z");
|
|
630
856
|
});
|
|
631
|
-
it(
|
|
632
|
-
scopedManager.registerAdapter(new MockAdapter(
|
|
857
|
+
it("preserves adapter lastActive for unpinned agents", async ()=>{
|
|
858
|
+
scopedManager.registerAdapter(new MockAdapter("claude", [
|
|
633
859
|
createMockAgent({
|
|
634
|
-
name:
|
|
860
|
+
name: "unpinned",
|
|
635
861
|
pid: process.pid,
|
|
636
|
-
lastActive: new Date(
|
|
862
|
+
lastActive: new Date("2026-01-01T00:00:00.000Z")
|
|
637
863
|
})
|
|
638
864
|
]));
|
|
639
865
|
await scopedManager.listAgents();
|
|
640
866
|
const agents = await scopedManager.listAgents();
|
|
641
867
|
expect(agents[0].pinned).toBe(false);
|
|
642
|
-
expect(agents[0].lastActive.toISOString()).toBe(
|
|
868
|
+
expect(agents[0].lastActive.toISOString()).toBe("2026-01-01T00:00:00.000Z");
|
|
643
869
|
});
|
|
644
|
-
it(
|
|
645
|
-
const adapter = new MockAdapter(
|
|
870
|
+
it("does not persist passive cwd changes when an existing cwd is present", async ()=>{
|
|
871
|
+
const adapter = new MockAdapter("claude", [
|
|
646
872
|
createMockAgent({
|
|
647
|
-
name:
|
|
873
|
+
name: "changing",
|
|
648
874
|
pid: process.pid,
|
|
649
|
-
projectPath:
|
|
875
|
+
projectPath: "/cwd/before"
|
|
650
876
|
})
|
|
651
877
|
]);
|
|
652
878
|
scopedManager.registerAdapter(adapter);
|
|
@@ -655,350 +881,356 @@ describe('AgentManager', ()=>{
|
|
|
655
881
|
nowMs += 1_000;
|
|
656
882
|
adapter.setAgents([
|
|
657
883
|
createMockAgent({
|
|
658
|
-
name:
|
|
884
|
+
name: "changing",
|
|
659
885
|
pid: process.pid,
|
|
660
|
-
projectPath:
|
|
886
|
+
projectPath: "/cwd/after"
|
|
661
887
|
})
|
|
662
888
|
]);
|
|
663
889
|
await scopedManager.listAgents();
|
|
664
890
|
const upserts = databaseOperations.filter((sql)=>/^\s*INSERT INTO agents/i.test(sql));
|
|
665
891
|
const transactions = databaseOperations.filter((sql)=>/^\s*(BEGIN|COMMIT)/i.test(sql));
|
|
666
|
-
expect(upserts).toHaveLength(
|
|
667
|
-
expect(
|
|
668
|
-
expect(
|
|
669
|
-
expect(registry.lookup('changing')?.cwd).toBe('/cwd/after');
|
|
892
|
+
expect(upserts).toHaveLength(0);
|
|
893
|
+
expect(transactions).toHaveLength(0);
|
|
894
|
+
expect(registry.lookup("changing")?.cwd).toBe("/cwd/before");
|
|
670
895
|
});
|
|
671
|
-
it(
|
|
896
|
+
it("prunes newly dead entries only when the passive cadence is due", async ()=>{
|
|
672
897
|
registry.register({
|
|
673
|
-
name:
|
|
674
|
-
type:
|
|
898
|
+
name: "cadenced",
|
|
899
|
+
type: "claude",
|
|
675
900
|
pid: process.pid,
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
901
|
+
runtime: "tmux",
|
|
902
|
+
runtimeRef: null,
|
|
903
|
+
cwd: "/cwd/cadenced",
|
|
904
|
+
startedAt: "2026-05-30T00:00:00.000Z",
|
|
905
|
+
sessionId: "sid-cadenced",
|
|
906
|
+
sessionFilePath: ""
|
|
681
907
|
});
|
|
682
|
-
const alive = vi.spyOn(registry,
|
|
908
|
+
const alive = vi.spyOn(registry, "isAlive").mockReturnValue(true);
|
|
683
909
|
await scopedManager.listAgents();
|
|
684
910
|
expect(alive).toHaveBeenCalledTimes(1);
|
|
685
911
|
alive.mockReturnValue(false);
|
|
686
912
|
nowMs += 29_999;
|
|
687
913
|
await scopedManager.listAgents();
|
|
688
914
|
expect(alive).toHaveBeenCalledTimes(1);
|
|
689
|
-
expect(registry.lookup(
|
|
915
|
+
expect(registry.lookup("cadenced")).not.toBeNull();
|
|
690
916
|
nowMs += 1;
|
|
691
917
|
await scopedManager.listAgents();
|
|
692
918
|
expect(alive).toHaveBeenCalledTimes(2);
|
|
693
|
-
expect(registry.lookup(
|
|
919
|
+
expect(registry.lookup("cadenced")).toBeNull();
|
|
694
920
|
});
|
|
695
|
-
it(
|
|
921
|
+
it("does not inherit a name when the same pid is reused by another agent type", async ()=>{
|
|
696
922
|
registry.register({
|
|
697
|
-
name:
|
|
698
|
-
type:
|
|
923
|
+
name: "old-claude",
|
|
924
|
+
type: "claude",
|
|
699
925
|
pid: process.pid,
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
926
|
+
runtime: "tmux",
|
|
927
|
+
runtimeRef: {
|
|
928
|
+
session: "old-claude"
|
|
929
|
+
},
|
|
930
|
+
cwd: "/cwd/old",
|
|
931
|
+
startedAt: "2026-05-30T00:00:00.000Z",
|
|
932
|
+
sessionId: "old-session",
|
|
933
|
+
sessionFilePath: ""
|
|
705
934
|
});
|
|
706
|
-
scopedManager.registerAdapter(new MockAdapter(
|
|
935
|
+
scopedManager.registerAdapter(new MockAdapter("codex", [
|
|
707
936
|
createMockAgent({
|
|
708
|
-
name:
|
|
709
|
-
type:
|
|
937
|
+
name: "new-codex",
|
|
938
|
+
type: "codex",
|
|
710
939
|
pid: process.pid,
|
|
711
|
-
projectPath:
|
|
712
|
-
sessionId:
|
|
940
|
+
projectPath: "/cwd/new",
|
|
941
|
+
sessionId: "new-session"
|
|
713
942
|
})
|
|
714
943
|
]));
|
|
715
944
|
const agents = await scopedManager.listAgents();
|
|
716
|
-
expect(agents[0].name).toBe(
|
|
717
|
-
expect(registry.lookup(
|
|
718
|
-
expect(registry.lookup(
|
|
719
|
-
type:
|
|
945
|
+
expect(agents[0].name).toBe("new-codex");
|
|
946
|
+
expect(registry.lookup("old-claude")).toBeNull();
|
|
947
|
+
expect(registry.lookup("new-codex")).toMatchObject({
|
|
948
|
+
type: "codex",
|
|
720
949
|
pid: process.pid
|
|
721
950
|
});
|
|
722
951
|
});
|
|
723
|
-
it(
|
|
724
|
-
const writeSpy = vi.spyOn(registry,
|
|
725
|
-
const pruneSpy = vi.spyOn(registry,
|
|
726
|
-
scopedManager.registerAdapter(new MockAdapter(
|
|
952
|
+
it("skips registerBatch when no agents are detected and prune is not due", async ()=>{
|
|
953
|
+
const writeSpy = vi.spyOn(registry, "registerBatch");
|
|
954
|
+
const pruneSpy = vi.spyOn(registry, "pruneIfDue");
|
|
955
|
+
scopedManager.registerAdapter(new MockAdapter("claude", []));
|
|
727
956
|
await scopedManager.listAgents();
|
|
728
957
|
await scopedManager.listAgents();
|
|
729
958
|
expect(writeSpy).not.toHaveBeenCalled();
|
|
730
959
|
expect(pruneSpy).toHaveBeenCalledTimes(2);
|
|
731
960
|
});
|
|
732
961
|
});
|
|
733
|
-
describe(
|
|
734
|
-
it(
|
|
735
|
-
const registry = new AgentRegistry(path.join(tmpDir,
|
|
962
|
+
describe("togglePin", ()=>{
|
|
963
|
+
it("resolves the agent name to its process identity and toggles the pin", ()=>{
|
|
964
|
+
const registry = new AgentRegistry(path.join(tmpDir, "toggle.json"));
|
|
736
965
|
const scopedManager = new AgentManager(registry);
|
|
737
966
|
registry.register({
|
|
738
|
-
name:
|
|
739
|
-
type:
|
|
967
|
+
name: "renamed-agent",
|
|
968
|
+
type: "claude",
|
|
740
969
|
pid: process.pid,
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
970
|
+
runtime: "tmux",
|
|
971
|
+
runtimeRef: null,
|
|
972
|
+
cwd: "/tmp",
|
|
973
|
+
startedAt: "2026-08-16T00:00:00.000Z",
|
|
974
|
+
sessionId: "session",
|
|
975
|
+
sessionFilePath: "",
|
|
746
976
|
pinned: false
|
|
747
977
|
});
|
|
748
|
-
expect(scopedManager.togglePin(
|
|
749
|
-
expect(registry.lookup(
|
|
978
|
+
expect(scopedManager.togglePin("renamed-agent")).toBe(true);
|
|
979
|
+
expect(registry.lookup("renamed-agent")?.pinned).toBe(true);
|
|
750
980
|
});
|
|
751
|
-
it(
|
|
752
|
-
expect(()=>manager.togglePin(
|
|
981
|
+
it("reports when the agent is no longer running", ()=>{
|
|
982
|
+
expect(()=>manager.togglePin("missing")).toThrow(/no longer running/i);
|
|
753
983
|
});
|
|
754
|
-
it(
|
|
755
|
-
const registry = new AgentRegistry(path.join(tmpDir,
|
|
984
|
+
it("rejects a dead process and prunes its row", ()=>{
|
|
985
|
+
const registry = new AgentRegistry(path.join(tmpDir, "dead-toggle.json"));
|
|
756
986
|
const scopedManager = new AgentManager(registry);
|
|
757
987
|
registry.register({
|
|
758
|
-
name:
|
|
759
|
-
type:
|
|
988
|
+
name: "dead",
|
|
989
|
+
type: "claude",
|
|
760
990
|
pid: 999999,
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
991
|
+
runtime: "tmux",
|
|
992
|
+
runtimeRef: null,
|
|
993
|
+
cwd: "/tmp",
|
|
994
|
+
startedAt: "2026-08-16T00:00:00.000Z",
|
|
995
|
+
sessionId: "session",
|
|
996
|
+
sessionFilePath: "",
|
|
766
997
|
pinned: false
|
|
767
998
|
});
|
|
768
|
-
expect(()=>scopedManager.togglePin(
|
|
769
|
-
expect(registry.lookup(
|
|
999
|
+
expect(()=>scopedManager.togglePin("dead")).toThrow(/no longer running/i);
|
|
1000
|
+
expect(registry.lookup("dead")).toBeNull();
|
|
770
1001
|
});
|
|
771
|
-
it(
|
|
772
|
-
const regPath = path.join(tmpDir,
|
|
1002
|
+
it("surfaces a clear readonly mutation error", ()=>{
|
|
1003
|
+
const regPath = path.join(tmpDir, "readonly-toggle.json");
|
|
773
1004
|
const writable = new AgentRegistry(regPath);
|
|
774
1005
|
writable.register({
|
|
775
|
-
name:
|
|
776
|
-
type:
|
|
1006
|
+
name: "readonly-agent",
|
|
1007
|
+
type: "claude",
|
|
777
1008
|
pid: process.pid,
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
1009
|
+
runtime: "tmux",
|
|
1010
|
+
runtimeRef: null,
|
|
1011
|
+
cwd: "/tmp",
|
|
1012
|
+
startedAt: "2026-08-16T00:00:00.000Z",
|
|
1013
|
+
sessionId: "session",
|
|
1014
|
+
sessionFilePath: "",
|
|
783
1015
|
pinned: false
|
|
784
1016
|
});
|
|
785
1017
|
const readonlyManager = new AgentManager(new AgentRegistry(regPath, {
|
|
786
1018
|
readonly: true
|
|
787
1019
|
}));
|
|
788
|
-
expect(()=>readonlyManager.togglePin(
|
|
1020
|
+
expect(()=>readonlyManager.togglePin("readonly-agent")).toThrow("Agent registry is readonly; cannot toggle pin.");
|
|
789
1021
|
});
|
|
790
1022
|
});
|
|
791
|
-
describe(
|
|
792
|
-
it(
|
|
793
|
-
manager.registerAdapter(new MockAdapter(
|
|
794
|
-
manager.registerAdapter(new MockAdapter(
|
|
1023
|
+
describe("clear", ()=>{
|
|
1024
|
+
it("should remove all adapters", ()=>{
|
|
1025
|
+
manager.registerAdapter(new MockAdapter("claude"));
|
|
1026
|
+
manager.registerAdapter(new MockAdapter("gemini_cli"));
|
|
795
1027
|
manager.clear();
|
|
796
1028
|
expect(manager.getAdapterCount()).toBe(0);
|
|
797
1029
|
expect(manager.getAdapters()).toEqual([]);
|
|
798
1030
|
});
|
|
799
1031
|
});
|
|
800
|
-
describe(
|
|
1032
|
+
describe("listSessions", ()=>{
|
|
801
1033
|
function createMockSession(overrides = {}) {
|
|
802
1034
|
return {
|
|
803
|
-
type:
|
|
804
|
-
sessionId:
|
|
805
|
-
cwd:
|
|
806
|
-
firstUserMessage:
|
|
807
|
-
lastActive: new Date(
|
|
808
|
-
startedAt: new Date(
|
|
809
|
-
sessionFilePath:
|
|
1035
|
+
type: "claude",
|
|
1036
|
+
sessionId: "session-1",
|
|
1037
|
+
cwd: "/repo",
|
|
1038
|
+
firstUserMessage: "hello",
|
|
1039
|
+
lastActive: new Date("2025-01-01T00:00:00Z"),
|
|
1040
|
+
startedAt: new Date("2025-01-01T00:00:00Z"),
|
|
1041
|
+
sessionFilePath: "/tmp/session-1.jsonl",
|
|
810
1042
|
...overrides
|
|
811
1043
|
};
|
|
812
1044
|
}
|
|
813
|
-
it(
|
|
1045
|
+
it("returns empty array when no adapters are registered", async ()=>{
|
|
814
1046
|
const result = await manager.listSessions();
|
|
815
1047
|
expect(result).toEqual([]);
|
|
816
1048
|
});
|
|
817
|
-
it(
|
|
1049
|
+
it("merges sessions from every registered adapter", async ()=>{
|
|
818
1050
|
const claudeSession = createMockSession({
|
|
819
|
-
type:
|
|
820
|
-
sessionId:
|
|
1051
|
+
type: "claude",
|
|
1052
|
+
sessionId: "c1"
|
|
821
1053
|
});
|
|
822
1054
|
const codexSession = createMockSession({
|
|
823
|
-
type:
|
|
824
|
-
sessionId:
|
|
1055
|
+
type: "codex",
|
|
1056
|
+
sessionId: "cx1"
|
|
825
1057
|
});
|
|
826
|
-
manager.registerAdapter(new MockAdapter(
|
|
1058
|
+
manager.registerAdapter(new MockAdapter("claude", [], false, [
|
|
827
1059
|
claudeSession
|
|
828
1060
|
]));
|
|
829
|
-
manager.registerAdapter(new MockAdapter(
|
|
1061
|
+
manager.registerAdapter(new MockAdapter("codex", [], false, [
|
|
830
1062
|
codexSession
|
|
831
1063
|
]));
|
|
832
1064
|
const result = await manager.listSessions();
|
|
833
1065
|
expect(result).toHaveLength(2);
|
|
834
1066
|
expect(result.map((s)=>s.sessionId).sort()).toEqual([
|
|
835
|
-
|
|
836
|
-
|
|
1067
|
+
"c1",
|
|
1068
|
+
"cx1"
|
|
837
1069
|
]);
|
|
838
1070
|
});
|
|
839
|
-
it(
|
|
1071
|
+
it("sorts merged sessions by lastActive descending", async ()=>{
|
|
840
1072
|
const older = createMockSession({
|
|
841
|
-
sessionId:
|
|
842
|
-
lastActive: new Date(
|
|
1073
|
+
sessionId: "older",
|
|
1074
|
+
lastActive: new Date("2025-01-01T00:00:00Z")
|
|
843
1075
|
});
|
|
844
1076
|
const newer = createMockSession({
|
|
845
|
-
type:
|
|
846
|
-
sessionId:
|
|
847
|
-
lastActive: new Date(
|
|
1077
|
+
type: "codex",
|
|
1078
|
+
sessionId: "newer",
|
|
1079
|
+
lastActive: new Date("2025-06-01T00:00:00Z")
|
|
848
1080
|
});
|
|
849
|
-
manager.registerAdapter(new MockAdapter(
|
|
1081
|
+
manager.registerAdapter(new MockAdapter("claude", [], false, [
|
|
850
1082
|
older
|
|
851
1083
|
]));
|
|
852
|
-
manager.registerAdapter(new MockAdapter(
|
|
1084
|
+
manager.registerAdapter(new MockAdapter("codex", [], false, [
|
|
853
1085
|
newer
|
|
854
1086
|
]));
|
|
855
1087
|
const result = await manager.listSessions();
|
|
856
1088
|
expect(result.map((s)=>s.sessionId)).toEqual([
|
|
857
|
-
|
|
858
|
-
|
|
1089
|
+
"newer",
|
|
1090
|
+
"older"
|
|
859
1091
|
]);
|
|
860
1092
|
});
|
|
861
|
-
it(
|
|
862
|
-
const claudeAdapter = new MockAdapter(
|
|
1093
|
+
it("skips adapters whose type does not match opts.type", async ()=>{
|
|
1094
|
+
const claudeAdapter = new MockAdapter("claude", [], false, [
|
|
863
1095
|
createMockSession({
|
|
864
|
-
type:
|
|
865
|
-
sessionId:
|
|
1096
|
+
type: "claude",
|
|
1097
|
+
sessionId: "c1"
|
|
866
1098
|
})
|
|
867
1099
|
]);
|
|
868
|
-
const codexAdapter = new MockAdapter(
|
|
1100
|
+
const codexAdapter = new MockAdapter("codex", [], false, [
|
|
869
1101
|
createMockSession({
|
|
870
|
-
type:
|
|
871
|
-
sessionId:
|
|
1102
|
+
type: "codex",
|
|
1103
|
+
sessionId: "cx1"
|
|
872
1104
|
})
|
|
873
1105
|
]);
|
|
874
1106
|
manager.registerAdapter(claudeAdapter);
|
|
875
1107
|
manager.registerAdapter(codexAdapter);
|
|
876
1108
|
const result = await manager.listSessions({
|
|
877
|
-
type:
|
|
1109
|
+
type: "claude"
|
|
878
1110
|
});
|
|
879
1111
|
expect(result).toHaveLength(1);
|
|
880
|
-
expect(result[0].sessionId).toBe(
|
|
1112
|
+
expect(result[0].sessionId).toBe("c1");
|
|
881
1113
|
// Codex adapter must not have been called
|
|
882
1114
|
expect(codexAdapter.lastListSessionsOpts).toBeUndefined();
|
|
883
1115
|
expect(claudeAdapter.lastListSessionsOpts).toEqual({
|
|
884
|
-
type:
|
|
1116
|
+
type: "claude"
|
|
885
1117
|
});
|
|
886
1118
|
});
|
|
887
|
-
it(
|
|
1119
|
+
it("tolerates an adapter that throws and still returns the others", async ()=>{
|
|
888
1120
|
const goodSession = createMockSession({
|
|
889
|
-
sessionId:
|
|
1121
|
+
sessionId: "good"
|
|
890
1122
|
});
|
|
891
|
-
manager.registerAdapter(new MockAdapter(
|
|
1123
|
+
manager.registerAdapter(new MockAdapter("claude", [], false, [
|
|
892
1124
|
goodSession
|
|
893
1125
|
]));
|
|
894
|
-
manager.registerAdapter(new MockAdapter(
|
|
895
|
-
const consoleErrorSpy = vi.spyOn(console,
|
|
1126
|
+
manager.registerAdapter(new MockAdapter("codex", [], false, [], true));
|
|
1127
|
+
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(()=>undefined);
|
|
896
1128
|
try {
|
|
897
1129
|
const result = await manager.listSessions();
|
|
898
1130
|
expect(result).toHaveLength(1);
|
|
899
|
-
expect(result[0].sessionId).toBe(
|
|
1131
|
+
expect(result[0].sessionId).toBe("good");
|
|
900
1132
|
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
901
1133
|
} finally{
|
|
902
1134
|
consoleErrorSpy.mockRestore();
|
|
903
1135
|
}
|
|
904
1136
|
});
|
|
905
|
-
it(
|
|
906
|
-
const a = new MockAdapter(
|
|
907
|
-
const b = new MockAdapter(
|
|
1137
|
+
it("passes the same opts to every called adapter", async ()=>{
|
|
1138
|
+
const a = new MockAdapter("claude", [], false, []);
|
|
1139
|
+
const b = new MockAdapter("codex", [], false, []);
|
|
908
1140
|
manager.registerAdapter(a);
|
|
909
1141
|
manager.registerAdapter(b);
|
|
910
1142
|
await manager.listSessions({
|
|
911
|
-
cwd:
|
|
1143
|
+
cwd: "/Users/test/proj"
|
|
912
1144
|
});
|
|
913
1145
|
expect(a.lastListSessionsOpts).toEqual({
|
|
914
|
-
cwd:
|
|
1146
|
+
cwd: "/Users/test/proj"
|
|
915
1147
|
});
|
|
916
1148
|
expect(b.lastListSessionsOpts).toEqual({
|
|
917
|
-
cwd:
|
|
1149
|
+
cwd: "/Users/test/proj"
|
|
918
1150
|
});
|
|
919
1151
|
});
|
|
920
1152
|
});
|
|
921
|
-
describe(
|
|
922
|
-
it(
|
|
1153
|
+
describe("resolveAgent", ()=>{
|
|
1154
|
+
it("should return null for empty input or empty agents list", ()=>{
|
|
923
1155
|
const agent = createMockAgent({
|
|
924
|
-
name:
|
|
1156
|
+
name: "test-agent"
|
|
925
1157
|
});
|
|
926
|
-
expect(manager.resolveAgent(
|
|
1158
|
+
expect(manager.resolveAgent("", [
|
|
927
1159
|
agent
|
|
928
1160
|
])).toBeNull();
|
|
929
|
-
expect(manager.resolveAgent(
|
|
1161
|
+
expect(manager.resolveAgent("test", [])).toBeNull();
|
|
930
1162
|
});
|
|
931
|
-
it(
|
|
1163
|
+
it("should resolve exact match (case-insensitive)", ()=>{
|
|
932
1164
|
const agent = createMockAgent({
|
|
933
|
-
name:
|
|
1165
|
+
name: "My-Agent"
|
|
934
1166
|
});
|
|
935
1167
|
const agents = [
|
|
936
1168
|
agent,
|
|
937
1169
|
createMockAgent({
|
|
938
|
-
name:
|
|
1170
|
+
name: "Other"
|
|
939
1171
|
})
|
|
940
1172
|
];
|
|
941
1173
|
// Exact match
|
|
942
|
-
expect(manager.resolveAgent(
|
|
1174
|
+
expect(manager.resolveAgent("My-Agent", agents)).toBe(agent);
|
|
943
1175
|
// Case-insensitive
|
|
944
|
-
expect(manager.resolveAgent(
|
|
1176
|
+
expect(manager.resolveAgent("my-agent", agents)).toBe(agent);
|
|
945
1177
|
});
|
|
946
|
-
it(
|
|
1178
|
+
it("should resolve unique partial match", ()=>{
|
|
947
1179
|
const agent = createMockAgent({
|
|
948
|
-
name:
|
|
1180
|
+
name: "ai-devkit"
|
|
949
1181
|
});
|
|
950
1182
|
const agents = [
|
|
951
1183
|
agent,
|
|
952
1184
|
createMockAgent({
|
|
953
|
-
name:
|
|
1185
|
+
name: "other-project"
|
|
954
1186
|
})
|
|
955
1187
|
];
|
|
956
|
-
const result = manager.resolveAgent(
|
|
1188
|
+
const result = manager.resolveAgent("dev", agents);
|
|
957
1189
|
expect(result).toBe(agent);
|
|
958
1190
|
});
|
|
959
|
-
it(
|
|
1191
|
+
it("should return array for ambiguous partial match", ()=>{
|
|
960
1192
|
const agent1 = createMockAgent({
|
|
961
|
-
name:
|
|
1193
|
+
name: "my-website"
|
|
962
1194
|
});
|
|
963
1195
|
const agent2 = createMockAgent({
|
|
964
|
-
name:
|
|
1196
|
+
name: "my-app"
|
|
965
1197
|
});
|
|
966
1198
|
const agents = [
|
|
967
1199
|
agent1,
|
|
968
1200
|
agent2,
|
|
969
1201
|
createMockAgent({
|
|
970
|
-
name:
|
|
1202
|
+
name: "other"
|
|
971
1203
|
})
|
|
972
1204
|
];
|
|
973
|
-
const result = manager.resolveAgent(
|
|
1205
|
+
const result = manager.resolveAgent("my", agents);
|
|
974
1206
|
expect(Array.isArray(result)).toBe(true);
|
|
975
1207
|
const matches = result;
|
|
976
1208
|
expect(matches).toHaveLength(2);
|
|
977
1209
|
expect(matches).toContain(agent1);
|
|
978
1210
|
expect(matches).toContain(agent2);
|
|
979
1211
|
});
|
|
980
|
-
it(
|
|
1212
|
+
it("should return null for no match", ()=>{
|
|
981
1213
|
const agents = [
|
|
982
1214
|
createMockAgent({
|
|
983
|
-
name:
|
|
1215
|
+
name: "ai-devkit"
|
|
984
1216
|
})
|
|
985
1217
|
];
|
|
986
|
-
expect(manager.resolveAgent(
|
|
1218
|
+
expect(manager.resolveAgent("xyz", agents)).toBeNull();
|
|
987
1219
|
});
|
|
988
|
-
it(
|
|
1220
|
+
it("should prefer exact match over partial matches", ()=>{
|
|
989
1221
|
// Edge case: "test" matches "test" (exact) and "testing" (partial)
|
|
990
1222
|
// Should return exact "test"
|
|
991
1223
|
const exact = createMockAgent({
|
|
992
|
-
name:
|
|
1224
|
+
name: "test"
|
|
993
1225
|
});
|
|
994
1226
|
const partial = createMockAgent({
|
|
995
|
-
name:
|
|
1227
|
+
name: "testing"
|
|
996
1228
|
});
|
|
997
1229
|
const agents = [
|
|
998
1230
|
exact,
|
|
999
1231
|
partial
|
|
1000
1232
|
];
|
|
1001
|
-
expect(manager.resolveAgent(
|
|
1233
|
+
expect(manager.resolveAgent("test", agents)).toBe(exact);
|
|
1002
1234
|
});
|
|
1003
1235
|
});
|
|
1004
1236
|
});
|