be266da9e29f18c23b3d93068d817a0a6ef58906
Officer's hour-long idle timer was doing two unrelated jobs: collecting its own in-memory binding, which is its business, and terminating the agent, which is the sidecar's. It could not do the first without the second, because `unsub` was a closure reachable only through `kill`. So a browser that went away killed a live agent an hour later — including one the sidecar had deliberately protected. The sidecar already refuses to collect a session that is mid-turn or holding background tasks: `task:started` disarms its idle GC, and `armIdle` re-checks and re-arms rather than firing once. Officer had no view of any of that. A laptop running out of battery overnight took a `run_in_background` job with it for no reason. `detach` now sits beside `kill` on both streaming handles, and `_sidecarUnsub` — declared and called for a long time, never once assigned — is populated at all three sites. `releaseSession` unsubscribes and forgets the record without killing; the idle timer points at it. `deleteSession` is unchanged, so an explicit disconnect still ends the session. The third assignment site was not in the plan: `adoptOrphanedSession` sets `_claudeKill` but nothing else, so an adopted session that later idled out would have dropped its record while the listener stayed subscribed — a leak of one per adopt-then-leave. No double subscription: releasing unsubscribes first, so a returning browser either adopts with a fresh listener or starts a first turn with none behind it. Step 1 of docs/chat-session-lifetime.md. Step 2 (a list verb, so running sessions can be found after a restart) is still open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Description
No description provided
42 MiB
Languages
TypeScript
90.9%
Shell
4.7%
JavaScript
4.1%
CSS
0.2%
HTML
0.1%