@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 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/process.ts"],"sourcesContent":["/**\n * Process Detection Utilities\n *\n * Shared shell command wrappers for detecting and inspecting running processes.\n * Built-in refresh discovery uses captureProcessSnapshot(); synchronous helpers\n * remain exported for compatibility with existing consumers.\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { execFile, execFileSync } from \"child_process\";\nimport type { ProcessInfo } from \"../adapters/AgentAdapter.js\";\n\nconst PROCESS_EXEC_MAX_BUFFER = 10 * 1024 * 1024;\nconst VALID_EXECUTABLE_NAME = /^[a-zA-Z0-9_-]+$/;\nconst ABSOLUTE_PATH = /^(?:\\/|[a-zA-Z]:[\\\\/])/;\nconst PATH_SEPARATOR = /[\\\\/]/;\n\nfunction pathBasename(executable: string): string {\n return path.basename(executable.replace(/\\\\/g, \"/\")).toLowerCase();\n}\n\nfunction isFile(filePath: string): boolean {\n try {\n return fs.statSync(filePath, { throwIfNoEntry: false })?.isFile() ?? false;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve argv[0] from a `ps` command line.\n *\n * `ps` joins argv with spaces, so an executable installed under a directory\n * containing spaces (`/Applications/Some App.app/Contents/Resources/codex`)\n * spans several whitespace tokens. When the first token of an absolute path\n * is not itself a file, longer space-joined prefixes ending in a token with a\n * path separator are tried, and the first one that exists on disk wins.\n * Otherwise the first token is returned, so arguments are never folded into\n * the executable path unless that combined path really is a file.\n */\nexport function executablePath(command: string): string {\n const parts = command.trim().split(/(\\s+)/);\n const first = parts[0] ?? \"\";\n if (parts.length === 1 || !ABSOLUTE_PATH.test(first) || isFile(first)) return first;\n\n let prefix = first;\n for (let i = 2; i < parts.length; i += 2) {\n prefix += parts[i - 1] + parts[i];\n if (PATH_SEPARATOR.test(parts[i]) && isFile(prefix)) return prefix;\n }\n return first;\n}\n\nexport function executableBasename(command: string): string {\n return pathBasename(executablePath(command));\n}\n\nfunction normalizeExecutableName(name: string): string {\n const lower = name.toLowerCase();\n return lower.endsWith(\".exe\") ? lower.slice(0, -4) : lower;\n}\n\nfunction normalizedProcessNames(namePatterns: readonly string[]): Set<string> {\n return new Set(namePatterns.filter(Boolean).map(normalizeExecutableName));\n}\n\n/**\n * Whether argv[0] of `command` is one of `names`.\n *\n * The first token is checked with string operations only, as before. The\n * filesystem is consulted only for absolute commands where a later\n * path-like token ends in one of the names, i.e. a possible executable under\n * a directory containing spaces.\n */\nfunction matchesExecutableName(command: string, names: ReadonlySet<string>): boolean {\n const tokens = command.trim().split(/\\s+/);\n const first = tokens[0] ?? \"\";\n if (names.has(normalizeExecutableName(pathBasename(first)))) return true;\n if (!ABSOLUTE_PATH.test(first)) return false;\n\n const mayContinuePath = tokens\n .slice(1)\n .some(\n (token) =>\n PATH_SEPARATOR.test(token) && names.has(normalizeExecutableName(pathBasename(token))),\n );\n return mayContinuePath && names.has(normalizeExecutableName(executableBasename(command)));\n}\n\nexport function filterByProcessNames(\n processes: readonly ProcessInfo[],\n namePatterns: readonly string[],\n): ProcessInfo[] {\n const names = normalizedProcessNames(namePatterns);\n if (names.size === 0) return [];\n return processes.filter((process) => matchesExecutableName(process.command, names));\n}\n\n/**\n * List running processes matching an agent executable name.\n *\n * Uses `ps -axo` then filters in JS for exact executable basename match.\n * This avoids shell pipelines and string interpolation.\n *\n * Returned ProcessInfo has pid, ppid, command, tty populated.\n * cwd and startTime are NOT populated — call enrichProcesses() to fill them.\n */\nexport function listAgentProcesses(namePattern: string): ProcessInfo[] {\n // Validate pattern contains only safe characters (alphanumeric, dash, underscore)\n if (!namePattern || !VALID_EXECUTABLE_NAME.test(namePattern)) {\n return [];\n }\n\n try {\n const output = execFileSync(\"ps\", [\"-axo\", \"pid=,ppid=,tty=,command=\"], { encoding: \"utf-8\" });\n\n const names = normalizedProcessNames([namePattern]);\n const processes: ProcessInfo[] = [];\n\n for (const line of output.trim().split(\"\\n\")) {\n if (!line.trim()) continue;\n\n const match = line.match(/^\\s*(\\d+)\\s+(\\d+)\\s+(\\S+)\\s+(.+)$/);\n if (!match) continue;\n\n const pid = parseInt(match[1], 10);\n const ppid = parseInt(match[2], 10);\n if (Number.isNaN(pid) || Number.isNaN(ppid)) continue;\n\n const tty = match[3];\n const command = match[4];\n\n if (!matchesExecutableName(command, names)) continue;\n\n const ttyShort = tty.startsWith(\"/dev/\") ? tty.slice(5) : tty;\n\n processes.push({\n pid,\n ppid,\n command,\n cwd: \"\",\n tty: ttyShort,\n });\n }\n\n return processes;\n } catch {\n return [];\n }\n}\n\n/** Runs an executable and resolves with its stdout. Injectable for tests. */\nexport type ProcessExec = (file: string, args: readonly string[]) => Promise<string>;\n\nexport interface ProcessSnapshotOptions {\n /**\n * Decides which name-matched processes are worth enriching with cwd and\n * start time. Non-candidates are still returned (pid, ppid, tty, command\n * only) so adapters can walk parent chains. Omitted = enrich everything.\n */\n isCandidate?: (process: ProcessInfo) => boolean;\n}\n\nexport type ProcessSnapshotCapture = (\n namePatterns: readonly string[],\n options?: ProcessSnapshotOptions,\n) => Promise<ProcessInfo[]>;\n\nconst execFileText: ProcessExec = (file, args) =>\n new Promise((resolve, reject) => {\n execFile(\n file,\n args,\n { encoding: \"utf-8\", maxBuffer: PROCESS_EXEC_MAX_BUFFER },\n (error, stdout) => {\n if (error) {\n reject(error);\n return;\n }\n resolve(stdout);\n },\n );\n });\n\ninterface ParsedProcessList {\n /** Every PID in the listing, used to evict cache entries for exited processes. */\n allPids: Set<number>;\n /** Processes whose executable matches one of the requested names. */\n matched: ProcessInfo[];\n}\n\nfunction parseProcessList(output: string, namePatterns: ReadonlySet<string>): ParsedProcessList {\n const allPids = new Set<number>();\n const matched: ProcessInfo[] = [];\n\n for (const line of output.trim().split(\"\\n\")) {\n if (!line.trim()) continue;\n\n const match = line.match(/^\\s*(\\d+)\\s+(\\d+)\\s+(\\S+)\\s+(.+)$/);\n if (!match) continue;\n\n const pid = parseInt(match[1], 10);\n const ppid = parseInt(match[2], 10);\n if (Number.isNaN(pid) || Number.isNaN(ppid)) continue;\n allPids.add(pid);\n\n const tty = match[3];\n const command = match[4];\n if (!matchesExecutableName(command, namePatterns)) continue;\n\n matched.push({\n pid,\n ppid,\n command,\n cwd: \"\",\n tty: tty.startsWith(\"/dev/\") ? tty.slice(5) : tty,\n });\n }\n\n return { allPids, matched };\n}\n\nasync function batchGetProcessCwdsAsync(\n exec: ProcessExec,\n pids: number[],\n): Promise<Map<number, string>> {\n const result = new Map<number, string>();\n if (pids.length === 0) return result;\n\n try {\n const output = await exec(\"lsof\", [\"-a\", \"-d\", \"cwd\", \"-Fn\", \"-p\", pids.join(\",\")]);\n let currentPid: number | null = null;\n for (const line of output.trim().split(\"\\n\")) {\n if (line.startsWith(\"p\")) {\n currentPid = parseInt(line.slice(1), 10);\n } else if (line.startsWith(\"n\") && currentPid !== null) {\n result.set(currentPid, line.slice(1));\n currentPid = null;\n }\n }\n return result;\n } catch {\n const entries = await Promise.all(\n pids.map(async (pid) => {\n try {\n const output = await exec(\"pwdx\", [String(pid)]);\n const match = output.match(/^\\d+:\\s*(.+)$/);\n return match ? ([pid, match[1].trim()] as const) : null;\n } catch {\n return null;\n }\n }),\n );\n for (const entry of entries) {\n if (entry) result.set(entry[0], entry[1]);\n }\n return result;\n }\n}\n\nasync function batchGetProcessStartTimesAsync(\n exec: ProcessExec,\n pids: number[],\n): Promise<Map<number, Date>> {\n const result = new Map<number, Date>();\n if (pids.length === 0) return result;\n\n try {\n const output = await exec(\"ps\", [\"-o\", \"pid=,lstart=\", \"-p\", pids.join(\",\")]);\n for (const rawLine of output.split(\"\\n\")) {\n const match = rawLine.trim().match(/^(\\d+)\\s+(.+)$/);\n if (!match) continue;\n const pid = parseInt(match[1], 10);\n const date = new Date(match[2].trim());\n if (Number.isFinite(pid) && !Number.isNaN(date.getTime())) result.set(pid, date);\n }\n } catch {\n // Return partial/empty data, matching the synchronous helper.\n }\n return result;\n}\n\ninterface CachedStartTime {\n /** ppid + command seen when the start time was read; a mismatch means PID reuse. */\n identity: string;\n startTime: Date;\n}\n\nfunction processIdentity(process: ProcessInfo): string {\n return `${process.ppid ?? \"\"}\\0${process.command}`;\n}\n\n/**\n * Create a snapshot capture function with its own start-time cache.\n *\n * Start times never change for a live process, so each PID is queried with\n * `ps lstart` once and reused on later refreshes. A cache entry is dropped\n * when its PID disappears from the base listing, and ignored when the PID's\n * ppid/command no longer match (PID reuse). cwd is re-read every refresh\n * because a process can change directory.\n */\nexport function createProcessSnapshotCapture(\n deps: { exec?: ProcessExec } = {},\n): ProcessSnapshotCapture {\n const exec = deps.exec ?? execFileText;\n const startTimeCache = new Map<number, CachedStartTime>();\n\n const cachedStartTime = (process: ProcessInfo): Date | undefined => {\n const cached = startTimeCache.get(process.pid);\n return cached?.identity === processIdentity(process) ? cached.startTime : undefined;\n };\n\n return async (namePatterns, options = {}) => {\n const names = normalizedProcessNames(\n namePatterns.filter((name) => Boolean(name) && VALID_EXECUTABLE_NAME.test(name)),\n );\n if (names.size === 0) return [];\n\n try {\n const output = await exec(\"ps\", [\"-axo\", \"pid=,ppid=,tty=,command=\"]);\n const { allPids, matched } = parseProcessList(output, names);\n\n for (const pid of startTimeCache.keys()) {\n if (!allPids.has(pid)) startTimeCache.delete(pid);\n }\n\n const { isCandidate } = options;\n const candidates = isCandidate ? matched.filter((process) => isCandidate(process)) : matched;\n const candidatePids = candidates.map((process) => process.pid);\n const uncachedPids = candidates\n .filter((process) => cachedStartTime(process) === undefined)\n .map((process) => process.pid);\n\n const [cwdMap, startTimeMap] = await Promise.all([\n batchGetProcessCwdsAsync(exec, candidatePids),\n batchGetProcessStartTimesAsync(exec, uncachedPids),\n ]);\n\n for (const process of candidates) {\n const startTime = startTimeMap.get(process.pid);\n if (startTime) {\n startTimeCache.set(process.pid, { identity: processIdentity(process), startTime });\n }\n }\n\n const candidateSet = new Set(candidatePids);\n return matched.map((process) =>\n candidateSet.has(process.pid)\n ? {\n ...process,\n cwd: cwdMap.get(process.pid) || \"\",\n startTime: cachedStartTime(process),\n }\n : process,\n );\n } catch {\n return [];\n }\n };\n}\n\n/**\n * Capture and enrich relevant processes without blocking the event loop.\n * One base process listing is shared by every requested executable name;\n * only processes accepted by `options.isCandidate` are enriched.\n */\nexport const captureProcessSnapshot: ProcessSnapshotCapture = createProcessSnapshotCapture();\n\n/**\n * Batch-get current working directories for multiple PIDs.\n *\n * Single `lsof -a -d cwd -Fn -p PID1,PID2,...` call.\n * Returns partial results — if lsof fails for one PID, others still return.\n */\nexport function batchGetProcessCwds(pids: number[]): Map<number, string> {\n const result = new Map<number, string>();\n if (pids.length === 0) return result;\n\n try {\n const output = execFileSync(\"lsof\", [\"-a\", \"-d\", \"cwd\", \"-Fn\", \"-p\", pids.join(\",\")], {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n });\n\n // lsof output format: p{PID}\\nn{path}\\np{PID}\\nn{path}...\n let currentPid: number | null = null;\n for (const line of output.trim().split(\"\\n\")) {\n if (line.startsWith(\"p\")) {\n currentPid = parseInt(line.slice(1), 10);\n } else if (line.startsWith(\"n\") && currentPid !== null) {\n result.set(currentPid, line.slice(1));\n currentPid = null;\n }\n }\n } catch {\n // Try per-PID fallback with pwdx (Linux)\n for (const pid of pids) {\n try {\n const output = execFileSync(\"pwdx\", [String(pid)], {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n });\n const match = output.match(/^\\d+:\\s*(.+)$/);\n if (match) {\n result.set(pid, match[1].trim());\n }\n } catch {\n // Skip this PID\n }\n }\n }\n\n return result;\n}\n\n/**\n * Batch-get process start times for multiple PIDs.\n *\n * Single `ps -o pid=,lstart= -p PID1,PID2,...` call.\n * Uses lstart format which gives full timestamp (e.g., \"Thu Feb 5 16:00:57 2026\").\n * Returns partial results.\n */\nexport function batchGetProcessStartTimes(pids: number[]): Map<number, Date> {\n const result = new Map<number, Date>();\n if (pids.length === 0) return result;\n\n try {\n const output = execFileSync(\"ps\", [\"-o\", \"pid=,lstart=\", \"-p\", pids.join(\",\")], {\n encoding: \"utf-8\",\n });\n\n for (const rawLine of output.split(\"\\n\")) {\n const line = rawLine.trim();\n if (!line) continue;\n\n // Format: \" PID DAY MON DD HH:MM:SS YYYY\"\n // e.g., \" 78070 Wed Mar 18 23:18:01 2026\"\n const match = line.match(/^\\s*(\\d+)\\s+(.+)$/);\n if (!match) continue;\n\n const pid = parseInt(match[1], 10);\n const dateStr = match[2].trim();\n\n if (!Number.isFinite(pid)) continue;\n\n const date = new Date(dateStr);\n if (!Number.isNaN(date.getTime())) {\n result.set(pid, date);\n }\n }\n } catch {\n // Return whatever we have\n }\n\n return result;\n}\n\n/**\n * Enrich ProcessInfo array with cwd and startTime.\n *\n * Calls batchGetProcessCwds and batchGetProcessStartTimes in batched shell calls,\n * then populates each ProcessInfo in-place. Returns partial results —\n * if a PID fails, that process keeps empty cwd / undefined startTime.\n */\nexport function enrichProcesses(processes: ProcessInfo[]): ProcessInfo[] {\n if (processes.length === 0) return processes;\n\n const pids = processes.map((p) => p.pid);\n const cwdMap = batchGetProcessCwds(pids);\n const startTimeMap = batchGetProcessStartTimes(pids);\n\n for (const proc of processes) {\n proc.cwd = cwdMap.get(proc.pid) || \"\";\n proc.startTime = startTimeMap.get(proc.pid);\n }\n\n return processes;\n}\n\nfunction isSameTerminalProcess(proc: ProcessInfo, matched: ProcessInfo): boolean {\n const sameTty = proc.tty !== \"\" && proc.tty !== \"?\" && proc.tty === matched.tty;\n const sameCwd = proc.cwd !== \"\" && proc.cwd === matched.cwd;\n\n return sameTty && (sameCwd || proc.cwd === \"\" || matched.cwd === \"\");\n}\n\nfunction matchesProcessIdentity(proc: ProcessInfo, matched: ProcessInfo): boolean {\n return proc.pid === matched.pid || isSameTerminalProcess(proc, matched);\n}\n\nexport function findWrapperProcess(\n processes: ProcessInfo[],\n child: ProcessInfo,\n): ProcessInfo | undefined {\n return processes.find(\n (proc) =>\n proc.pid !== child.pid && child.ppid === proc.pid && matchesProcessIdentity(proc, child),\n );\n}\n\n/**\n * Find parent wrapper processes that should not be reported as separate agents.\n *\n * A process is considered a wrapper when it is the parent of another candidate\n * agent process in the same terminal/worktree, or when it points at the same\n * terminal/worktree as an already session-matched process.\n */\nexport function findWrapperProcessPids(\n processes: ProcessInfo[],\n matchedProcesses: ProcessInfo[] = [],\n): Set<number> {\n const wrappers = new Set<number>();\n\n for (const child of processes) {\n const wrapper = findWrapperProcess(processes, child);\n if (wrapper) {\n wrappers.add(wrapper.pid);\n }\n }\n\n for (const proc of processes) {\n if (\n matchedProcesses.some(\n (matched) => proc.pid !== matched.pid && isSameTerminalProcess(proc, matched),\n )\n ) {\n wrappers.add(proc.pid);\n }\n }\n\n return wrappers;\n}\n\n/**\n * Get the TTY device for a specific process\n */\nexport function getProcessTty(pid: number): string {\n try {\n const output = execFileSync(\"ps\", [\"-p\", String(pid), \"-o\", \"tty=\"], { encoding: \"utf-8\" });\n\n const tty = output.trim();\n return tty.startsWith(\"/dev/\") ? tty.slice(5) : tty;\n } catch {\n return \"?\";\n }\n}\n"],"names":["fs","path","execFile","execFileSync","PROCESS_EXEC_MAX_BUFFER","VALID_EXECUTABLE_NAME","ABSOLUTE_PATH","PATH_SEPARATOR","pathBasename","executable","basename","replace","toLowerCase","isFile","filePath","statSync","throwIfNoEntry","executablePath","command","parts","trim","split","first","length","test","prefix","i","executableBasename","normalizeExecutableName","name","lower","endsWith","slice","normalizedProcessNames","namePatterns","Set","filter","Boolean","map","matchesExecutableName","names","tokens","has","mayContinuePath","some","token","filterByProcessNames","processes","size","process","listAgentProcesses","namePattern","output","encoding","line","match","pid","parseInt","ppid","Number","isNaN","tty","ttyShort","startsWith","push","cwd","execFileText","file","args","Promise","resolve","reject","maxBuffer","error","stdout","parseProcessList","allPids","matched","add","batchGetProcessCwdsAsync","exec","pids","result","Map","join","currentPid","set","entries","all","String","entry","batchGetProcessStartTimesAsync","rawLine","date","Date","isFinite","getTime","processIdentity","createProcessSnapshotCapture","deps","startTimeCache","cachedStartTime","cached","get","identity","startTime","undefined","options","keys","delete","isCandidate","candidates","candidatePids","uncachedPids","cwdMap","startTimeMap","candidateSet","captureProcessSnapshot","batchGetProcessCwds","stdio","batchGetProcessStartTimes","dateStr","enrichProcesses","p","proc","isSameTerminalProcess","sameTty","sameCwd","matchesProcessIdentity","findWrapperProcess","child","find","findWrapperProcessPids","matchedProcesses","wrappers","wrapper","getProcessTty"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/process.ts"],"sourcesContent":["/**\n * Process Detection Utilities\n *\n * Shared shell command wrappers for detecting and inspecting running processes.\n * Built-in refresh discovery uses captureProcessSnapshot(); synchronous helpers\n * remain exported for compatibility with existing consumers.\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { execFile, execFileSync } from \"child_process\";\nimport type { ProcessInfo } from \"../adapters/AgentAdapter.js\";\n\nconst PROCESS_EXEC_MAX_BUFFER = 10 * 1024 * 1024;\nconst VALID_EXECUTABLE_NAME = /^[a-zA-Z0-9_-]+$/;\nconst ABSOLUTE_PATH = /^(?:\\/|[a-zA-Z]:[\\\\/])/;\nconst PATH_SEPARATOR = /[\\\\/]/;\n\nfunction pathBasename(executable: string): string {\n return path.basename(executable.replace(/\\\\/g, \"/\")).toLowerCase();\n}\n\nfunction isFile(filePath: string): boolean {\n try {\n return fs.statSync(filePath, { throwIfNoEntry: false })?.isFile() ?? false;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve argv[0] from a `ps` command line.\n *\n * `ps` joins argv with spaces, so an executable installed under a directory\n * containing spaces (`/Applications/Some App.app/Contents/Resources/codex`)\n * spans several whitespace tokens. When the first token of an absolute path\n * is not itself a file, longer space-joined prefixes ending in a token with a\n * path separator are tried, and the first one that exists on disk wins.\n * Otherwise the first token is returned, so arguments are never folded into\n * the executable path unless that combined path really is a file.\n */\nexport function executablePath(command: string): string {\n const parts = command.trim().split(/(\\s+)/);\n const first = parts[0] ?? \"\";\n if (parts.length === 1 || !ABSOLUTE_PATH.test(first) || isFile(first)) return first;\n\n let prefix = first;\n for (let i = 2; i < parts.length; i += 2) {\n prefix += parts[i - 1] + parts[i];\n if (PATH_SEPARATOR.test(parts[i]) && isFile(prefix)) return prefix;\n }\n return first;\n}\n\nexport function executableBasename(command: string): string {\n return pathBasename(executablePath(command));\n}\n\nfunction normalizeExecutableName(name: string): string {\n const lower = name.toLowerCase();\n return lower.endsWith(\".exe\") ? lower.slice(0, -4) : lower;\n}\n\nfunction normalizedProcessNames(namePatterns: readonly string[]): Set<string> {\n return new Set(namePatterns.filter(Boolean).map(normalizeExecutableName));\n}\n\n/**\n * Whether argv[0] of `command` is one of `names`.\n *\n * The first token is checked with string operations only, as before. The\n * filesystem is consulted only for absolute commands where a later\n * path-like token ends in one of the names, i.e. a possible executable under\n * a directory containing spaces.\n */\nfunction matchesExecutableName(command: string, names: ReadonlySet<string>): boolean {\n const tokens = command.trim().split(/\\s+/);\n const first = tokens[0] ?? \"\";\n if (names.has(normalizeExecutableName(pathBasename(first)))) return true;\n if (!ABSOLUTE_PATH.test(first)) return false;\n\n const mayContinuePath = tokens\n .slice(1)\n .some(\n (token) =>\n PATH_SEPARATOR.test(token) && names.has(normalizeExecutableName(pathBasename(token))),\n );\n return mayContinuePath && names.has(normalizeExecutableName(executableBasename(command)));\n}\n\nexport function filterByProcessNames(\n processes: readonly ProcessInfo[],\n namePatterns: readonly string[],\n): ProcessInfo[] {\n const names = normalizedProcessNames(namePatterns);\n if (names.size === 0) return [];\n return processes.filter((process) => matchesExecutableName(process.command, names));\n}\n\n/**\n * List running processes matching an agent executable name.\n *\n * Uses `ps -axo` then filters in JS for exact executable basename match.\n * This avoids shell pipelines and string interpolation.\n *\n * Returned ProcessInfo has pid, ppid, command, tty populated.\n * cwd and startTime are NOT populated — call enrichProcesses() to fill them.\n */\nexport function listAgentProcesses(namePattern: string): ProcessInfo[] {\n // Validate pattern contains only safe characters (alphanumeric, dash, underscore)\n if (!namePattern || !VALID_EXECUTABLE_NAME.test(namePattern)) {\n return [];\n }\n\n try {\n const output = execFileSync(\"ps\", [\"-axo\", \"pid=,ppid=,tty=,command=\"], { encoding: \"utf-8\" });\n\n const names = normalizedProcessNames([namePattern]);\n const processes: ProcessInfo[] = [];\n\n for (const line of output.trim().split(\"\\n\")) {\n if (!line.trim()) continue;\n\n const match = line.match(/^\\s*(\\d+)\\s+(\\d+)\\s+(\\S+)\\s+(.+)$/);\n if (!match) continue;\n\n const pid = parseInt(match[1], 10);\n const ppid = parseInt(match[2], 10);\n if (Number.isNaN(pid) || Number.isNaN(ppid)) continue;\n\n const tty = match[3];\n const command = match[4];\n\n if (!matchesExecutableName(command, names)) continue;\n\n const ttyShort = tty.startsWith(\"/dev/\") ? tty.slice(5) : tty;\n\n processes.push({\n pid,\n ppid,\n command,\n cwd: \"\",\n tty: ttyShort,\n });\n }\n\n return processes;\n } catch {\n return [];\n }\n}\n\n/** Runs an executable and resolves with its stdout. Injectable for tests. */\nexport type ProcessExec = (file: string, args: readonly string[]) => Promise<string>;\n\nexport interface ProcessSnapshotOptions {\n /**\n * Decides which name-matched processes are worth enriching with cwd and\n * start time. Non-candidates are still returned (pid, ppid, tty, command\n * only) so adapters can walk parent chains. Omitted = enrich everything.\n */\n isCandidate?: (process: ProcessInfo) => boolean;\n}\n\nexport type ProcessSnapshotCapture = (\n namePatterns: readonly string[],\n options?: ProcessSnapshotOptions,\n) => Promise<ProcessInfo[]>;\n\nconst execFileText: ProcessExec = (file, args) =>\n new Promise((resolve, reject) => {\n execFile(\n file,\n args,\n { encoding: \"utf-8\", maxBuffer: PROCESS_EXEC_MAX_BUFFER },\n (error, stdout) => {\n if (error) {\n reject(error);\n return;\n }\n resolve(stdout);\n },\n );\n });\n\ninterface ParsedProcessList {\n /** Every PID in the listing, used to evict cache entries for exited processes. */\n allPids: Set<number>;\n /** Processes whose executable matches one of the requested names. */\n matched: ProcessInfo[];\n}\n\nfunction parseProcessList(output: string, namePatterns: ReadonlySet<string>): ParsedProcessList {\n const allPids = new Set<number>();\n const matched: ProcessInfo[] = [];\n\n for (const line of output.trim().split(\"\\n\")) {\n if (!line.trim()) continue;\n\n const match = line.match(/^\\s*(\\d+)\\s+(\\d+)\\s+(\\S+)\\s+(.+)$/);\n if (!match) continue;\n\n const pid = parseInt(match[1], 10);\n const ppid = parseInt(match[2], 10);\n if (Number.isNaN(pid) || Number.isNaN(ppid)) continue;\n allPids.add(pid);\n\n const tty = match[3];\n const command = match[4];\n if (!matchesExecutableName(command, namePatterns)) continue;\n\n matched.push({\n pid,\n ppid,\n command,\n cwd: \"\",\n tty: tty.startsWith(\"/dev/\") ? tty.slice(5) : tty,\n });\n }\n\n return { allPids, matched };\n}\n\n/** Working directories of `pids` (async `lsof`); PIDs it cannot read are omitted. */\nexport function getProcessCwdsAsync(pids: number[]): Promise<Map<number, string>> {\n return batchGetProcessCwdsAsync(execFileText, pids);\n}\n\nasync function batchGetProcessCwdsAsync(\n exec: ProcessExec,\n pids: number[],\n): Promise<Map<number, string>> {\n const result = new Map<number, string>();\n if (pids.length === 0) return result;\n\n try {\n const output = await exec(\"lsof\", [\"-a\", \"-d\", \"cwd\", \"-Fn\", \"-p\", pids.join(\",\")]);\n let currentPid: number | null = null;\n for (const line of output.trim().split(\"\\n\")) {\n if (line.startsWith(\"p\")) {\n currentPid = parseInt(line.slice(1), 10);\n } else if (line.startsWith(\"n\") && currentPid !== null) {\n result.set(currentPid, line.slice(1));\n currentPid = null;\n }\n }\n return result;\n } catch {\n const entries = await Promise.all(\n pids.map(async (pid) => {\n try {\n const output = await exec(\"pwdx\", [String(pid)]);\n const match = output.match(/^\\d+:\\s*(.+)$/);\n return match ? ([pid, match[1].trim()] as const) : null;\n } catch {\n return null;\n }\n }),\n );\n for (const entry of entries) {\n if (entry) result.set(entry[0], entry[1]);\n }\n return result;\n }\n}\n\nasync function batchGetProcessStartTimesAsync(\n exec: ProcessExec,\n pids: number[],\n): Promise<Map<number, Date>> {\n const result = new Map<number, Date>();\n if (pids.length === 0) return result;\n\n try {\n const output = await exec(\"ps\", [\"-o\", \"pid=,lstart=\", \"-p\", pids.join(\",\")]);\n for (const rawLine of output.split(\"\\n\")) {\n const match = rawLine.trim().match(/^(\\d+)\\s+(.+)$/);\n if (!match) continue;\n const pid = parseInt(match[1], 10);\n const date = new Date(match[2].trim());\n if (Number.isFinite(pid) && !Number.isNaN(date.getTime())) result.set(pid, date);\n }\n } catch {\n // Return partial/empty data, matching the synchronous helper.\n }\n return result;\n}\n\ninterface CachedStartTime {\n /** ppid + command seen when the start time was read; a mismatch means PID reuse. */\n identity: string;\n startTime: Date;\n}\n\nfunction processIdentity(process: ProcessInfo): string {\n return `${process.ppid ?? \"\"}\\0${process.command}`;\n}\n\n/**\n * Create a snapshot capture function with its own start-time cache.\n *\n * Start times never change for a live process, so each PID is queried with\n * `ps lstart` once and reused on later refreshes. A cache entry is dropped\n * when its PID disappears from the base listing, and ignored when the PID's\n * ppid/command no longer match (PID reuse). cwd is re-read every refresh\n * because a process can change directory.\n */\nexport function createProcessSnapshotCapture(\n deps: { exec?: ProcessExec } = {},\n): ProcessSnapshotCapture {\n const exec = deps.exec ?? execFileText;\n const startTimeCache = new Map<number, CachedStartTime>();\n\n const cachedStartTime = (process: ProcessInfo): Date | undefined => {\n const cached = startTimeCache.get(process.pid);\n return cached?.identity === processIdentity(process) ? cached.startTime : undefined;\n };\n\n return async (namePatterns, options = {}) => {\n const names = normalizedProcessNames(\n namePatterns.filter((name) => Boolean(name) && VALID_EXECUTABLE_NAME.test(name)),\n );\n if (names.size === 0) return [];\n\n try {\n const output = await exec(\"ps\", [\"-axo\", \"pid=,ppid=,tty=,command=\"]);\n const { allPids, matched } = parseProcessList(output, names);\n\n for (const pid of startTimeCache.keys()) {\n if (!allPids.has(pid)) startTimeCache.delete(pid);\n }\n\n const { isCandidate } = options;\n const candidates = isCandidate ? matched.filter((process) => isCandidate(process)) : matched;\n const candidatePids = candidates.map((process) => process.pid);\n const uncachedPids = candidates\n .filter((process) => cachedStartTime(process) === undefined)\n .map((process) => process.pid);\n\n const [cwdMap, startTimeMap] = await Promise.all([\n batchGetProcessCwdsAsync(exec, candidatePids),\n batchGetProcessStartTimesAsync(exec, uncachedPids),\n ]);\n\n for (const process of candidates) {\n const startTime = startTimeMap.get(process.pid);\n if (startTime) {\n startTimeCache.set(process.pid, { identity: processIdentity(process), startTime });\n }\n }\n\n const candidateSet = new Set(candidatePids);\n return matched.map((process) =>\n candidateSet.has(process.pid)\n ? {\n ...process,\n cwd: cwdMap.get(process.pid) || \"\",\n startTime: cachedStartTime(process),\n }\n : process,\n );\n } catch {\n return [];\n }\n };\n}\n\n/**\n * Capture and enrich relevant processes without blocking the event loop.\n * One base process listing is shared by every requested executable name;\n * only processes accepted by `options.isCandidate` are enriched.\n */\nexport const captureProcessSnapshot: ProcessSnapshotCapture = createProcessSnapshotCapture();\n\n/**\n * Batch-get current working directories for multiple PIDs.\n *\n * Single `lsof -a -d cwd -Fn -p PID1,PID2,...` call.\n * Returns partial results — if lsof fails for one PID, others still return.\n */\nexport function batchGetProcessCwds(pids: number[]): Map<number, string> {\n const result = new Map<number, string>();\n if (pids.length === 0) return result;\n\n try {\n const output = execFileSync(\"lsof\", [\"-a\", \"-d\", \"cwd\", \"-Fn\", \"-p\", pids.join(\",\")], {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n });\n\n // lsof output format: p{PID}\\nn{path}\\np{PID}\\nn{path}...\n let currentPid: number | null = null;\n for (const line of output.trim().split(\"\\n\")) {\n if (line.startsWith(\"p\")) {\n currentPid = parseInt(line.slice(1), 10);\n } else if (line.startsWith(\"n\") && currentPid !== null) {\n result.set(currentPid, line.slice(1));\n currentPid = null;\n }\n }\n } catch {\n // Try per-PID fallback with pwdx (Linux)\n for (const pid of pids) {\n try {\n const output = execFileSync(\"pwdx\", [String(pid)], {\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n });\n const match = output.match(/^\\d+:\\s*(.+)$/);\n if (match) {\n result.set(pid, match[1].trim());\n }\n } catch {\n // Skip this PID\n }\n }\n }\n\n return result;\n}\n\n/**\n * Batch-get process start times for multiple PIDs.\n *\n * Single `ps -o pid=,lstart= -p PID1,PID2,...` call.\n * Uses lstart format which gives full timestamp (e.g., \"Thu Feb 5 16:00:57 2026\").\n * Returns partial results.\n */\nexport function batchGetProcessStartTimes(pids: number[]): Map<number, Date> {\n const result = new Map<number, Date>();\n if (pids.length === 0) return result;\n\n try {\n const output = execFileSync(\"ps\", [\"-o\", \"pid=,lstart=\", \"-p\", pids.join(\",\")], {\n encoding: \"utf-8\",\n });\n\n for (const rawLine of output.split(\"\\n\")) {\n const line = rawLine.trim();\n if (!line) continue;\n\n // Format: \" PID DAY MON DD HH:MM:SS YYYY\"\n // e.g., \" 78070 Wed Mar 18 23:18:01 2026\"\n const match = line.match(/^\\s*(\\d+)\\s+(.+)$/);\n if (!match) continue;\n\n const pid = parseInt(match[1], 10);\n const dateStr = match[2].trim();\n\n if (!Number.isFinite(pid)) continue;\n\n const date = new Date(dateStr);\n if (!Number.isNaN(date.getTime())) {\n result.set(pid, date);\n }\n }\n } catch {\n // Return whatever we have\n }\n\n return result;\n}\n\n/**\n * Enrich ProcessInfo array with cwd and startTime.\n *\n * Calls batchGetProcessCwds and batchGetProcessStartTimes in batched shell calls,\n * then populates each ProcessInfo in-place. Returns partial results —\n * if a PID fails, that process keeps empty cwd / undefined startTime.\n */\nexport function enrichProcesses(processes: ProcessInfo[]): ProcessInfo[] {\n if (processes.length === 0) return processes;\n\n const pids = processes.map((p) => p.pid);\n const cwdMap = batchGetProcessCwds(pids);\n const startTimeMap = batchGetProcessStartTimes(pids);\n\n for (const proc of processes) {\n proc.cwd = cwdMap.get(proc.pid) || \"\";\n proc.startTime = startTimeMap.get(proc.pid);\n }\n\n return processes;\n}\n\nfunction isSameTerminalProcess(proc: ProcessInfo, matched: ProcessInfo): boolean {\n const sameTty = proc.tty !== \"\" && proc.tty !== \"?\" && proc.tty === matched.tty;\n const sameCwd = proc.cwd !== \"\" && proc.cwd === matched.cwd;\n\n return sameTty && (sameCwd || proc.cwd === \"\" || matched.cwd === \"\");\n}\n\nfunction matchesProcessIdentity(proc: ProcessInfo, matched: ProcessInfo): boolean {\n return proc.pid === matched.pid || isSameTerminalProcess(proc, matched);\n}\n\nexport function findWrapperProcess(\n processes: ProcessInfo[],\n child: ProcessInfo,\n): ProcessInfo | undefined {\n return processes.find(\n (proc) =>\n proc.pid !== child.pid && child.ppid === proc.pid && matchesProcessIdentity(proc, child),\n );\n}\n\n/**\n * Find parent wrapper processes that should not be reported as separate agents.\n *\n * A process is considered a wrapper when it is the parent of another candidate\n * agent process in the same terminal/worktree, or when it points at the same\n * terminal/worktree as an already session-matched process.\n */\nexport function findWrapperProcessPids(\n processes: ProcessInfo[],\n matchedProcesses: ProcessInfo[] = [],\n): Set<number> {\n const wrappers = new Set<number>();\n\n for (const child of processes) {\n const wrapper = findWrapperProcess(processes, child);\n if (wrapper) {\n wrappers.add(wrapper.pid);\n }\n }\n\n for (const proc of processes) {\n if (\n matchedProcesses.some(\n (matched) => proc.pid !== matched.pid && isSameTerminalProcess(proc, matched),\n )\n ) {\n wrappers.add(proc.pid);\n }\n }\n\n return wrappers;\n}\n\n/**\n * Get the TTY device for a specific process\n */\nexport function getProcessTty(pid: number): string {\n try {\n const output = execFileSync(\"ps\", [\"-p\", String(pid), \"-o\", \"tty=\"], { encoding: \"utf-8\" });\n\n const tty = output.trim();\n return tty.startsWith(\"/dev/\") ? tty.slice(5) : tty;\n } catch {\n return \"?\";\n }\n}\n"],"names":["fs","path","execFile","execFileSync","PROCESS_EXEC_MAX_BUFFER","VALID_EXECUTABLE_NAME","ABSOLUTE_PATH","PATH_SEPARATOR","pathBasename","executable","basename","replace","toLowerCase","isFile","filePath","statSync","throwIfNoEntry","executablePath","command","parts","trim","split","first","length","test","prefix","i","executableBasename","normalizeExecutableName","name","lower","endsWith","slice","normalizedProcessNames","namePatterns","Set","filter","Boolean","map","matchesExecutableName","names","tokens","has","mayContinuePath","some","token","filterByProcessNames","processes","size","process","listAgentProcesses","namePattern","output","encoding","line","match","pid","parseInt","ppid","Number","isNaN","tty","ttyShort","startsWith","push","cwd","execFileText","file","args","Promise","resolve","reject","maxBuffer","error","stdout","parseProcessList","allPids","matched","add","getProcessCwdsAsync","pids","batchGetProcessCwdsAsync","exec","result","Map","join","currentPid","set","entries","all","String","entry","batchGetProcessStartTimesAsync","rawLine","date","Date","isFinite","getTime","processIdentity","createProcessSnapshotCapture","deps","startTimeCache","cachedStartTime","cached","get","identity","startTime","undefined","options","keys","delete","isCandidate","candidates","candidatePids","uncachedPids","cwdMap","startTimeMap","candidateSet","captureProcessSnapshot","batchGetProcessCwds","stdio","batchGetProcessStartTimes","dateStr","enrichProcesses","p","proc","isSameTerminalProcess","sameTty","sameCwd","matchesProcessIdentity","findWrapperProcess","child","find","findWrapperProcessPids","matchedProcesses","wrappers","wrapper","getProcessTty"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/AgentManager.ts
CHANGED
|
@@ -47,7 +47,7 @@ export interface AgentManagerOptions {
|
|
|
47
47
|
onRuntimeDiscoveryError?: (error: unknown) => void;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
class AgentNotRunningError extends Error {
|
|
51
51
|
constructor(public agentName: string) {
|
|
52
52
|
super(`Agent "${agentName}" is no longer running.`);
|
|
53
53
|
this.name = "AgentNotRunningError";
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import * as capacity from "../../capacity/index.js";
|
|
3
|
+
import * as agentManager from "../../index.js";
|
|
4
|
+
|
|
5
|
+
const checkedAt = "2026-09-30T12:00:00.000Z";
|
|
6
|
+
const freshAuth = JSON.stringify({
|
|
7
|
+
anthropic: {
|
|
8
|
+
type: "oauth",
|
|
9
|
+
access: "fake-pi-anthropic-token",
|
|
10
|
+
refresh: "fake-pi-anthropic-refresh",
|
|
11
|
+
expires: Date.parse("2026-10-01T12:00:00.000Z"),
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("Pi Anthropic capacity", () => {
|
|
16
|
+
it("exports a Pi Anthropic capacity report reader", () => {
|
|
17
|
+
expect(capacity).toHaveProperty("getAnthropicCapacityReport");
|
|
18
|
+
expect(agentManager).toHaveProperty("getAnthropicCapacityReport");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("reads Pi's Anthropic OAuth credential and reports the Pi harness", async () => {
|
|
22
|
+
const readFile = vi.fn(async () => freshAuth);
|
|
23
|
+
const fetch = vi.fn(
|
|
24
|
+
async () =>
|
|
25
|
+
new Response(JSON.stringify({ five_hour: { utilization: 20 } }), {
|
|
26
|
+
status: 200,
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const report = await capacity.getAnthropicCapacityReport({
|
|
31
|
+
now: () => new Date(checkedAt),
|
|
32
|
+
env: { HOME: "/users/test" },
|
|
33
|
+
readFile,
|
|
34
|
+
fetch,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(readFile).toHaveBeenCalledWith("/users/test/.pi/agent/auth.json", "utf8");
|
|
38
|
+
expect(fetch).toHaveBeenCalledWith(
|
|
39
|
+
"https://api.anthropic.com/api/oauth/usage",
|
|
40
|
+
expect.objectContaining({
|
|
41
|
+
headers: expect.objectContaining({
|
|
42
|
+
Authorization: "Bearer fake-pi-anthropic-token",
|
|
43
|
+
}),
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
expect(report).toMatchObject({
|
|
47
|
+
harness: "pi",
|
|
48
|
+
provider: "anthropic",
|
|
49
|
+
generatedAt: checkedAt,
|
|
50
|
+
authenticated: true,
|
|
51
|
+
available: "yes",
|
|
52
|
+
});
|
|
53
|
+
expect(JSON.stringify(report)).not.toContain("fake-pi-anthropic-token");
|
|
54
|
+
expect(JSON.stringify(report)).not.toContain("fake-pi-anthropic-refresh");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("does not fetch for an expired Pi login and returns a stale report", async () => {
|
|
58
|
+
const fetch = vi.fn();
|
|
59
|
+
const report = await capacity.getAnthropicCapacityReport({
|
|
60
|
+
now: () => new Date(checkedAt),
|
|
61
|
+
env: { HOME: "/users/test" },
|
|
62
|
+
readFile: async () =>
|
|
63
|
+
JSON.stringify({
|
|
64
|
+
anthropic: {
|
|
65
|
+
type: "oauth",
|
|
66
|
+
access: "fake-expired-token",
|
|
67
|
+
expires: 1,
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
fetch,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
74
|
+
expect(report).toEqual({
|
|
75
|
+
harness: "pi",
|
|
76
|
+
provider: "anthropic",
|
|
77
|
+
generatedAt: checkedAt,
|
|
78
|
+
authenticated: true,
|
|
79
|
+
available: "unknown",
|
|
80
|
+
windows: [],
|
|
81
|
+
creditsRemaining: null,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it.each([
|
|
86
|
+
[
|
|
87
|
+
"missing file",
|
|
88
|
+
async () => Promise.reject(Object.assign(new Error("missing"), { code: "ENOENT" })),
|
|
89
|
+
],
|
|
90
|
+
["malformed file", async () => "{"],
|
|
91
|
+
["missing credential", async () => "{}"],
|
|
92
|
+
["malformed credential", async () => JSON.stringify({ anthropic: { type: "oauth" } })],
|
|
93
|
+
])("rejects a %s without fetching", async (_name, readFile) => {
|
|
94
|
+
const fetch = vi.fn();
|
|
95
|
+
await expect(
|
|
96
|
+
capacity.getAnthropicCapacityReport({
|
|
97
|
+
now: () => new Date(checkedAt),
|
|
98
|
+
env: { HOME: "/users/test" },
|
|
99
|
+
readFile,
|
|
100
|
+
fetch,
|
|
101
|
+
}),
|
|
102
|
+
).rejects.toThrow(/Pi Anthropic/);
|
|
103
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { parseAnthropicCapacity } from "../../capacity/providers/anthropic.js";
|
|
3
|
+
import { resolvePiAnthropicCredential } from "../../harnesses/pi/credentials.js";
|
|
4
|
+
|
|
5
|
+
describe("capacity boundaries", () => {
|
|
6
|
+
it("keeps Pi credential extraction separate from Anthropic quota parsing", async () => {
|
|
7
|
+
const credential = await resolvePiAnthropicCredential({
|
|
8
|
+
env: { HOME: "/users/test" },
|
|
9
|
+
readFile: async () =>
|
|
10
|
+
JSON.stringify({
|
|
11
|
+
anthropic: {
|
|
12
|
+
type: "oauth",
|
|
13
|
+
access: "fake-boundary-token",
|
|
14
|
+
expires: Date.parse("2026-10-01T00:00:00.000Z"),
|
|
15
|
+
},
|
|
16
|
+
}),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const snapshot = parseAnthropicCapacity({ five_hour: { utilization: 25 } });
|
|
20
|
+
|
|
21
|
+
expect(credential).toEqual({
|
|
22
|
+
kind: "oauth",
|
|
23
|
+
access: "fake-boundary-token",
|
|
24
|
+
expiresMs: Date.parse("2026-10-01T00:00:00.000Z"),
|
|
25
|
+
});
|
|
26
|
+
expect(snapshot).toMatchObject({
|
|
27
|
+
authenticated: true,
|
|
28
|
+
available: "yes",
|
|
29
|
+
windows: [{ id: "session", usedPercent: 25 }],
|
|
30
|
+
creditsRemaining: null,
|
|
31
|
+
});
|
|
32
|
+
expect(snapshot).not.toHaveProperty("harness");
|
|
33
|
+
expect(snapshot).not.toHaveProperty("provider");
|
|
34
|
+
expect(snapshot).not.toHaveProperty("generatedAt");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -2,7 +2,7 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { describe, expect, it, vi } from "vitest";
|
|
4
4
|
import * as capacity from "../../capacity/index.js";
|
|
5
|
-
import {
|
|
5
|
+
import { parseAnthropicCapacity } from "../../capacity/providers/anthropic.js";
|
|
6
6
|
import * as agentManager from "../../index.js";
|
|
7
7
|
|
|
8
8
|
const fixture = (name: string) =>
|
|
@@ -226,10 +226,7 @@ describe("Claude capacity", () => {
|
|
|
226
226
|
});
|
|
227
227
|
|
|
228
228
|
it("preserves valid windows in a partial payload", async () => {
|
|
229
|
-
const report =
|
|
230
|
-
JSON.parse(await fixture("claude-usage-partial.json")),
|
|
231
|
-
"2026-09-25T18:00:00.000Z",
|
|
232
|
-
);
|
|
229
|
+
const report = parseAnthropicCapacity(JSON.parse(await fixture("claude-usage-partial.json")));
|
|
233
230
|
expect(report.available).toBe("yes");
|
|
234
231
|
expect(report.windows).toEqual([
|
|
235
232
|
{
|
|
@@ -251,9 +248,7 @@ describe("Claude capacity", () => {
|
|
|
251
248
|
|
|
252
249
|
it("rejects a non-object usage payload", async () => {
|
|
253
250
|
const raw = JSON.parse(await fixture("claude-usage-malformed.json"));
|
|
254
|
-
expect(() =>
|
|
255
|
-
"Claude usage response is malformed",
|
|
256
|
-
);
|
|
251
|
+
expect(() => parseAnthropicCapacity(raw)).toThrow("Claude usage response is malformed");
|
|
257
252
|
});
|
|
258
253
|
|
|
259
254
|
it.each([
|
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
CODEX_APP_SERVER_ARGS,
|
|
4
4
|
parseUsage,
|
|
5
5
|
probeCodexCapacity,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "../../capacity/
|
|
6
|
+
} from "../../capacity/sources/codex.js";
|
|
7
|
+
import { resolveCodexAuthPath } from "../../harnesses/codex/credentials.js";
|
|
8
|
+
import { toRateWindow } from "../../capacity/providers/openai-subscription.js";
|
|
9
9
|
|
|
10
10
|
const checkedAt = "2026-08-20T10:00:00.000Z";
|
|
11
11
|
const context = { installed: true, checkedAt };
|
|
@@ -52,9 +52,7 @@ describe("Codex auth resolution", () => {
|
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
it("falls back to ~/.codex/auth.json", () => {
|
|
55
|
-
expect(resolveCodexAuthPath({ HOME: "/users/test" })).toBe(
|
|
56
|
-
"/users/test/.codex/auth.json",
|
|
57
|
-
);
|
|
55
|
+
expect(resolveCodexAuthPath({ HOME: "/users/test" })).toBe("/users/test/.codex/auth.json");
|
|
58
56
|
});
|
|
59
57
|
});
|
|
60
58
|
|
|
@@ -108,9 +106,7 @@ describe("tiered Codex probing", () => {
|
|
|
108
106
|
status: 200,
|
|
109
107
|
}),
|
|
110
108
|
)
|
|
111
|
-
.mockResolvedValueOnce(
|
|
112
|
-
new Response(JSON.stringify(apiUsage()), { status: 200 }),
|
|
113
|
-
);
|
|
109
|
+
.mockResolvedValueOnce(new Response(JSON.stringify(apiUsage()), { status: 200 }));
|
|
114
110
|
const rpc = vi.fn();
|
|
115
111
|
const result = await probeCodexCapacity({
|
|
116
112
|
...context,
|
|
@@ -129,9 +125,7 @@ describe("tiered Codex probing", () => {
|
|
|
129
125
|
expect(fetch.mock.calls[0][0]).toBe(
|
|
130
126
|
"https://auth.openai.com/api/accounts/v1/user-auth-credential/whoami",
|
|
131
127
|
);
|
|
132
|
-
expect(fetch.mock.calls[1][0]).toBe(
|
|
133
|
-
"https://chatgpt.com/backend-api/wham/usage",
|
|
134
|
-
);
|
|
128
|
+
expect(fetch.mock.calls[1][0]).toBe("https://chatgpt.com/backend-api/wham/usage");
|
|
135
129
|
expect(fetch.mock.calls[1][1].headers).toMatchObject({
|
|
136
130
|
Authorization: "Bearer pat-secret",
|
|
137
131
|
"ChatGPT-Account-Id": "acct-1",
|
|
@@ -154,9 +148,7 @@ describe("tiered Codex probing", () => {
|
|
|
154
148
|
it("selects a fresh OAuth token without calling whoami", async () => {
|
|
155
149
|
const fetch = vi
|
|
156
150
|
.fn()
|
|
157
|
-
.mockResolvedValue(
|
|
158
|
-
new Response(JSON.stringify(apiUsage()), { status: 200 }),
|
|
159
|
-
);
|
|
151
|
+
.mockResolvedValue(new Response(JSON.stringify(apiUsage()), { status: 200 }));
|
|
160
152
|
const result = await probeCodexCapacity({
|
|
161
153
|
...context,
|
|
162
154
|
readFile: async () =>
|
|
@@ -210,9 +202,7 @@ describe("tiered Codex probing", () => {
|
|
|
210
202
|
])("falls back to the CLI for %s", async (name, readFile) => {
|
|
211
203
|
const fetch = vi
|
|
212
204
|
.fn()
|
|
213
|
-
.mockResolvedValue(
|
|
214
|
-
new Response("", { status: name === "OAuth 401" ? 401 : 200 }),
|
|
215
|
-
);
|
|
205
|
+
.mockResolvedValue(new Response("", { status: name === "OAuth 401" ? 401 : 200 }));
|
|
216
206
|
const rpc = vi.fn(async () => ({
|
|
217
207
|
rateLimits: {
|
|
218
208
|
rateLimits: {
|
|
@@ -239,8 +229,7 @@ describe("tiered Codex probing", () => {
|
|
|
239
229
|
}));
|
|
240
230
|
const result = await probeCodexCapacity({
|
|
241
231
|
...context,
|
|
242
|
-
readFile: async () =>
|
|
243
|
-
JSON.stringify({ personal_access_token: "pat-secret" }),
|
|
232
|
+
readFile: async () => JSON.stringify({ personal_access_token: "pat-secret" }),
|
|
244
233
|
fetch: vi.fn().mockRejectedValue(new Error("network failure pat-secret")),
|
|
245
234
|
rpc,
|
|
246
235
|
});
|
|
@@ -252,9 +241,7 @@ describe("tiered Codex probing", () => {
|
|
|
252
241
|
const fetch = vi
|
|
253
242
|
.fn()
|
|
254
243
|
.mockRejectedValueOnce(new Error("PAT failed"))
|
|
255
|
-
.mockResolvedValueOnce(
|
|
256
|
-
new Response(JSON.stringify(apiUsage()), { status: 200 }),
|
|
257
|
-
);
|
|
244
|
+
.mockResolvedValueOnce(new Response(JSON.stringify(apiUsage()), { status: 200 }));
|
|
258
245
|
const rpc = vi.fn();
|
|
259
246
|
const result = await probeCodexCapacity({
|
|
260
247
|
...context,
|
|
@@ -294,13 +281,7 @@ describe("tiered Codex probing", () => {
|
|
|
294
281
|
"account/read",
|
|
295
282
|
]);
|
|
296
283
|
expect(JSON.stringify(messages)).not.toMatch(/prompt|turn\/start/);
|
|
297
|
-
expect(CODEX_APP_SERVER_ARGS).toEqual([
|
|
298
|
-
"-s",
|
|
299
|
-
"read-only",
|
|
300
|
-
"-a",
|
|
301
|
-
"untrusted",
|
|
302
|
-
"app-server",
|
|
303
|
-
]);
|
|
284
|
+
expect(CODEX_APP_SERVER_ARGS).toEqual(["-s", "read-only", "-a", "untrusted", "app-server"]);
|
|
304
285
|
});
|
|
305
286
|
|
|
306
287
|
it("uses account/read to distinguish logged-out CLI state", async () => {
|
|
@@ -316,11 +297,7 @@ describe("tiered Codex probing", () => {
|
|
|
316
297
|
});
|
|
317
298
|
|
|
318
299
|
it("never exposes tokens or raw auth content through failures", async () => {
|
|
319
|
-
const secrets = [
|
|
320
|
-
"pat-secret-value",
|
|
321
|
-
"oauth-secret-value",
|
|
322
|
-
"refresh-secret-value",
|
|
323
|
-
];
|
|
300
|
+
const secrets = ["pat-secret-value", "oauth-secret-value", "refresh-secret-value"];
|
|
324
301
|
const result = await probeCodexCapacity({
|
|
325
302
|
...context,
|
|
326
303
|
readFile: async () =>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"openai-codex": {
|
|
3
3
|
"type": "oauth",
|
|
4
|
-
"access": "
|
|
4
|
+
"access": "sample access value for tests",
|
|
5
5
|
"refresh": "fixture-refresh-token",
|
|
6
6
|
"expires": 1793000000000,
|
|
7
7
|
"accountId": "fixture-chatgpt-account-id"
|
|
@@ -2,14 +2,17 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { probeOpenAiCapacity } from "../../capacity/sources/pi.js";
|
|
5
6
|
import {
|
|
6
|
-
|
|
7
|
+
buildOpenAiPlatformCapacity,
|
|
7
8
|
parseOpenAiUsage,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from "../../capacity/openai.js";
|
|
9
|
+
} from "../../capacity/providers/openai-platform.js";
|
|
10
|
+
import { resolvePiOpenAiCredential as resolveOpenAiCredential } from "../../harnesses/pi/credentials.js";
|
|
11
11
|
|
|
12
12
|
const checkedAt = "2026-09-26T10:00:00.000Z";
|
|
13
|
+
const fixtureOauthAccess = "sample access value for tests";
|
|
14
|
+
const fakeJwt = (payload: string) =>
|
|
15
|
+
["test", Buffer.from(payload).toString("base64url"), "test"].join(".");
|
|
13
16
|
const fixture = (name: string) =>
|
|
14
17
|
readFile(fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)), "utf8");
|
|
15
18
|
const fixtureString = (name: string) =>
|
|
@@ -59,7 +62,7 @@ describe("OpenAI tiered credential resolution", () => {
|
|
|
59
62
|
openai: { type: "api_key", key: "pi-openai-test-key" },
|
|
60
63
|
"openai-codex": {
|
|
61
64
|
type: "oauth",
|
|
62
|
-
access:
|
|
65
|
+
access: fixtureOauthAccess,
|
|
63
66
|
refresh: "fixture-refresh-token",
|
|
64
67
|
expires: 1793000000000,
|
|
65
68
|
accountId: "fixture-chatgpt-account-id",
|
|
@@ -83,7 +86,7 @@ describe("OpenAI tiered credential resolution", () => {
|
|
|
83
86
|
}),
|
|
84
87
|
).resolves.toEqual({
|
|
85
88
|
kind: "oauth",
|
|
86
|
-
access:
|
|
89
|
+
access: fixtureOauthAccess,
|
|
87
90
|
accountId: "fixture-chatgpt-account-id",
|
|
88
91
|
expiresMs: 1793000000000,
|
|
89
92
|
});
|
|
@@ -148,7 +151,7 @@ describe("OpenAI OAuth capacity probe", () => {
|
|
|
148
151
|
expect.objectContaining({
|
|
149
152
|
method: "GET",
|
|
150
153
|
headers: {
|
|
151
|
-
Authorization:
|
|
154
|
+
Authorization: `Bearer ${fixtureOauthAccess}`,
|
|
152
155
|
"ChatGPT-Account-Id": "fixture-chatgpt-account-id",
|
|
153
156
|
},
|
|
154
157
|
}),
|
|
@@ -217,7 +220,7 @@ describe("OpenAI OAuth capacity probe", () => {
|
|
|
217
220
|
const expiredJwtAuth = JSON.stringify({
|
|
218
221
|
"openai-codex": {
|
|
219
222
|
type: "oauth",
|
|
220
|
-
access:
|
|
223
|
+
access: fakeJwt(JSON.stringify({ exp: 1789000000 })),
|
|
221
224
|
accountId: "fixture-chatgpt-account-id",
|
|
222
225
|
},
|
|
223
226
|
});
|
|
@@ -235,7 +238,7 @@ describe("OpenAI OAuth capacity probe", () => {
|
|
|
235
238
|
const futureJwtAuth = JSON.stringify({
|
|
236
239
|
"openai-codex": {
|
|
237
240
|
type: "oauth",
|
|
238
|
-
access:
|
|
241
|
+
access: fakeJwt(JSON.stringify({ exp: 1793000000 })),
|
|
239
242
|
accountId: "fixture-chatgpt-account-id",
|
|
240
243
|
},
|
|
241
244
|
});
|
|
@@ -255,7 +258,7 @@ describe("OpenAI OAuth capacity probe", () => {
|
|
|
255
258
|
const auth = JSON.stringify({
|
|
256
259
|
"openai-codex": {
|
|
257
260
|
type: "oauth",
|
|
258
|
-
access: "
|
|
261
|
+
access: fakeJwt("not-json"),
|
|
259
262
|
accountId: "fixture-chatgpt-account-id",
|
|
260
263
|
},
|
|
261
264
|
});
|
|
@@ -391,11 +394,8 @@ describe("OpenAI usage mapping", () => {
|
|
|
391
394
|
...parseOpenAiUsage(JSON.parse(await fixture("openai-usage-completions.json"))),
|
|
392
395
|
...parseOpenAiUsage(JSON.parse(await fixture("openai-usage-responses.json"))),
|
|
393
396
|
];
|
|
394
|
-
const report =
|
|
397
|
+
const report = buildOpenAiPlatformCapacity(buckets, checkedAt);
|
|
395
398
|
expect(report).toMatchObject({
|
|
396
|
-
harness: "pi",
|
|
397
|
-
provider: "openai",
|
|
398
|
-
generatedAt: checkedAt,
|
|
399
399
|
authenticated: true,
|
|
400
400
|
available: "yes",
|
|
401
401
|
creditsRemaining: null,
|
|
@@ -427,7 +427,7 @@ describe("OpenAI usage mapping", () => {
|
|
|
427
427
|
});
|
|
428
428
|
|
|
429
429
|
it("reports zero usage for days without buckets", () => {
|
|
430
|
-
const report =
|
|
430
|
+
const report = buildOpenAiPlatformCapacity([], checkedAt);
|
|
431
431
|
expect(report.windows.map((window) => window.current)).toEqual([0, 0]);
|
|
432
432
|
expect(report.windows[0].resetsAt).toBe("2026-09-27T00:00:00.000Z");
|
|
433
433
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
|
-
import { parseWhamUsage, toRateWindow } from "../../capacity/
|
|
4
|
+
import { parseWhamUsage, toRateWindow } from "../../capacity/providers/openai-subscription.js";
|
|
5
5
|
|
|
6
6
|
const fixture = (name: string) =>
|
|
7
7
|
readFile(fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)), "utf8");
|