@ai-devkit/agent-manager 0.37.0 → 0.39.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 +0 -4
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +2 -2
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/capacity/anthropic.test.js +115 -0
- package/dist/__tests__/capacity/anthropic.test.js.map +1 -0
- package/dist/__tests__/capacity/boundaries.test.js +45 -0
- package/dist/__tests__/capacity/boundaries.test.js.map +1 -0
- package/dist/__tests__/capacity/claude.test.js +3 -3
- package/dist/__tests__/capacity/claude.test.js.map +1 -1
- package/dist/__tests__/capacity/codex.test.js +3 -1
- package/dist/__tests__/capacity/codex.test.js.map +1 -1
- package/dist/__tests__/capacity/openai.test.js +21 -12
- package/dist/__tests__/capacity/openai.test.js.map +1 -1
- package/dist/__tests__/capacity/wham.test.js +1 -1
- package/dist/__tests__/capacity/wham.test.js.map +1 -1
- package/dist/__tests__/capacity/zai.test.js +7 -8
- package/dist/__tests__/capacity/zai.test.js.map +1 -1
- package/dist/__tests__/harnesses/claude/durable/ClaudePrintAgent.integration.test.js +2 -1
- package/dist/__tests__/harnesses/claude/durable/ClaudePrintAgent.integration.test.js.map +1 -1
- package/dist/__tests__/harnesses/claude/durable/ClaudePrintRunner.test.js.map +1 -1
- package/dist/__tests__/harnesses/codex/durable/CodexCliProbe.test.js +7 -15
- package/dist/__tests__/harnesses/codex/durable/CodexCliProbe.test.js.map +1 -1
- package/dist/__tests__/harnesses/codex/durable/CodexPrintAgent.integration.test.js +3 -1
- package/dist/__tests__/harnesses/codex/durable/CodexPrintAgent.integration.test.js.map +1 -1
- package/dist/__tests__/harnesses/codex/durable/CodexPrintAgentService.test.js +11 -26
- package/dist/__tests__/harnesses/codex/durable/CodexPrintAgentService.test.js.map +1 -1
- package/dist/__tests__/harnesses/codex/durable/CodexPrintRunner.test.js +3 -7
- package/dist/__tests__/harnesses/codex/durable/CodexPrintRunner.test.js.map +1 -1
- package/dist/__tests__/harnesses/devin/DevinAdapter.test.js +597 -0
- package/dist/__tests__/harnesses/devin/DevinAdapter.test.js.map +1 -0
- package/dist/__tests__/harnesses/devin/DevinAgentMapper.test.js +91 -0
- package/dist/__tests__/harnesses/devin/DevinAgentMapper.test.js.map +1 -0
- package/dist/__tests__/harnesses/devin/DevinSessionLocator.test.js +192 -0
- package/dist/__tests__/harnesses/devin/DevinSessionLocator.test.js.map +1 -0
- package/dist/__tests__/harnesses/devin/DevinSessionParser.test.js +407 -0
- package/dist/__tests__/harnesses/devin/DevinSessionParser.test.js.map +1 -0
- package/dist/__tests__/harnesses/devin/fixtures.js +95 -0
- package/dist/__tests__/harnesses/devin/fixtures.js.map +1 -0
- package/dist/__tests__/harnesses/pi/durable/PiCliProbe.test.js +5 -11
- package/dist/__tests__/harnesses/pi/durable/PiCliProbe.test.js.map +1 -1
- package/dist/__tests__/harnesses/pi/durable/PiPrintAgentService.test.js +9 -19
- package/dist/__tests__/harnesses/pi/durable/PiPrintAgentService.test.js.map +1 -1
- package/dist/__tests__/harnesses/pi/durable/PiPrintRunner.test.js +3 -7
- package/dist/__tests__/harnesses/pi/durable/PiPrintRunner.test.js.map +1 -1
- package/dist/__tests__/{readiness → harnesses/readiness}/AgentReadiness.test.js +95 -11
- package/dist/__tests__/harnesses/readiness/AgentReadiness.test.js.map +1 -0
- package/dist/__tests__/harnesses/runtimeProfiles.test.js +89 -0
- package/dist/__tests__/harnesses/runtimeProfiles.test.js.map +1 -0
- package/dist/__tests__/print/DurableAgent.test.js +4 -6
- package/dist/__tests__/print/DurableAgent.test.js.map +1 -1
- package/dist/__tests__/print/DurableAgentRepository.test.js +1 -12
- package/dist/__tests__/print/DurableAgentRepository.test.js.map +1 -1
- package/dist/__tests__/runtime/ManagedAgentRuntime.test.js.map +1 -1
- package/dist/__tests__/terminal/GhosttyBackend.test.js +334 -0
- package/dist/__tests__/terminal/GhosttyBackend.test.js.map +1 -0
- package/dist/__tests__/terminal/TmuxManager.test.js +1 -1
- package/dist/__tests__/terminal/TmuxManager.test.js.map +1 -1
- package/dist/adapters/AgentAdapter.d.ts +1 -1
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/AgentAdapter.js +2 -1
- package/dist/adapters/AgentAdapter.js.map +1 -1
- package/dist/capacity/index.d.ts +9 -19
- package/dist/capacity/index.d.ts.map +1 -1
- package/dist/capacity/index.js +11 -4
- package/dist/capacity/index.js.map +1 -1
- package/dist/capacity/providers/anthropic.d.ts +9 -0
- package/dist/capacity/providers/anthropic.d.ts.map +1 -0
- package/dist/capacity/{claude.js → providers/anthropic.js} +10 -128
- package/dist/capacity/providers/anthropic.js.map +1 -0
- package/dist/capacity/providers/openai-platform.d.ts +14 -0
- package/dist/capacity/providers/openai-platform.d.ts.map +1 -0
- package/dist/capacity/providers/openai-platform.js +120 -0
- package/dist/capacity/providers/openai-platform.js.map +1 -0
- package/dist/capacity/providers/openai-subscription.d.ts +20 -0
- package/dist/capacity/providers/openai-subscription.d.ts.map +1 -0
- package/dist/capacity/providers/openai-subscription.js +129 -0
- package/dist/capacity/providers/openai-subscription.js.map +1 -0
- package/dist/capacity/providers/zai.d.ts +8 -0
- package/dist/capacity/providers/zai.d.ts.map +1 -0
- package/dist/capacity/{zai.js → providers/zai.js} +17 -62
- package/dist/capacity/providers/zai.js.map +1 -0
- package/dist/capacity/sources/claude.d.ts +9 -0
- package/dist/capacity/sources/claude.d.ts.map +1 -0
- package/dist/capacity/sources/claude.js +17 -0
- package/dist/capacity/sources/claude.js.map +1 -0
- package/dist/capacity/{codex.d.ts → sources/codex.d.ts} +5 -9
- package/dist/capacity/sources/codex.d.ts.map +1 -0
- package/dist/capacity/{codex.js → sources/codex.js} +31 -84
- package/dist/capacity/sources/codex.js.map +1 -0
- package/dist/capacity/sources/pi.d.ts +30 -0
- package/dist/capacity/sources/pi.d.ts.map +1 -0
- package/dist/capacity/sources/pi.js +81 -0
- package/dist/capacity/sources/pi.js.map +1 -0
- package/dist/capacity/types.d.ts +2 -0
- package/dist/capacity/types.d.ts.map +1 -1
- package/dist/capacity/types.js +1 -1
- package/dist/capacity/types.js.map +1 -1
- package/dist/harnesses/claude/credentials.d.ts +10 -0
- package/dist/harnesses/claude/credentials.d.ts.map +1 -0
- package/dist/harnesses/claude/credentials.js +112 -0
- package/dist/harnesses/claude/credentials.js.map +1 -0
- package/dist/harnesses/claude/readiness.d.ts +3 -0
- package/dist/harnesses/claude/readiness.d.ts.map +1 -0
- package/dist/harnesses/claude/readiness.js +63 -0
- package/dist/harnesses/claude/readiness.js.map +1 -0
- package/dist/harnesses/codex/credentials.d.ts +15 -0
- package/dist/harnesses/codex/credentials.d.ts.map +1 -0
- package/dist/harnesses/codex/credentials.js +58 -0
- package/dist/harnesses/codex/credentials.js.map +1 -0
- package/dist/harnesses/codex/readiness.d.ts +3 -0
- package/dist/harnesses/codex/readiness.d.ts.map +1 -0
- package/dist/harnesses/codex/readiness.js +60 -0
- package/dist/harnesses/codex/readiness.js.map +1 -0
- package/dist/harnesses/copilot/readiness.d.ts +3 -0
- package/dist/harnesses/copilot/readiness.d.ts.map +1 -0
- package/dist/harnesses/copilot/readiness.js +39 -0
- package/dist/harnesses/copilot/readiness.js.map +1 -0
- package/dist/harnesses/devin/DevinAdapter.d.ts +39 -0
- package/dist/harnesses/devin/DevinAdapter.d.ts.map +1 -0
- package/dist/harnesses/devin/DevinAdapter.js +177 -0
- package/dist/harnesses/devin/DevinAdapter.js.map +1 -0
- package/dist/harnesses/devin/DevinAgentMapper.d.ts +11 -0
- package/dist/harnesses/devin/DevinAgentMapper.d.ts.map +1 -0
- package/dist/harnesses/devin/DevinAgentMapper.js +35 -0
- package/dist/harnesses/devin/DevinAgentMapper.js.map +1 -0
- package/dist/harnesses/devin/DevinSessionLocator.d.ts +46 -0
- package/dist/harnesses/devin/DevinSessionLocator.d.ts.map +1 -0
- package/dist/harnesses/devin/DevinSessionLocator.js +176 -0
- package/dist/harnesses/devin/DevinSessionLocator.js.map +1 -0
- package/dist/harnesses/devin/DevinSessionParser.d.ts +29 -0
- package/dist/harnesses/devin/DevinSessionParser.d.ts.map +1 -0
- package/dist/harnesses/devin/DevinSessionParser.js +166 -0
- package/dist/harnesses/devin/DevinSessionParser.js.map +1 -0
- package/dist/harnesses/devin/readiness.d.ts +3 -0
- package/dist/harnesses/devin/readiness.d.ts.map +1 -0
- package/dist/harnesses/devin/readiness.js +39 -0
- package/dist/harnesses/devin/readiness.js.map +1 -0
- package/dist/harnesses/index.d.ts.map +1 -1
- package/dist/harnesses/index.js +3 -1
- package/dist/harnesses/index.js.map +1 -1
- package/dist/harnesses/opencode/readiness.d.ts +3 -0
- package/dist/harnesses/opencode/readiness.d.ts.map +1 -0
- package/dist/harnesses/opencode/readiness.js +50 -0
- package/dist/harnesses/opencode/readiness.js.map +1 -0
- package/dist/harnesses/pi/credentials.d.ts +22 -0
- package/dist/harnesses/pi/credentials.d.ts.map +1 -0
- package/dist/harnesses/pi/credentials.js +98 -0
- package/dist/harnesses/pi/credentials.js.map +1 -0
- package/dist/harnesses/pi/readiness.d.ts +3 -0
- package/dist/harnesses/pi/readiness.d.ts.map +1 -0
- package/dist/harnesses/pi/readiness.js +96 -0
- package/dist/harnesses/pi/readiness.js.map +1 -0
- package/dist/harnesses/readiness/AgentReadiness.d.ts +6 -0
- package/dist/harnesses/readiness/AgentReadiness.d.ts.map +1 -0
- package/dist/harnesses/readiness/AgentReadiness.js +70 -0
- package/dist/harnesses/readiness/AgentReadiness.js.map +1 -0
- package/dist/harnesses/readiness/checks.d.ts +31 -0
- package/dist/harnesses/readiness/checks.d.ts.map +1 -0
- package/dist/harnesses/readiness/checks.js +270 -0
- package/dist/harnesses/readiness/checks.js.map +1 -0
- package/dist/{readiness/AgentReadiness.d.ts → harnesses/readiness/types.d.ts} +19 -10
- package/dist/harnesses/readiness/types.d.ts.map +1 -0
- package/dist/harnesses/readiness/types.js +4 -0
- package/dist/harnesses/readiness/types.js.map +1 -0
- package/dist/harnesses/runtimeProfiles.d.ts +11 -0
- package/dist/harnesses/runtimeProfiles.d.ts.map +1 -0
- package/dist/harnesses/runtimeProfiles.js +100 -0
- package/dist/harnesses/runtimeProfiles.js.map +1 -0
- package/dist/index.d.ts +16 -42
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -23
- package/dist/index.js.map +1 -1
- package/dist/runtime/ManagedAgentRuntime.d.ts +3 -3
- package/dist/runtime/ManagedAgentRuntime.d.ts.map +1 -1
- package/dist/runtime/ManagedAgentRuntime.js +6 -20
- package/dist/runtime/ManagedAgentRuntime.js.map +1 -1
- package/dist/runtime/herdr/HerdrAgentDiscovery.d.ts.map +1 -1
- package/dist/runtime/herdr/HerdrAgentDiscovery.js +2 -43
- package/dist/runtime/herdr/HerdrAgentDiscovery.js.map +1 -1
- package/dist/runtime/tmux/PidPolling.d.ts +1 -1
- package/dist/runtime/tmux/PidPolling.d.ts.map +1 -1
- package/dist/runtime/tmux/PidPolling.js.map +1 -1
- package/dist/runtime/tmux/TmuxAgentRuntime.d.ts +1 -1
- package/dist/runtime/tmux/TmuxAgentRuntime.d.ts.map +1 -1
- package/dist/runtime/tmux/TmuxAgentRuntime.js +1 -1
- package/dist/runtime/tmux/TmuxAgentRuntime.js.map +1 -1
- package/dist/runtime/tmux/TmuxManager.d.ts +12 -0
- package/dist/runtime/tmux/TmuxManager.d.ts.map +1 -0
- package/dist/{terminal → runtime/tmux}/TmuxManager.js +9 -28
- package/dist/runtime/tmux/TmuxManager.js.map +1 -0
- package/dist/runtime/types.d.ts +1 -1
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/types.js.map +1 -1
- package/dist/terminal/TerminalFocusManager.d.ts +4 -32
- package/dist/terminal/TerminalFocusManager.d.ts.map +1 -1
- package/dist/terminal/TerminalFocusManager.js +17 -267
- package/dist/terminal/TerminalFocusManager.js.map +1 -1
- package/dist/terminal/TtyWriter.d.ts +1 -44
- package/dist/terminal/TtyWriter.d.ts.map +1 -1
- package/dist/terminal/TtyWriter.js +12 -347
- package/dist/terminal/TtyWriter.js.map +1 -1
- package/dist/terminal/backends/ghostty.d.ts +3 -0
- package/dist/terminal/backends/ghostty.d.ts.map +1 -0
- package/dist/terminal/backends/ghostty.js +161 -0
- package/dist/terminal/backends/ghostty.js.map +1 -0
- package/dist/terminal/backends/index.d.ts +5 -0
- package/dist/terminal/backends/index.d.ts.map +1 -0
- package/dist/terminal/backends/index.js +21 -0
- package/dist/terminal/backends/index.js.map +1 -0
- package/dist/terminal/backends/iterm2.d.ts +3 -0
- package/dist/terminal/backends/iterm2.d.ts.map +1 -0
- package/dist/terminal/backends/iterm2.js +135 -0
- package/dist/terminal/backends/iterm2.js.map +1 -0
- package/dist/terminal/backends/shared.d.ts +9 -0
- package/dist/terminal/backends/shared.d.ts.map +1 -0
- package/dist/terminal/backends/shared.js +38 -0
- package/dist/terminal/backends/shared.js.map +1 -0
- package/dist/terminal/backends/terminal-app.d.ts +3 -0
- package/dist/terminal/backends/terminal-app.d.ts.map +1 -0
- package/dist/terminal/backends/terminal-app.js +129 -0
- package/dist/terminal/backends/terminal-app.js.map +1 -0
- package/dist/terminal/backends/tmux.d.ts +3 -0
- package/dist/terminal/backends/tmux.d.ts.map +1 -0
- package/dist/terminal/backends/tmux.js +76 -0
- package/dist/terminal/backends/tmux.js.map +1 -0
- package/dist/terminal/backends/wezterm.d.ts +3 -0
- package/dist/terminal/backends/wezterm.d.ts.map +1 -0
- package/dist/terminal/backends/wezterm.js +71 -0
- package/dist/terminal/backends/wezterm.js.map +1 -0
- package/dist/terminal/types.d.ts +22 -0
- package/dist/terminal/types.d.ts.map +1 -0
- package/dist/terminal/types.js +11 -0
- package/dist/terminal/types.js.map +1 -0
- package/dist/utils/process.d.ts +2 -0
- package/dist/utils/process.d.ts.map +1 -1
- package/dist/utils/process.js +3 -0
- package/dist/utils/process.js.map +1 -1
- package/package.json +1 -1
- package/src/AgentManager.ts +1 -1
- package/src/__tests__/capacity/anthropic.test.ts +105 -0
- package/src/__tests__/capacity/boundaries.test.ts +36 -0
- package/src/__tests__/capacity/claude.test.ts +3 -8
- package/src/__tests__/capacity/codex.test.ts +12 -35
- package/src/__tests__/capacity/fixtures/openai-codex-auth.json +1 -1
- package/src/__tests__/capacity/openai.test.ts +15 -15
- package/src/__tests__/capacity/wham.test.ts +1 -1
- package/src/__tests__/capacity/zai.test.ts +27 -62
- package/src/__tests__/harnesses/claude/durable/ClaudePrintAgent.integration.test.ts +2 -2
- package/src/__tests__/harnesses/claude/durable/ClaudePrintRunner.test.ts +1 -1
- package/src/__tests__/harnesses/codex/durable/CodexCliProbe.test.ts +14 -19
- package/src/__tests__/harnesses/codex/durable/CodexPrintAgent.integration.test.ts +3 -3
- package/src/__tests__/harnesses/codex/durable/CodexPrintAgentService.test.ts +24 -30
- package/src/__tests__/harnesses/codex/durable/CodexPrintRunner.test.ts +4 -8
- package/src/__tests__/harnesses/devin/DevinAdapter.test.ts +292 -0
- package/src/__tests__/harnesses/devin/DevinAgentMapper.test.ts +88 -0
- package/src/__tests__/harnesses/devin/DevinSessionLocator.test.ts +139 -0
- package/src/__tests__/harnesses/devin/DevinSessionParser.test.ts +218 -0
- package/src/__tests__/harnesses/devin/fixtures.ts +144 -0
- package/src/__tests__/harnesses/pi/durable/PiCliProbe.test.ts +7 -11
- package/src/__tests__/harnesses/pi/durable/PiPrintAgentService.test.ts +19 -22
- package/src/__tests__/harnesses/pi/durable/PiPrintRunner.test.ts +7 -8
- package/src/__tests__/{readiness → harnesses/readiness}/AgentReadiness.test.ts +77 -9
- package/src/__tests__/harnesses/runtimeProfiles.test.ts +110 -0
- package/src/__tests__/print/DurableAgent.test.ts +4 -13
- package/src/__tests__/print/DurableAgentRepository.test.ts +1 -13
- package/src/__tests__/runtime/ManagedAgentRuntime.test.ts +1 -1
- package/src/__tests__/terminal/GhosttyBackend.test.ts +271 -0
- package/src/__tests__/terminal/TmuxManager.test.ts +1 -1
- package/src/adapters/AgentAdapter.ts +1 -0
- package/src/capacity/index.ts +29 -23
- package/src/capacity/{claude.ts → providers/anthropic.ts} +16 -131
- package/src/capacity/providers/openai-platform.ts +150 -0
- package/src/capacity/{wham.ts → providers/openai-subscription.ts} +86 -8
- package/src/capacity/{zai.ts → providers/zai.ts} +29 -111
- package/src/capacity/sources/claude.ts +30 -0
- package/src/capacity/{codex.ts → sources/codex.ts} +54 -124
- package/src/capacity/sources/pi.ts +131 -0
- package/src/capacity/types.ts +3 -0
- package/src/harnesses/claude/credentials.ts +122 -0
- package/src/harnesses/claude/readiness.ts +69 -0
- package/src/harnesses/codex/credentials.ts +81 -0
- package/src/harnesses/codex/readiness.ts +72 -0
- package/src/harnesses/copilot/readiness.ts +33 -0
- package/src/harnesses/devin/DevinAdapter.ts +224 -0
- package/src/harnesses/devin/DevinAgentMapper.ts +43 -0
- package/src/harnesses/devin/DevinSessionLocator.ts +230 -0
- package/src/harnesses/devin/DevinSessionParser.ts +236 -0
- package/src/harnesses/devin/readiness.ts +36 -0
- package/src/harnesses/index.ts +2 -0
- package/src/harnesses/opencode/readiness.ts +48 -0
- package/src/harnesses/pi/credentials.ts +129 -0
- package/src/harnesses/pi/readiness.ts +104 -0
- package/src/harnesses/readiness/AgentReadiness.ts +89 -0
- package/src/harnesses/readiness/checks.ts +336 -0
- package/src/harnesses/readiness/types.ts +90 -0
- package/src/{utils/agents.ts → harnesses/runtimeProfiles.ts} +35 -38
- package/src/index.ts +15 -128
- package/src/runtime/ManagedAgentRuntime.ts +7 -22
- package/src/runtime/herdr/HerdrAgentDiscovery.ts +3 -19
- package/src/runtime/tmux/PidPolling.ts +1 -1
- package/src/runtime/tmux/TmuxAgentRuntime.ts +1 -1
- package/src/{terminal → runtime/tmux}/TmuxManager.ts +9 -30
- package/src/runtime/types.ts +1 -1
- package/src/terminal/TerminalFocusManager.ts +19 -292
- package/src/terminal/TtyWriter.ts +15 -335
- package/src/terminal/backends/ghostty.ts +180 -0
- package/src/terminal/backends/index.ts +23 -0
- package/src/terminal/backends/iterm2.ts +126 -0
- package/src/terminal/backends/shared.ts +47 -0
- package/src/terminal/backends/terminal-app.ts +122 -0
- package/src/terminal/backends/tmux.ts +62 -0
- package/src/terminal/backends/wezterm.ts +65 -0
- package/src/terminal/types.ts +23 -0
- package/src/utils/process.ts +5 -0
- package/dist/__tests__/readiness/AgentReadiness.test.js.map +0 -1
- package/dist/__tests__/utils/agents.test.js +0 -93
- package/dist/__tests__/utils/agents.test.js.map +0 -1
- package/dist/capacity/claude.d.ts +0 -17
- package/dist/capacity/claude.d.ts.map +0 -1
- package/dist/capacity/claude.js.map +0 -1
- package/dist/capacity/codex.d.ts.map +0 -1
- package/dist/capacity/codex.js.map +0 -1
- package/dist/capacity/openai.d.ts +0 -47
- package/dist/capacity/openai.d.ts.map +0 -1
- package/dist/capacity/openai.js +0 -307
- package/dist/capacity/openai.js.map +0 -1
- package/dist/capacity/wham.d.ts +0 -16
- package/dist/capacity/wham.d.ts.map +0 -1
- package/dist/capacity/wham.js +0 -66
- package/dist/capacity/wham.js.map +0 -1
- package/dist/capacity/zai.d.ts +0 -14
- package/dist/capacity/zai.d.ts.map +0 -1
- package/dist/capacity/zai.js.map +0 -1
- package/dist/readiness/AgentReadiness.d.ts.map +0 -1
- package/dist/readiness/AgentReadiness.js +0 -614
- package/dist/readiness/AgentReadiness.js.map +0 -1
- package/dist/terminal/TmuxManager.d.ts +0 -28
- package/dist/terminal/TmuxManager.d.ts.map +0 -1
- package/dist/terminal/TmuxManager.js.map +0 -1
- package/dist/terminal/index.d.ts +0 -5
- package/dist/terminal/index.d.ts.map +0 -1
- package/dist/terminal/index.js +0 -5
- package/dist/terminal/index.js.map +0 -1
- package/dist/utils/agents.d.ts +0 -16
- package/dist/utils/agents.d.ts.map +0 -1
- package/dist/utils/agents.js +0 -117
- package/dist/utils/agents.js.map +0 -1
- package/dist/utils/index.d.ts +0 -8
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -6
- package/dist/utils/index.js.map +0 -1
- package/src/__tests__/utils/agents.test.ts +0 -105
- package/src/capacity/openai.ts +0 -355
- package/src/readiness/AgentReadiness.ts +0 -730
- package/src/terminal/index.ts +0 -4
- package/src/utils/index.ts +0 -14
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { describe, expect, it, vi } from "vitest";
|
|
3
3
|
import {
|
|
4
|
-
AGENTS,
|
|
5
4
|
getAgentReadinessReport,
|
|
6
5
|
getAgentReadinessReports,
|
|
7
6
|
type AgentReadinessOptions,
|
|
8
7
|
type ReadinessAgentType,
|
|
9
|
-
} from "
|
|
8
|
+
} from "../../../harnesses/readiness/AgentReadiness.js";
|
|
9
|
+
import { HARNESS_RUNTIME_PROFILES } from "../../../harnesses/runtimeProfiles.js";
|
|
10
10
|
|
|
11
11
|
type Files = Record<string, string>;
|
|
12
12
|
|
|
13
13
|
const builtInSkillNames = ["agent-management", "memory", "verify"];
|
|
14
|
-
const skillRoots: Record<
|
|
14
|
+
const skillRoots: Record<string, string> = {
|
|
15
15
|
claude: ".claude/skills",
|
|
16
16
|
codex: ".codex/skills",
|
|
17
17
|
copilot: ".copilot/skills",
|
|
18
|
+
gemini_cli: ".gemini/skills",
|
|
18
19
|
grok_cli: ".grok/skills",
|
|
20
|
+
antigravity_cli: ".gemini/config/skills",
|
|
19
21
|
opencode: ".config/opencode/skill",
|
|
20
22
|
pi: ".pi/agent/skills",
|
|
21
23
|
};
|
|
@@ -56,7 +58,18 @@ function fixture(overrides: Partial<AgentReadinessOptions> = {}) {
|
|
|
56
58
|
litellm: { key: "litellm-secret", env: { LITELLM_BASE_URL: "https://litellm.example.test" } },
|
|
57
59
|
}),
|
|
58
60
|
};
|
|
59
|
-
for (const directory of [
|
|
61
|
+
for (const directory of [
|
|
62
|
+
".claude",
|
|
63
|
+
".codex",
|
|
64
|
+
".copilot",
|
|
65
|
+
".gemini",
|
|
66
|
+
".grok",
|
|
67
|
+
".kiro",
|
|
68
|
+
".gemini/antigravity-cli",
|
|
69
|
+
".config/opencode",
|
|
70
|
+
".config/devin",
|
|
71
|
+
".pi",
|
|
72
|
+
]) {
|
|
60
73
|
files[path.join(homeDir, directory)] = "<dir>";
|
|
61
74
|
}
|
|
62
75
|
for (const root of Object.values(skillRoots)) {
|
|
@@ -64,7 +77,9 @@ function fixture(overrides: Partial<AgentReadinessOptions> = {}) {
|
|
|
64
77
|
files[path.join(homeDir, root, skill, "SKILL.md")] = "# skill";
|
|
65
78
|
}
|
|
66
79
|
}
|
|
67
|
-
const executablePaths = Object.values(
|
|
80
|
+
const executablePaths = Object.values(HARNESS_RUNTIME_PROFILES).map((agent) =>
|
|
81
|
+
path.join("/bin", agent.command),
|
|
82
|
+
);
|
|
68
83
|
const options: AgentReadinessOptions = {
|
|
69
84
|
homeDir,
|
|
70
85
|
path: "/bin",
|
|
@@ -86,6 +101,8 @@ function fixture(overrides: Partial<AgentReadinessOptions> = {}) {
|
|
|
86
101
|
return { stdout: "github.com\n Logged in to github.com account test-user\n", stderr: "" };
|
|
87
102
|
if (command === "opencode")
|
|
88
103
|
return { stdout: "● litellm api\n● OpenAI oauth\n", stderr: "" };
|
|
104
|
+
if (command === "devin")
|
|
105
|
+
return { stdout: "Logged in (via Devin).\n\nUser:\n Name: test-user\n", stderr: "" };
|
|
89
106
|
throw new Error(`unexpected command ${command}`);
|
|
90
107
|
}),
|
|
91
108
|
codexAuth: async () => true,
|
|
@@ -103,20 +120,31 @@ describe("agent readiness", () => {
|
|
|
103
120
|
expect(Object.keys(reports)).toEqual([
|
|
104
121
|
"claude",
|
|
105
122
|
"codex",
|
|
106
|
-
"
|
|
123
|
+
"gemini_cli",
|
|
107
124
|
"grok_cli",
|
|
125
|
+
"kiro",
|
|
126
|
+
"antigravity_cli",
|
|
108
127
|
"opencode",
|
|
128
|
+
"copilot",
|
|
109
129
|
"pi",
|
|
130
|
+
"devin",
|
|
110
131
|
]);
|
|
111
|
-
expect(reports).not.toHaveProperty("gemini_cli");
|
|
112
132
|
for (const [agent, report] of Object.entries(reports) as Array<
|
|
113
133
|
[ReadinessAgentType, (typeof reports)[ReadinessAgentType]]
|
|
114
134
|
>) {
|
|
115
135
|
expect(report.type).toBe(agent);
|
|
116
136
|
expect(report.executable.status).toBe("pass");
|
|
117
137
|
expect(report.globalConfig.status).toBe("pass");
|
|
118
|
-
expect(report.builtInSkills).
|
|
138
|
+
expect(report.builtInSkills.status).toBe("info");
|
|
119
139
|
}
|
|
140
|
+
expect(reports.gemini_cli.builtInSkills).toMatchObject({ present: 3, required: 3 });
|
|
141
|
+
expect(reports.antigravity_cli.builtInSkills).toMatchObject({ present: 3, required: 3 });
|
|
142
|
+
expect(reports.kiro.builtInSkills).toMatchObject({
|
|
143
|
+
path: null,
|
|
144
|
+
present: 0,
|
|
145
|
+
required: 3,
|
|
146
|
+
missing: builtInSkillNames,
|
|
147
|
+
});
|
|
120
148
|
expect(reports.codex.integration).toMatchObject({
|
|
121
149
|
label: "ai-devkit hook",
|
|
122
150
|
installed: true,
|
|
@@ -133,13 +161,16 @@ describe("agent readiness", () => {
|
|
|
133
161
|
status: "pass",
|
|
134
162
|
});
|
|
135
163
|
expect(reports.copilot.integration).toBeUndefined();
|
|
164
|
+
expect(reports.gemini_cli.integration).toBeUndefined();
|
|
136
165
|
expect(reports.grok_cli.integration).toBeUndefined();
|
|
166
|
+
expect(reports.kiro.integration).toBeUndefined();
|
|
167
|
+
expect(reports.antigravity_cli.integration).toBeUndefined();
|
|
137
168
|
expect(reports.opencode.integration).toBeUndefined();
|
|
138
169
|
});
|
|
139
170
|
|
|
140
171
|
it("resolves agent executables from PATH directories containing spaces", async () => {
|
|
141
172
|
const spacedBin = "/Applications/Some App.app/Contents/Resources";
|
|
142
|
-
const codexPath = path.join(spacedBin,
|
|
173
|
+
const codexPath = path.join(spacedBin, HARNESS_RUNTIME_PROFILES.codex.command);
|
|
143
174
|
const { options } = fixture({
|
|
144
175
|
path: [spacedBin, "/usr/local/bin"].join(path.delimiter),
|
|
145
176
|
access: async (target) => {
|
|
@@ -255,4 +286,41 @@ describe("agent readiness", () => {
|
|
|
255
286
|
status: "pass",
|
|
256
287
|
});
|
|
257
288
|
});
|
|
289
|
+
|
|
290
|
+
it("checks Devin auth from devin auth status output", async () => {
|
|
291
|
+
const { options } = fixture({
|
|
292
|
+
runCommand: vi.fn(async (command) => {
|
|
293
|
+
if (command === "devin")
|
|
294
|
+
return { stdout: "Logged in (via Devin).\n\nUser:\n Name: test-user\n", stderr: "" };
|
|
295
|
+
if (command === "pi") return { stdout: "npm:@ai-devkit/pi-session-tracker\n", stderr: "" };
|
|
296
|
+
if (command === "claude") return { stdout: JSON.stringify({ loggedIn: true }), stderr: "" };
|
|
297
|
+
throw new Error(`unexpected command ${command}`);
|
|
298
|
+
}),
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const report = await getAgentReadinessReport("devin", options);
|
|
302
|
+
|
|
303
|
+
expect(report.auth).toMatchObject({
|
|
304
|
+
state: "authenticated",
|
|
305
|
+
source: "devin auth status",
|
|
306
|
+
provider: null,
|
|
307
|
+
availableProviders: ["devin"],
|
|
308
|
+
status: "pass",
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("reports Devin as unauthenticated without a login line", async () => {
|
|
313
|
+
const { options } = fixture({
|
|
314
|
+
runCommand: vi.fn(async (command) => {
|
|
315
|
+
if (command === "devin") return { stdout: "Not logged in\n", stderr: "" };
|
|
316
|
+
if (command === "pi") return { stdout: "npm:@ai-devkit/pi-session-tracker\n", stderr: "" };
|
|
317
|
+
if (command === "claude") return { stdout: JSON.stringify({ loggedIn: true }), stderr: "" };
|
|
318
|
+
throw new Error(`unexpected command ${command}`);
|
|
319
|
+
}),
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const report = await getAgentReadinessReport("devin", options);
|
|
323
|
+
|
|
324
|
+
expect(report.auth).toMatchObject({ state: "unauthenticated", status: "fail" });
|
|
325
|
+
});
|
|
258
326
|
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
HARNESS_RUNTIME_PROFILES,
|
|
7
|
+
runtimeAgentMatchesHarness,
|
|
8
|
+
} from "../../harnesses/runtimeProfiles.js";
|
|
9
|
+
|
|
10
|
+
describe("harness runtime profiles", () => {
|
|
11
|
+
it("keeps launch and runtime identity metadata together", () => {
|
|
12
|
+
expect(HARNESS_RUNTIME_PROFILES.antigravity_cli).toMatchObject({
|
|
13
|
+
command: "agy",
|
|
14
|
+
runtimeKind: "agy",
|
|
15
|
+
discoveryAliases: ["agy", "antigravity", "antigravity_cli"],
|
|
16
|
+
});
|
|
17
|
+
expect(HARNESS_RUNTIME_PROFILES.antigravity_cli.matches("/usr/local/bin/agy")).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("recognizes agent commands without matching similar argument text", () => {
|
|
21
|
+
expect(HARNESS_RUNTIME_PROFILES.copilot.matches("/opt/copilot-cli/1.0/copilot")).toBe(true);
|
|
22
|
+
expect(HARNESS_RUNTIME_PROFILES.copilot.matches("node /repo/copilot-cli/script.js")).toBe(
|
|
23
|
+
false,
|
|
24
|
+
);
|
|
25
|
+
expect(HARNESS_RUNTIME_PROFILES.grok_cli.matches("/Users/dev/.grok/bin/grok --approve")).toBe(
|
|
26
|
+
true,
|
|
27
|
+
);
|
|
28
|
+
expect(HARNESS_RUNTIME_PROFILES.grok_cli.matches("node /repo/grok-cli/script.js")).toBe(false);
|
|
29
|
+
expect(
|
|
30
|
+
HARNESS_RUNTIME_PROFILES.gemini_cli.matches("node --no-warnings /usr/local/bin/gemini"),
|
|
31
|
+
).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("matches runtime discovery names through the owning harness profile", () => {
|
|
35
|
+
expect(runtimeAgentMatchesHarness("kiro-cli", "kiro")).toBe(true);
|
|
36
|
+
expect(runtimeAgentMatchesHarness("gemini", "gemini_cli")).toBe(true);
|
|
37
|
+
expect(runtimeAgentMatchesHarness("codex", "claude")).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("harness matchers with executables under paths containing spaces", () => {
|
|
42
|
+
let root: string;
|
|
43
|
+
|
|
44
|
+
function install(relativePath: string): string {
|
|
45
|
+
const fullPath = path.join(root, relativePath);
|
|
46
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
47
|
+
fs.writeFileSync(fullPath, "");
|
|
48
|
+
return fullPath;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
root = fs.mkdtempSync(path.join(os.tmpdir(), "runtime-profiles-spaces-"));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("recognizes argv[0] under a spaced directory", () => {
|
|
60
|
+
const codex = install("Applications/Some App.app/Contents/Resources/codex");
|
|
61
|
+
const claude = install("Users/dev/My Tools/bin/claude");
|
|
62
|
+
|
|
63
|
+
expect(HARNESS_RUNTIME_PROFILES.codex.matches(`${codex} --sandbox workspace-write`)).toBe(true);
|
|
64
|
+
expect(HARNESS_RUNTIME_PROFILES.codex.matches(codex)).toBe(true);
|
|
65
|
+
expect(HARNESS_RUNTIME_PROFILES.claude.matches(`${claude} --continue`)).toBe(true);
|
|
66
|
+
expect(HARNESS_RUNTIME_PROFILES.claude.matches(`${codex} exec --cd /repo`)).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("recognizes argv[0] by distribution name under a spaced directory", () => {
|
|
70
|
+
const copilot = install("opt/Home Brew/Caskroom/copilot-cli/1.0.60/copilot");
|
|
71
|
+
|
|
72
|
+
expect(HARNESS_RUNTIME_PROFILES.copilot.matches(`${copilot} --allow-all-tools`)).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("does not treat arguments containing spaces as the executable", () => {
|
|
76
|
+
const vim = install("usr/bin/vim");
|
|
77
|
+
const codex = install("Applications/Some App.app/Contents/Resources/codex");
|
|
78
|
+
|
|
79
|
+
expect(HARNESS_RUNTIME_PROFILES.claude.matches(`${vim} My Notes/claude`)).toBe(false);
|
|
80
|
+
expect(
|
|
81
|
+
HARNESS_RUNTIME_PROFILES.claude.matches(`${codex} exec review the Other Dir/claude`),
|
|
82
|
+
).toBe(false);
|
|
83
|
+
expect(HARNESS_RUNTIME_PROFILES.copilot.matches(`${vim} Some Dir/copilot-cli/notes.md`)).toBe(
|
|
84
|
+
false,
|
|
85
|
+
);
|
|
86
|
+
expect(HARNESS_RUNTIME_PROFILES.claude.matches("/usr/bin/vim My Notes/claude")).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("recognizes spaced argv[0] in script-aware matchers", () => {
|
|
90
|
+
const pi = install("Users/dev/Node Tools/bin/pi");
|
|
91
|
+
const kiro = install("Applications/Kiro CLI.app/Contents/MacOS/kiro-cli");
|
|
92
|
+
const node = install("Applications/Dev Tools/node/bin/node");
|
|
93
|
+
|
|
94
|
+
expect(HARNESS_RUNTIME_PROFILES.pi.matches(`${pi} --model x`)).toBe(true);
|
|
95
|
+
expect(HARNESS_RUNTIME_PROFILES.kiro.matches(`${kiro} chat`)).toBe(true);
|
|
96
|
+
expect(
|
|
97
|
+
HARNESS_RUNTIME_PROFILES.gemini_cli.matches(`${node} /Users/dev/.npm/bin/gemini --yolo`),
|
|
98
|
+
).toBe(true);
|
|
99
|
+
expect(HARNESS_RUNTIME_PROFILES.pi.matches(`${node} /opt/pi-tools/script.js`)).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("does not match agent words inside an executable directory", () => {
|
|
103
|
+
const node = install("opt/Dev pi gemini/bin/node");
|
|
104
|
+
|
|
105
|
+
expect(HARNESS_RUNTIME_PROFILES.pi.matches(`${node} /repo/script.js`)).toBe(false);
|
|
106
|
+
expect(HARNESS_RUNTIME_PROFILES.gemini_cli.matches(`${node} /repo/script.js`)).toBe(false);
|
|
107
|
+
expect(HARNESS_RUNTIME_PROFILES.gemini_cli.matches(`${node} /repo/bin/gemini`)).toBe(true);
|
|
108
|
+
expect(HARNESS_RUNTIME_PROFILES.pi.matches(`${node} --title pi`)).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { DurableAgentBusyError } from "../../durable/DurableAgent.js";
|
|
2
3
|
|
|
3
|
-
describe("durable-agent
|
|
4
|
-
it("
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
expect(api).toHaveProperty("DurableAgentBusyError");
|
|
8
|
-
const ErrorType = api.DurableAgentBusyError as new (
|
|
9
|
-
agentId: string,
|
|
10
|
-
name: string,
|
|
11
|
-
) => Error & {
|
|
12
|
-
code: string;
|
|
13
|
-
agentId: string;
|
|
14
|
-
};
|
|
15
|
-
const error = new ErrorType("agent-id", "reviewer");
|
|
4
|
+
describe("durable-agent errors", () => {
|
|
5
|
+
it("classifies a busy error without exposing prompt data", () => {
|
|
6
|
+
const error = new DurableAgentBusyError("agent-id", "reviewer");
|
|
16
7
|
|
|
17
8
|
expect(error).toMatchObject({
|
|
18
9
|
name: "DurableAgentBusyError",
|
|
@@ -2,6 +2,7 @@ import fs from "fs";
|
|
|
2
2
|
import os from "os";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { DurableAgentRepository } from "../../durable/DurableAgentRepository.js";
|
|
5
6
|
|
|
6
7
|
const tempDirs: string[] = [];
|
|
7
8
|
|
|
@@ -9,12 +10,6 @@ afterEach(() => {
|
|
|
9
10
|
for (const dir of tempDirs.splice(0)) fs.rmSync(dir, { recursive: true, force: true });
|
|
10
11
|
});
|
|
11
12
|
|
|
12
|
-
async function loadStore(): Promise<any> {
|
|
13
|
-
const api = (await import("../../index.js")) as Record<string, unknown>;
|
|
14
|
-
expect(api).toHaveProperty("DurableAgentRepository");
|
|
15
|
-
return api.DurableAgentRepository;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
13
|
function fixture(): { root: string; cwd: string; dbPath: string } {
|
|
19
14
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "durable-agent-store-"));
|
|
20
15
|
tempDirs.push(root);
|
|
@@ -25,7 +20,6 @@ function fixture(): { root: string; cwd: string; dbPath: string } {
|
|
|
25
20
|
|
|
26
21
|
describe("DurableAgentRepository create/list/resolve", () => {
|
|
27
22
|
it("creates distinct durable identities with a canonical cwd and lists them", async () => {
|
|
28
|
-
const DurableAgentRepository = await loadStore();
|
|
29
23
|
const { cwd, dbPath } = fixture();
|
|
30
24
|
const repository = new DurableAgentRepository({
|
|
31
25
|
dbPath,
|
|
@@ -51,7 +45,6 @@ describe("DurableAgentRepository create/list/resolve", () => {
|
|
|
51
45
|
});
|
|
52
46
|
|
|
53
47
|
it("resolves exact ids and names and rejects duplicate names", async () => {
|
|
54
|
-
const DurableAgentRepository = await loadStore();
|
|
55
48
|
const { cwd, dbPath } = fixture();
|
|
56
49
|
const repository = new DurableAgentRepository({ dbPath });
|
|
57
50
|
const agent = await repository.create({ name: "Reviewer", cwd });
|
|
@@ -65,7 +58,6 @@ describe("DurableAgentRepository create/list/resolve", () => {
|
|
|
65
58
|
});
|
|
66
59
|
|
|
67
60
|
it("rejects a missing cwd", async () => {
|
|
68
|
-
const DurableAgentRepository = await loadStore();
|
|
69
61
|
const { root, dbPath } = fixture();
|
|
70
62
|
const repository = new DurableAgentRepository({ dbPath });
|
|
71
63
|
|
|
@@ -77,7 +69,6 @@ describe("DurableAgentRepository create/list/resolve", () => {
|
|
|
77
69
|
|
|
78
70
|
describe("DurableAgentRepository run ownership", () => {
|
|
79
71
|
it("fails fast when another exact owner is live and completes only for its token", async () => {
|
|
80
|
-
const DurableAgentRepository = await loadStore();
|
|
81
72
|
const { cwd, dbPath } = fixture();
|
|
82
73
|
const live = new Map<number, string>([[process.pid, "owner-start"]]);
|
|
83
74
|
const processInspector = {
|
|
@@ -112,7 +103,6 @@ describe("DurableAgentRepository run ownership", () => {
|
|
|
112
103
|
});
|
|
113
104
|
|
|
114
105
|
it("retains busy for a live provider then recovers a dead run without signaling it", async () => {
|
|
115
|
-
const DurableAgentRepository = await loadStore();
|
|
116
106
|
const { cwd, dbPath } = fixture();
|
|
117
107
|
const live = new Map<number, string>([
|
|
118
108
|
[process.pid, "owner-start"],
|
|
@@ -151,7 +141,6 @@ describe("DurableAgentRepository run ownership", () => {
|
|
|
151
141
|
});
|
|
152
142
|
|
|
153
143
|
it("reconciles an interrupted run to degraded during list", async () => {
|
|
154
|
-
const DurableAgentRepository = await loadStore();
|
|
155
144
|
const { cwd, dbPath } = fixture();
|
|
156
145
|
const live = new Map<number, string>([[process.pid, "owner-start"]]);
|
|
157
146
|
const repository = new DurableAgentRepository({
|
|
@@ -179,7 +168,6 @@ describe("DurableAgentRepository run ownership", () => {
|
|
|
179
168
|
});
|
|
180
169
|
|
|
181
170
|
it("rejects send acquisition when the bound cwd is replaced by a symlink", async () => {
|
|
182
|
-
const DurableAgentRepository = await loadStore();
|
|
183
171
|
const { root, cwd, dbPath } = fixture();
|
|
184
172
|
const repository = new DurableAgentRepository({ dbPath });
|
|
185
173
|
const agent = await repository.create({ name: "bound", cwd });
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "../../runtime/ManagedAgentRuntime.js";
|
|
16
16
|
import type { AgentInfo } from "../../adapters/AgentAdapter.js";
|
|
17
17
|
import type { AgentRegistry, RegistryEntry } from "../../utils/AgentRegistry.js";
|
|
18
|
-
import type { TmuxManager } from "../../
|
|
18
|
+
import type { TmuxManager } from "../../runtime/tmux/TmuxManager.js";
|
|
19
19
|
import type { InteractiveAgentRuntime } from "../../runtime/types.js";
|
|
20
20
|
|
|
21
21
|
function makeAgent(overrides: Partial<AgentInfo> = {}): AgentInfo {
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import { writeFile } from "fs/promises";
|
|
3
|
+
import type { Mock } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { ghosttyBackend } from "../../terminal/backends/ghostty.js";
|
|
6
|
+
import { TerminalType, type TerminalLocation } from "../../terminal/types.js";
|
|
7
|
+
import { TtyWriter } from "../../terminal/TtyWriter.js";
|
|
8
|
+
import { getProcessCwdsAsync } from "../../utils/process.js";
|
|
9
|
+
|
|
10
|
+
vi.mock("child_process", async () => {
|
|
11
|
+
const actual = await vi.importActual<typeof import("child_process")>("child_process");
|
|
12
|
+
return { ...actual, execFile: vi.fn() };
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
vi.mock("fs/promises", async () => {
|
|
16
|
+
const actual = await vi.importActual<typeof import("fs/promises")>("fs/promises");
|
|
17
|
+
return { ...actual, writeFile: vi.fn() };
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
vi.mock("../../utils/process.js", async () => {
|
|
21
|
+
const actual =
|
|
22
|
+
await vi.importActual<typeof import("../../utils/process.js")>("../../utils/process.js");
|
|
23
|
+
return { ...actual, getProcessCwdsAsync: vi.fn() };
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const mockedExecFile = execFile as unknown as Mock;
|
|
27
|
+
const mockedGetProcessCwds = getProcessCwdsAsync as unknown as Mock;
|
|
28
|
+
const mockedWriteFile = writeFile as unknown as Mock;
|
|
29
|
+
const SEP = "\x1f";
|
|
30
|
+
|
|
31
|
+
interface FakeTerminal {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
dir: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Fake `ps`, process cwds and Ghostty AppleScript. `processes` rows are
|
|
39
|
+
* `pid ppid tty comm`; `cwds` maps pid to its working directory. Writing an
|
|
40
|
+
* OSC 2 title to a TTY renames the terminal listed in `ttyOwner`.
|
|
41
|
+
*/
|
|
42
|
+
function fakeSystem(opts: {
|
|
43
|
+
processes: string[];
|
|
44
|
+
cwds?: Record<number, string>;
|
|
45
|
+
terminals: FakeTerminal[];
|
|
46
|
+
ttyOwner?: Record<string, string>;
|
|
47
|
+
osascriptResult?: string;
|
|
48
|
+
}) {
|
|
49
|
+
const terminals = opts.terminals.map((t) => ({ ...t }));
|
|
50
|
+
mockedGetProcessCwds.mockImplementation(async (pids: number[]) => {
|
|
51
|
+
const cwds = new Map<number, string>();
|
|
52
|
+
for (const pid of pids) if (opts.cwds?.[pid]) cwds.set(pid, opts.cwds[pid]);
|
|
53
|
+
return cwds;
|
|
54
|
+
});
|
|
55
|
+
mockedWriteFile.mockImplementation(async (tty: string, data: string) => {
|
|
56
|
+
// eslint-disable-next-line no-control-regex
|
|
57
|
+
const title = /^\x1b\]2;(.*)\x07$/.exec(data)?.[1];
|
|
58
|
+
const owner = terminals.find((t) => t.id === opts.ttyOwner?.[tty]);
|
|
59
|
+
if (owner && title !== undefined) owner.name = title;
|
|
60
|
+
});
|
|
61
|
+
mockedExecFile.mockImplementation((...args: unknown[]) => {
|
|
62
|
+
const [cmd, argv] = args as [string, string[]];
|
|
63
|
+
const cb = args[args.length - 1] as (err: Error | null, r?: { stdout: string }) => void;
|
|
64
|
+
let stdout = "";
|
|
65
|
+
if (cmd === "ps") stdout = opts.processes.join("\n") + "\n";
|
|
66
|
+
else if (cmd === "osascript" && argv[1].includes("repeat with t in terminals")) {
|
|
67
|
+
stdout = terminals.map((t) => [t.id, t.name, t.dir].join(SEP)).join("\n") + "\n";
|
|
68
|
+
} else if (cmd === "osascript") stdout = opts.osascriptResult ?? "ok\n";
|
|
69
|
+
cb(null, { stdout });
|
|
70
|
+
return {};
|
|
71
|
+
});
|
|
72
|
+
return terminals;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const GHOSTTY = "500 1 ?? /Applications/Ghostty.app/Contents/MacOS/ghostty";
|
|
76
|
+
|
|
77
|
+
describe("Ghostty terminal backend", () => {
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
vi.clearAllMocks();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("find", () => {
|
|
83
|
+
it("ignores TTYs when Ghostty is not running", async () => {
|
|
84
|
+
fakeSystem({ processes: ["600 1 ttys001 /usr/bin/login"], terminals: [] });
|
|
85
|
+
|
|
86
|
+
expect(await ghosttyBackend.find("/dev/ttys001")).toBeNull();
|
|
87
|
+
expect(mockedExecFile).not.toHaveBeenCalledWith(
|
|
88
|
+
"osascript",
|
|
89
|
+
expect.anything(),
|
|
90
|
+
expect.anything(),
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("ignores TTYs whose session was not started by Ghostty", async () => {
|
|
95
|
+
fakeSystem({
|
|
96
|
+
processes: [GHOSTTY, "600 500 ttys001 /usr/bin/login", "700 1 ttys002 /usr/bin/login"],
|
|
97
|
+
terminals: [{ id: "T1", name: "zsh", dir: "/repo" }],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(await ghosttyBackend.find("/dev/ttys002")).toBeNull();
|
|
101
|
+
expect(mockedWriteFile).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("matches by working directory when it pairs one terminal with one TTY", async () => {
|
|
105
|
+
fakeSystem({
|
|
106
|
+
processes: [
|
|
107
|
+
GHOSTTY,
|
|
108
|
+
"600 500 ttys001 /usr/bin/login",
|
|
109
|
+
"601 600 ttys001 -zsh",
|
|
110
|
+
"602 601 ttys001 claude",
|
|
111
|
+
"700 500 ttys002 /usr/bin/login",
|
|
112
|
+
"701 700 ttys002 -zsh",
|
|
113
|
+
],
|
|
114
|
+
cwds: { 601: "/repo/a", 602: "/repo/a", 701: "/repo/b" },
|
|
115
|
+
terminals: [
|
|
116
|
+
{ id: "T1", name: "claude", dir: "/repo/a" },
|
|
117
|
+
{ id: "T2", name: "zsh", dir: "/repo/b" },
|
|
118
|
+
],
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(await ghosttyBackend.find("/dev/ttys001")).toEqual({
|
|
122
|
+
type: TerminalType.GHOSTTY,
|
|
123
|
+
identifier: "T1",
|
|
124
|
+
tty: "/dev/ttys001",
|
|
125
|
+
});
|
|
126
|
+
expect(mockedWriteFile).not.toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("uses a title marker when two terminals share the directory, then restores the title", async () => {
|
|
130
|
+
const terminals = fakeSystem({
|
|
131
|
+
processes: [
|
|
132
|
+
GHOSTTY,
|
|
133
|
+
"600 500 ttys001 /usr/bin/login",
|
|
134
|
+
"601 600 ttys001 claude",
|
|
135
|
+
"700 500 ttys002 /usr/bin/login",
|
|
136
|
+
"701 700 ttys002 codex",
|
|
137
|
+
],
|
|
138
|
+
cwds: { 601: "/repo", 701: "/repo" },
|
|
139
|
+
terminals: [
|
|
140
|
+
{ id: "T1", name: "first agent", dir: "/repo" },
|
|
141
|
+
{ id: "T2", name: "second agent", dir: "/repo" },
|
|
142
|
+
],
|
|
143
|
+
ttyOwner: { "/dev/ttys001": "T1", "/dev/ttys002": "T2" },
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const location = await ghosttyBackend.find("/dev/ttys002");
|
|
147
|
+
|
|
148
|
+
expect(location?.identifier).toBe("T2");
|
|
149
|
+
const titles = mockedWriteFile.mock.calls.map(([tty, data]) => [tty, data]);
|
|
150
|
+
expect(titles).toHaveLength(2);
|
|
151
|
+
expect(titles[0][0]).toBe("/dev/ttys002");
|
|
152
|
+
// eslint-disable-next-line no-control-regex
|
|
153
|
+
expect(titles[0][1]).toMatch(/^\x1b\]2;ai-devkit-\d+-\d+\x07$/);
|
|
154
|
+
expect(titles[1]).toEqual(["/dev/ttys002", "\x1b]2;second agent\x07"]);
|
|
155
|
+
expect(terminals.find((t) => t.id === "T2")?.name).toBe("second agent");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("does not trust a unique directory match when another Ghostty TTY has a process there", async () => {
|
|
159
|
+
// T1's own working directory is unreported; T2 sits in the agent's directory.
|
|
160
|
+
fakeSystem({
|
|
161
|
+
processes: [
|
|
162
|
+
GHOSTTY,
|
|
163
|
+
"600 500 ttys001 /usr/bin/login",
|
|
164
|
+
"601 600 ttys001 claude",
|
|
165
|
+
"700 500 ttys002 /usr/bin/login",
|
|
166
|
+
"701 700 ttys002 -zsh",
|
|
167
|
+
],
|
|
168
|
+
cwds: { 601: "/repo", 701: "/repo" },
|
|
169
|
+
terminals: [
|
|
170
|
+
{ id: "T1", name: "claude", dir: "" },
|
|
171
|
+
{ id: "T2", name: "zsh", dir: "/repo" },
|
|
172
|
+
],
|
|
173
|
+
ttyOwner: { "/dev/ttys001": "T1", "/dev/ttys002": "T2" },
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect((await ghosttyBackend.find("/dev/ttys001"))?.identifier).toBe("T1");
|
|
177
|
+
expect(mockedWriteFile).toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("returns null when no terminal shows the marker", async () => {
|
|
181
|
+
vi.useFakeTimers();
|
|
182
|
+
try {
|
|
183
|
+
fakeSystem({
|
|
184
|
+
processes: [GHOSTTY, "600 500 ttys001 /usr/bin/login"],
|
|
185
|
+
terminals: [
|
|
186
|
+
{ id: "T1", name: "a", dir: "/x" },
|
|
187
|
+
{ id: "T2", name: "b", dir: "/y" },
|
|
188
|
+
],
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const result = ghosttyBackend.find("/dev/ttys001");
|
|
192
|
+
await vi.runAllTimersAsync();
|
|
193
|
+
|
|
194
|
+
expect(await result).toBeNull();
|
|
195
|
+
} finally {
|
|
196
|
+
vi.useRealTimers();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("returns null instead of throwing when Ghostty is not scriptable", async () => {
|
|
201
|
+
mockedGetProcessCwds.mockResolvedValue(new Map());
|
|
202
|
+
mockedExecFile.mockImplementation((...args: unknown[]) => {
|
|
203
|
+
const [cmd] = args as [string];
|
|
204
|
+
const cb = args[args.length - 1] as (err: Error | null, r?: { stdout: string }) => void;
|
|
205
|
+
if (cmd === "osascript") cb(new Error("Not authorized to send Apple events"));
|
|
206
|
+
else cb(null, { stdout: `${GHOSTTY}\n600 500 ttys001 /usr/bin/login\n` });
|
|
207
|
+
return {};
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
expect(await ghosttyBackend.find("/dev/ttys001")).toBeNull();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe("focus, send and sendKey", () => {
|
|
215
|
+
const location: TerminalLocation = {
|
|
216
|
+
type: TerminalType.GHOSTTY,
|
|
217
|
+
identifier: "T1",
|
|
218
|
+
tty: "/dev/ttys001",
|
|
219
|
+
};
|
|
220
|
+
const scripts = () =>
|
|
221
|
+
mockedExecFile.mock.calls.filter(([cmd]) => cmd === "osascript").map(([, argv]) => argv[1]);
|
|
222
|
+
|
|
223
|
+
it("focuses the terminal by id and activates Ghostty", async () => {
|
|
224
|
+
fakeSystem({ processes: [], terminals: [] });
|
|
225
|
+
|
|
226
|
+
expect(await ghosttyBackend.focus(location)).toBe(true);
|
|
227
|
+
expect(scripts()[0]).toContain('every terminal whose id is "T1"');
|
|
228
|
+
expect(scripts()[0]).toContain("focus t");
|
|
229
|
+
expect(scripts()[0]).toContain("activate");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("reports a failed focus when the terminal is gone", async () => {
|
|
233
|
+
fakeSystem({ processes: [], terminals: [], osascriptResult: "not_found\n" });
|
|
234
|
+
|
|
235
|
+
expect(await ghosttyBackend.focus(location)).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("pastes the message with input text, then types a carriage return", async () => {
|
|
239
|
+
fakeSystem({ processes: [], terminals: [] });
|
|
240
|
+
|
|
241
|
+
await TtyWriter.send(location, 'say "hi"\nnext');
|
|
242
|
+
|
|
243
|
+
expect(scripts()).toHaveLength(2);
|
|
244
|
+
expect(scripts()[0]).toContain('input text "say \\"hi\\"\\nnext" to t');
|
|
245
|
+
expect(scripts()[1]).toContain('perform action "text:\\\\x0d" on t');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("throws when the terminal is gone", async () => {
|
|
249
|
+
fakeSystem({ processes: [], terminals: [], osascriptResult: "not_found\n" });
|
|
250
|
+
|
|
251
|
+
await expect(TtyWriter.send(location, "hello")).rejects.toThrow(
|
|
252
|
+
"Ghostty terminal not found for TTY /dev/ttys001",
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Ghostty parses `text:` with Zig escapes; AppleScript doubles each backslash.
|
|
257
|
+
it.each([
|
|
258
|
+
["2", "text:2"],
|
|
259
|
+
["\x1b", "text:\\\\x1b"],
|
|
260
|
+
["\r", "text:\\\\x0d"],
|
|
261
|
+
["\\", "text:\\\\\\\\"],
|
|
262
|
+
])("types key %j with the Ghostty action %s", async (key, action) => {
|
|
263
|
+
fakeSystem({ processes: [], terminals: [] });
|
|
264
|
+
|
|
265
|
+
await TtyWriter.sendKey(location, key);
|
|
266
|
+
|
|
267
|
+
expect(scripts()[0]).toContain(`perform action "${action}" on t`);
|
|
268
|
+
expect(scripts()[0]).not.toContain("send key");
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile } from "child_process";
|
|
2
2
|
import type { MockedFunction } from "vitest";
|
|
3
|
-
import { TmuxManager } from "../../
|
|
3
|
+
import { TmuxManager } from "../../runtime/tmux/TmuxManager.js";
|
|
4
4
|
|
|
5
5
|
vi.mock("child_process", () => ({
|
|
6
6
|
execFile: vi.fn(),
|