Skip to content

Providers phase 2a: typed AgentSession contract + neutral wire types - #397

Merged
Juliusolsson05 merged 1 commit into
mainfrom
refactor/providers-phase2a-session-contract
Jul 6, 2026
Merged

Juliusolsson05 merged 1 commit into
mainfrom
refactor/providers-phase2a-session-contract

Conversation

@Juliusolsson05

Copy link
Copy Markdown
Owner

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 → typed AgentSession interface with a strictly-typed event map in @shared/types/session.ts; ManagerEvents un-coupled from Claude-provider imports (ScreenSnapshot/JsonlEntry → neutral AgentScreenSnapshot/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

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant