Add Hermes Agent and Codex CLI providers (multi-provider runtime support) - #383
Open
josephndex wants to merge 1 commit into
Open
Add Hermes Agent and Codex CLI providers (multi-provider runtime support)#383josephndex wants to merge 1 commit into
josephndex wants to merge 1 commit into
Conversation
Extends the single-provider AgentRuntime/HookEventHandler to route by providerId across N concurrently-registered providers (one HookEventHandler per provider, sharing one AgentStateStore), then adds two new providers on that seam: - Codex CLI: no hooks/plugin API exists (checked against codex-cli 0.144.1's full --help surface), but Codex does write a structured, append-only JSONL rollout per session at ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl (session_meta, response_item.function_call/function_call_output, event_msg.task_complete). codexTailer.ts polls that directory (byte-offset tracked per file, partial-line-safe), and re-emits parsed events through the existing POST /api/hooks/:providerId ingress -- reading Codex's own structured format, not scraping terminal output. - Hermes Agent: has three separate hook systems (checked against a live v0.20.0 install + its docs). Gateway hooks only fire for messaging-platform sessions, not CLI. Shell hooks cover CLI but only a small fixed event set. Plugin hooks (ctx.register_hook() under ~/.hermes/plugins/) are the one that covers CLI sessions with the full lifecycle catalog needed here (pre_tool_call/post_tool_call/on_session_start/on_session_end/ on_session_finalize/subagent_start/subagent_stop). installHooks() drops a small stdlib-only Python plugin there and enables it via the documented `hermes plugins enable` CLI -- no dependency added to Hermes's venv, no hand-edited config.yaml. Claude Code's own provider is untouched; all upstream tests still pass. Adds 42 new tests (codex.test.ts, hermes.test.ts, agentRuntime.multiProvider.test.ts) covering per-event normalization, malformed/duplicate events, simultaneous multi-provider sessions sharing one store, isolated tool state between concurrent agents, and dispose(). Known limitations documented inline: Codex's rollout format has no explicit session-end record, so a session is heuristically closed after an idle timeout; Hermes only loads plugins at process start, so a session already running before installation won't retroactively receive events.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds two new
HookProviderintegrations — Codex CLI and Hermes Agent — on top of a small runtime patch that letsAgentRuntimehost more than one provider at once (previously hardcoded to exactly one). All three providers now share oneAgentStateStore/office; agents from any of them render side by side, independently tracked.Both new integrations are built against each CLI's actual current capabilities, checked directly rather than assumed:
~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl—session_meta,response_item.function_call/function_call_output,event_msg.task_complete).codexTailer.tspolls that directory (byte-offset tracked per file, partial-line-safe) and re-emits parsed events through the existingPOST /api/hooks/:providerIdingress — reading Codex's own structured format, not scraping terminal output.ctx.register_hook()under~/.hermes/plugins/) carry the full documented lifecycle catalog this needed (pre_tool_call/post_tool_call/on_session_start/on_session_end/on_session_finalize/subagent_start/subagent_stop).installHooks()drops a small stdlib-only Python plugin there and enables it via the documentedhermes plugins enableCLI — no new dependency in Hermes's venv, no hand-editedconfig.yaml.The runtime patch
AgentRuntime/HookEventHandlerpreviously took oneprovider: HookProviderat construction. This PR changes the constructor toproviders: HookProvider[]and builds oneHookEventHandlerper provider (each still scoped to exactly one provider internally — no shared mutable "current provider" field, so concurrent events from different providers can't race). All handlers share the runtime'sstore/timer Maps/SessionRouterseam. The module-level file-watcher/team singletons (setHookProvider,setFileWatcherHookProvider,setTeamProvider) stay bound toproviders[0](Claude) only, since neither new provider relies on the JSONL-scanning fallback path — this keeps Claude's existing behavior byte-for-byte identical (see Test plan).Type of change
Test plan
npm run build && cd server && npx vitest run→ 419 passed (377 pre-existing, unmodified, confirming Claude support has zero regressions + 42 new).server/__tests__/codex.test.ts,server/__tests__/hermes.test.ts—normalizeHookEventper event,formatToolStatus, reading-vs-writing tool classification.server/__tests__/agentRuntime.multiProvider.test.ts— simultaneous sessions from different providers landing in one store tagged byproviderId, duplicate-event handling, malformed/wrong-typed events never throwing, isolated tool state between concurrent agents on different providers, unknown-providerIdevents dropped safely,dispose()tearing down every provider handler.codex execsession against a temp dir and confirmed the full parse → normalize → route pipeline fired end-to-end over the actual JSONL rollout Codex wrote (confirmed via server logs); confirmed the untracked-project-dir privacy gate (the same one Claude uses) correctly declined to visually adopt it, proving the gate applies uniformly across providers.npm run lint,npm run check-types,npm run asyncapi:generate(no drift),npm run e2e:inventory(no drift) all pass clean.E2E coverage
pixel-agents/e2e/tests/.What e2e tests cover this change? No Playwright e2e was added. The existing suite's e2e model (
e2e/README.md→ "Mocking model & rules") is built around mocking a realclaudebinary viamock-claude/VS Code Electron fixtures — extending that pattern to two more real CLIs (codex,hermes) that don't have an equivalent lightweight mock harness felt like a separate, larger effort better scoped as its own follow-up rather than folded into this PR. TheagentRuntime.multiProvider.test.tsVitest suite exercises the same runtime code path (handleHookEvent→HookEventHandler→AgentStateStore) that a Playwright e2e test would ultimately assert against, just without the browser/webview layer — happy to add real e2e coverage as a follow-up if maintainers would rather have it in this PR before merge.Known limitations (documented inline in the code)
HookRegistry.discover_and_load()), so a Hermes session already running before the plugin is installed cannot retroactively receive events — this is a Hermes limitation, not something this PR works around by restarting sessions.