| phase | implementation |
|---|---|
| title | Generalize Process-to-Session Mapping — Implementation |
| description | Implementation notes for shared utilities and adapter refactoring |
packages/agent-manager/src/
├── adapters/
│ ├── AgentAdapter.ts # Interface + ProcessInfo (added startTime?)
│ ├── ClaudeCodeAdapter.ts # ~419 lines — session dir via path encoding
│ └── CodexAdapter.ts # ~319 lines — session dir via date dirs
├── utils/
│ ├── process.ts # Shell wrappers: ps aux, lsof, ps lstart, getProcessTty
│ ├── session.ts # Shell wrappers: stat for birthtimes
│ ├── matching.ts # 1:1 greedy matching + agent naming
│ └── index.ts # Re-exports
└── AgentManager.ts # Orchestrates adapters
utils/process.ts — All execSync calls for process data:
listAgentProcesses(namePattern): Uses[c]laudegrep trick to avoid matching grep itself. Post-filters bypath.basename(executable)for exact match. Input validated against/^[a-zA-Z0-9_-]+$/to prevent shell injection.batchGetProcessCwds(pids): Singlelsof -a -d cwd -Fn -p PID1,PID2,.... Falls back to per-PIDpwdxon Linux if lsof fails.batchGetProcessStartTimes(pids): Singleps -o pid=,lstart=. Parses full timestamp vianew Date(dateStr).enrichProcesses(processes): Convenience — calls both batch functions, populates in-place.
utils/session.ts — Session file discovery:
batchGetSessionFileBirthtimes(dirs): Combines all dir globs into singlestatcall. Uses|| trueto handle empty globs gracefully.
utils/matching.ts — Matching algorithm:
matchProcessesToSessions: Builds candidate pairs (CWD match + within 3min tolerance), sorts by delta ascending, greedy 1:1 assign.generateAgentName(cwd, pid): Returnsbasename(cwd) (pid)orunknown (pid).
ClaudeCodeAdapter:
- Session dir:
~/.claude/projects/<encoded>/where encoded =cwd.replace(/\//g, '-') discoverSessions: Encodes each unique process CWD, checks if dir exists, callsbatchGetSessionFileBirthtimes, setsresolvedCwdfrom dir-to-CWD mappingreadSession(filePath, projectPath): Parses all JSONL lines for timestamps, slug, cwd, entry type, interruption state, user message text- Status: Based on
lastEntryType(user/assistant/progress/thinking/system). No age-based override since process is confirmed running.
CodexAdapter:
- Session dir:
~/.codex/sessions/YYYY/MM/DD/ discoverSessions: Scans ±1 day window around each process start time. Reads each file once intocontentCache: Map<string, string>. SetsresolvedCwdfromsession_metafirst line.parseSession(cachedContent, filePath): Uses cached content when available, falls back to disk read. Extracts session ID, project path, summary, timestamps, last payload type.- Status: Based on
lastPayloadTypeand 5-minute idle threshold.
- Shell command utils return partial results — if lsof/ps fails for one PID, others still return
- Session file read failures are silently skipped (file may have been deleted between stat and read)
- Adapters fall back to process-only AgentInfo for unmatched processes
listAgentProcessesrejects patterns with shell metacharacters (returns[])
- 1
ps aux | grepper adapter (not per process) - 1
lsoffor all PIDs (not per PID) - 1
ps -o lstartfor all PIDs - 1
statper adapter across all session directories - JSONL files only read for matched sessions (CodexAdapter caches content from discovery phase)
- Legacy
listProcesses,getProcessCwd,getSessionFileBirthtimesremoved — no consumers getProcessTtykept — used byTerminalFocusManager
agent-manager package:
utils/file.ts— entire file (readLastLines,readJsonLines) — no production callersutils/process.ts—listProcesses,getProcessCwd,ListProcessesOptions— deprecated, no callersutils/session.ts—getSessionFileBirthtimes— unused wrapper, all callers use batch version
CLI package:
util/process.ts— entire file (listProcesses,getProcessCwd,getProcessTty,isProcessRunning,getProcessInfo) — zero production importsutil/file.ts— entire file (readLastLines,readJsonLines,fileExists,readJson) — zero production imports