(target, startOffset = 0)
| 220 | // Transcript Attachment & Tailing |
| 221 | // ============================================================ |
| 222 | function attachTranscript(target, startOffset = 0) { |
| 223 | state.transcriptPath = target.full; |
| 224 | state.currentSessionId = path.basename(state.transcriptPath, '.jsonl'); |
| 225 | setTurnState('idle', { sessionId: state.currentSessionId, reason: 'transcript_attached' }); |
| 226 | state.pendingInitialClearTranscript = target.ignoreInitialClearCommand |
| 227 | ? { sessionId: state.currentSessionId } |
| 228 | : null; |
| 229 | if (state.pendingSwitchTarget && state.pendingSwitchTarget.sessionId === state.currentSessionId) { |
| 230 | state.pendingSwitchTarget = null; |
| 231 | } |
| 232 | state.transcriptOffset = Math.max(0, startOffset); |
| 233 | state.tailRemainder = Buffer.alloc(0); |
| 234 | state.eventBuffer = []; |
| 235 | state.eventSeq = 0; |
| 236 | |
| 237 | if (state.expectingSwitch) { |
| 238 | state.expectingSwitch = false; |
| 239 | if (state.expectingSwitchTimer) { clearTimeout(state.expectingSwitchTimer); state.expectingSwitchTimer = null; } |
| 240 | } |
| 241 | if (state.switchWatcherDelayTimer) { clearTimeout(state.switchWatcherDelayTimer); state.switchWatcherDelayTimer = null; } |
| 242 | |
| 243 | try { |
| 244 | const stat = fs.statSync(state.transcriptPath); |
| 245 | state.tailCatchingUp = stat.size > state.transcriptOffset; |
| 246 | } catch { |
| 247 | state.tailCatchingUp = false; |
| 248 | } |
| 249 | |
| 250 | log(`Transcript attached: ${state.currentSessionId} (offset=${state.transcriptOffset} catchUp=${state.tailCatchingUp})`); |
| 251 | broadcast({ |
| 252 | type: 'transcript_ready', |
| 253 | transcript: state.transcriptPath, |
| 254 | sessionId: state.currentSessionId, |
| 255 | lastSeq: 0, |
| 256 | }); |
| 257 | startTailing(); |
| 258 | } |
| 259 | |
| 260 | function markExpectingSwitch() { |
| 261 | state.expectingSwitch = true; |
no test coverage detected