(key: string, _event: string, filename: string)
| 304 | } |
| 305 | |
| 306 | private async onEvent(key: string, _event: string, filename: string): Promise<void> { |
| 307 | const entry = this.entries.get(key); |
| 308 | if (!entry || entry.pending.length === 0) return; |
| 309 | if (!entry.fileRe.test(filename)) return; |
| 310 | |
| 311 | try { |
| 312 | await stat(join(entry.dir, filename)); |
| 313 | } catch { |
| 314 | return; |
| 315 | } |
| 316 | |
| 317 | for (const p of entry.pending) { |
| 318 | if (p.existingBefore.has(filename)) continue; |
| 319 | if (p.session.agentSessionId !== null) continue; |
| 320 | const sessionId = p.adapter.extractSessionId?.(filename); |
| 321 | if (!sessionId) return; |
| 322 | p.session.setAgentSessionId(sessionId); |
| 323 | this.logger.info('transcript.jsonl.detected', { |
| 324 | wsId: p.session.wsId, |
| 325 | recordId: p.session.recordId, |
| 326 | agent: p.adapter.id, |
| 327 | filename, |
| 328 | agentSessionId: sessionId, |
| 329 | }); |
| 330 | if (this.sessionRegistry) { |
| 331 | // Fire-and-forget — failed write just means we don't get the |
| 332 | // resumeHint persisted, which downgrades resume to `--continue` |
| 333 | // semantics next time. |
| 334 | void this.sessionRegistry |
| 335 | .update(p.session.wsId, p.session.recordId, { |
| 336 | resumeHint: { kind: 'agent-session-id', value: sessionId }, |
| 337 | }) |
| 338 | .catch((err) => { |
| 339 | this.logger.warn('transcript_watch.registry_update_failed', { |
| 340 | wsId: p.session.wsId, |
| 341 | id: p.session.recordId, |
| 342 | err, |
| 343 | }); |
| 344 | }); |
| 345 | } |
| 346 | this.unregister(p.session); |
| 347 | return; |
| 348 | } |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | async function snapshotFiles(dir: string, fileRe: RegExp): Promise<Set<string>> { |
no test coverage detected