refactor: route all state mutations through process() via system signals (phases 0-2)#131
refactor: route all state mutations through process() via system signals (phases 0-2)#131
Conversation
…als (phases 0-2) Eliminates all remaining public mutator methods on SessionRuntime that bypassed process(). All state mutations now flow through the reducer or post-reducer hooks in handleSystemSignal(), centralizing the mutation boundary. **Phase 0 — Dead code removal** Delete storePendingPermission(), setBackendSessionId(), setMessageHistory() (zero production callers; reducer already handles these). **Phase 1 — Simple data patches via signals** - setAdapterName() → ADAPTER_NAME_SET signal (reducer patches data + emits PERSIST_NOW) - addConsumer()/removeConsumer() → CONSUMER_CONNECTED/DISCONNECTED signals (post-reducer hooks mutate consumerSockets/consumerRateLimiters) - enqueuePendingPassthrough() → PASSTHROUGH_ENQUEUED signal (post-reducer hook pushes to pendingPassthroughs) **Phase 2 — Backend connection state via signals** - attachBackendConnection() folded into BACKEND_CONNECTED payload (reducer patches adapterSupportsSlashPassthrough; hook sets backendSession/backendAbort/slashExecutor before effects run so drained pending messages can reach the live backend) - resetBackendConnectionState() folded into BACKEND_DISCONNECTED (reducer resets backendSessionId + adapterSupportsSlashPassthrough + clears pendingPermissions; hook nulls handles) - drainPendingMessages() moved into BACKEND_CONNECTED reducer effects (SEND_TO_BACKEND) - drainPendingPermissionIds() moved into BACKEND_DISCONNECTED reducer effects (BROADCAST_TO_PARTICIPANTS permission_cancelled) - seedSessionState() → SESSION_SEEDED signal + new RESOLVE_GIT_INFO effect type (gitTracker added to EffectExecutorDeps) **Post-review fixes** - closeBackendConnection() now dispatches BACKEND_DISCONNECTED instead of manually nulling handles, fixing a permission-cancellation gap during teardown - executeEffects() has a per-effect try-catch: a failing effect logs an error event and continues rather than aborting subsequent effects - refreshGitInfo() wrapped in try-catch matching resolveGitInfo() pattern, preventing deleted cwds from crashing the backend message loop - trySendRawToBackend() uses explicit capability check instead of broad catch - Consolidated reduceSystemSignal() into single exhaustive switch (removed lifecycleForSignal helper and mixed if-chain) - Extracted applyUserMessageReducer() to eliminate duplicated reducer-call-then-IO - Deleted dead dead code: mapSetModelEffects, mapUserMessageEffects (unreachable) - Updated comments: routing table function names, handleSystemSignal() JSDoc documenting three-phase execution, effect-executor/mapper module comments Net: 11 imperative methods removed from SessionRuntime public API. -95 lines overall (563 additions, 658 deletions).
Summary of ChangesHello @teng-lin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly refactors the core session management logic by enforcing a strict, signal-based approach for all state mutations. The change centralizes how session data is updated, moving away from direct imperative method calls to a more predictable and auditable system using reducers and system signals. This improves consistency, simplifies the Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This is an impressive and well-executed refactoring. Centralizing all state mutations through SessionRuntime.process() via system signals is a significant architectural improvement that will greatly enhance the maintainability and predictability of the session state management. The changes are consistently applied across the codebase, and the test suite has been thoroughly updated to reflect the new design. The addition of error handling in the effect executor and the more precise logic in trySendRawToBackend are also excellent robustness improvements. I have a suggestion to improve the type safety and clarity of the trySendRawToBackend method by using a type guard.
| if ( | ||
| !("sendRaw" in backendSession) || | ||
| typeof (backendSession as unknown as Record<string, unknown>).sendRaw !== "function" | ||
| ) { | ||
| return "unsupported"; | ||
| } | ||
| (backendSession as unknown as { sendRaw: (s: string) => void }).sendRaw(ndjson); | ||
| return "sent"; |
There was a problem hiding this comment.
To ensure type safety and align with best practices for verifying object capabilities, consider using a type guard to check if backendSession implements the sendRaw method. This approach is more robust than using as any and clearly defines the expected interface, as recommended by the rule for verifying component requirements. The typeof check for the function is a good runtime check, but it should be combined with a type guard to inform TypeScript about the object's shape. You would need to define a helper type guard function (e.g., hasSendRawMethod and its corresponding interface HasSendRaw) elsewhere in the module or file.
interface HasSendRaw { sendRaw: (data: string) => void; } function hasSendRawMethod(obj: any): obj is HasSendRaw { return typeof obj === 'object' && obj !== null && typeof obj.sendRaw === 'function'; } if (!hasSendRawMethod(backendSession)) { return "unsupported"; } backendSession.sendRaw(ndjson); return "sent";References
- This rule emphasizes using type guards to verify if an object implements a required interface or capability, promoting type safety over type assertions like
as any.
Summary
Eliminates all remaining public mutator methods on
SessionRuntimethat bypassedprocess(). All session state mutations now flow through the reducer or post-reducer hooks insidehandleSystemSignal(), centralizing the mutation boundary established in PR #130.storePendingPermission,setBackendSessionId,setMessageHistory) — reducer already handled thesesetAdapterName()→ADAPTER_NAME_SETsignal (reducer patchesadapterName+ emitsPERSIST_NOW)addConsumer()/removeConsumer()→CONSUMER_CONNECTED/CONSUMER_DISCONNECTEDsignals with post-reducer handle hooksenqueuePendingPassthrough()→PASSTHROUGH_ENQUEUEDsignal with post-reducer hookattachBackendConnection()folded intoBACKEND_CONNECTEDpayload — handle refs set before effects execute so drained pending messages reach the live backendresetBackendConnectionState()folded intoBACKEND_DISCONNECTED— reducer resets data fields, hook nulls handlesdrainPendingMessages()→SEND_TO_BACKENDeffects inBACKEND_CONNECTED;drainPendingPermissionIds()→BROADCAST_TO_PARTICIPANTScancel effects inBACKEND_DISCONNECTEDseedSessionState()→SESSION_SEEDEDsignal + newRESOLVE_GIT_INFOeffect type (gitTracker added toEffectExecutorDeps)Post-review fixes applied:
closeBackendConnection()now dispatchesBACKEND_DISCONNECTED(fixes permission-cancellation gap during session teardown)executeEffects()has a per-effect try-catch so a failing effect logs an error and continues rather than aborting subsequent effectsrefreshGitInfo()wrapped in try-catch matchingresolveGitInfo()— prevents deleted cwds from crashing the backend message looptrySendRawToBackend()uses explicit capability check instead of broad catch that misclassified errorsreduceSystemSignal()into single exhaustive switch; removedlifecycleForSignalhelperapplyUserMessageReducer()to eliminate duplicated reducer-call-then-IO patternmapSetModelEffectsandmapUserMessageEffects(unreachable code)handleSystemSignal(), updated module commentsNet result: 11 imperative methods removed from
SessionRuntimepublic API. -95 lines (563 additions, 711 deletions).Test Plan
pnpm typecheck— cleanpnpm test— all 2900 tests passRESOLVE_GIT_INFOeffect,BACKEND_CONNECTEDordering guarantee (handle set beforeSEND_TO_BACKENDeffects),BACKEND_DISCONNECTEDlifecycle transition +adapterSupportsSlashPassthroughreset,SESSION_SEEDEDempty-params edge casegrep -rn "\.setAdapterName\|\.addConsumer\|\.removeConsumer\|\.attachBackendConnection\|\.resetBackendConnectionState\|\.drainPendingMessages\|\.drainPendingPermissionIds\|\.storePendingPermission\|\.setBackendSessionId\|\.setMessageHistory\|\.seedSessionState\|\.enqueuePendingPassthrough" src/ --include="*.ts" | grep -v "test\|\.d\.ts"