Repository navigation
Providers phase 2a: typed AgentSession contract + neutral wire types - #397
Merged
Merged
Conversation
Replaces sessionManager's structural AgentSessionLike + createSession: unknown + cast with a real AgentSession interface that has a strictly-typed event map. Un-couples ManagerEvents from provider imports at the same time. Before this change: - MainProviderConfig.createSession returned `unknown`; the manager cast to a duck-type whose `on(event: string, listener: (...args: never[]) => void)` documented conformance in prose but enforced NOTHING. - ManagerEvents.screen was typed with ScreenSnapshot imported from @providers/claude/runtime/claudeSession (a provider!). - ManagerEvents.jsonl-entry was typed with JsonlEntry from the claude-code-headless PACKAGE. - A provider that dropped `screen`, misspelled `semantic-event`, or shipped a different payload for `jsonl-entry` compiled fine and failed silently at runtime as "the feature is just missing" (#394 §4's dominant silent-failure family). After: - AgentSession lives in @shared/types/session.ts with a typed event map (AgentSessionEvents) and a strictly-typed AgentSessionEmitter interface merge — the same pattern the headless packages already use for their own classes. - MainProviderConfig.createSession returns AgentSession, so the Record<AgentProviderKind, MainProviderConfig> registry now compile-checks conformance for every registered provider. Verified with a temp DriftedSession stub: TS2741 "Property 'resize' is missing in type 'DriftedSession' but required in type 'AgentSession'" fires exactly where it should. - ScreenSnapshot / JsonlEntry moved to neutral homes (AgentScreenSnapshot / AgentTranscriptEntry) in the shared types. Same runtime payloads — the shapes are what Claude AND Codex both emit today — just re-homed so the dependency arrow points OUT of main, not IN from a provider. jsonlCoalescer and preload/api/types follow suit; preload's JsonlEntry becomes an alias so renderer imports keep working. - The two `as AgentSessionLike & {...}` casts around awaitPastePlaceholder / awaitReadyForPrompt become direct property accesses — the optional methods are now typed on AgentSession itself. - Legacy per-condition Claude events (resume-prompt, permission-prompt, compaction-state) live on AgentLegacyClaudeConditionEvents composed into AgentSessionEvents. CodexSession's event map declares them too (no emit ever fires — zero runtime cost) so sessionManager's typed on/emit accepts every provider without a cast. Phase 3 removes this legacy surface entirely. What this DOESN'T do (and why): - resolveCondition's return type stays broadly `{reason: string}` at the AgentSession contract (a third provider needs freedom to define its own failure vocabulary); a single cast at the manager's claude-only call site keeps the concrete ResolveConditionResult surface for renderer/IPC consumers. Phase 3 generalizes. - The provider wrapper classes (ClaudeSession, CodexSession) are NOT eliminated. They still exist to translate the two divergent headless packages into one contract. Phase 6 (headless standardization) either extracts a shared @agent-code/headless-core package and lets the packages implement AgentSession directly, or ships a conformance suite that lets us delete the wrappers. Phase 2a is what makes phase 6 possible — the app now has a compile-checked target shape for the packages to converge toward. Verified: - Both TS projects clean: `tsc --noEmit -p tsconfig.node.json` and `-p tsconfig.web.json` (after node emit for composite refs). - `electron-vite build` clean (main + preload + renderer). - `vitest` 170/170 passing (same pre-existing voice-dictation submodule harness quirk as phase 1 — file this diff never touches). - Drift check: a DriftedSession without `resize` errors with TS2741 exactly where intended. Reverted before commit. Part of #394. Stacks on #395 (phase 1). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 6, 2026
Closed
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.
Re-filed #396 — the original was auto-closed when its stacked base branch (#395, now merged) was deleted; GitHub refuses to retarget closed PRs. Identical content, full description and verification evidence on #396. Part of #394.
TL;DR:
createSession: unknown+ structural cast → typedAgentSessioninterface with a strictly-typed event map in@shared/types/session.ts;ManagerEventsun-coupled from Claude-provider imports (ScreenSnapshot/JsonlEntry→ neutralAgentScreenSnapshot/AgentTranscriptEntry); contract drift now fails compilation inside the provider (verified with a drift stub → TS2741).Verified on this exact branch: tsc clean on both projects, electron-vite build clean, 170/170 tests, plus a merge simulation against main combined with #395.
🤖 Generated with Claude Code