feat: three-prong session lifecycle fuse for opencode adapter#145
Merged
Conversation
Implement a first-signal-wins fuse pattern for opencode sessions: 1. Wrapper script captures exit code to a .exit file (primary signal) 2. PID death polling via kill(pid,0) with recycling protection (secondary) 3. Configurable master timeout (default 3h) emits session.timeout (tertiary) Each signal cancels the others via AbortController. Sessions launched via agentctl remain visible in list()/discover()/events() through the meta-dir even when opencode native storage lacks a session file. Also adds listSessionMeta() and updateSessionMeta() utilities. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement three-signal fuse pattern for detecting opencode session completion. Previously, sessions launched via agentctl were invisible because opencode's run mode doesn't write native session files. Three signals, first to fire cancels the others (AbortController): 1. Exit file: wrapper writes exit code to .exit file on completion 2. PID death: poll kill(pid,0) every 15s with PID recycling detection 3. Master timeout: configurable (default 3h), emits session.timeout Also fixes: - list() now enumerates from meta dir as primary source - bootstrapFusesFromMeta emits pending events for pre-resolved sessions - firedFuseIds prevents legacy poll from duplicating fuse events - session.timeout added as new lifecycle event type Closes #144
Remove lifecycle fuse tests from opencode.test.ts since they already exist in opencode-fuse.test.ts and opencode-launch.test.ts. Keeps opencode.test.ts focused on adapter core functionality. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The lifecycle fuse tests were already comprehensively covered in opencode-fuse.test.ts. The copies in opencode.test.ts had timing issues causing 5s timeouts in CI. Remove them and keep only the meta-dir list() tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8daea1c to
e35af53
Compare
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.
Closes #144
Problem
opencode run(non-interactive mode) doesn't write session files to opencode's native storage directory. This makes sessions launched viaagentctl launch --matrixinvisible toagentctl list,events(), and the supervisor.Discovered during autonomous implementation runs: agents did real work but died before pushing/opening PRs, and the supervisor was never notified because no
session.stoppedevents were emitted.Solution: Three-Prong Fuse
When agentctl launches an opencode session, three signals race. First to fire cancels the others via AbortController:
.exitfile → detected on next poll cycle (≤15s)kill(pid, 0)poll every 15s with PID recycling protection viastartTimesession.timeout(does NOT kill the process — supervisor decides)Changes
src/adapters/opencode.ts— Fuse registration, bootstrap from meta dir, three-prong check loop, dedup viafiredFuseIds, pending events for pre-resolved sessionssrc/core/types.ts— Addedsession.timeoutevent typesrc/utils/session-meta.ts— Extended meta with cwd, model, prompt, masterTimeoutMs fieldssrc/adapters/opencode.test.ts— Integration tests for all three signals + dedupsrc/adapters/opencode-fuse.test.ts— Unit tests for fuse cancellation, PID recycling, timeoutsrc/utils/session-meta.test.ts— Tests for extended session metaTest Plan
npm run lint✅npm run typecheck✅npm run build✅npm test— 571 passing ✅session.stoppedfires within 15s of process exit