thinkpool-pair 0.7.265 → 0.7.266
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude-session.mjs +5 -3
- package/codex-session.mjs +27 -12
- package/package.json +1 -1
package/claude-session.mjs
CHANGED
|
@@ -1051,6 +1051,11 @@ export function startClaudeSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
1051
1051
|
// session is ending) — not a real error. Stay silent; the re-create owns what's next.
|
|
1052
1052
|
if (myAc.signal.aborted) return
|
|
1053
1053
|
const msg = e?.message || String(e)
|
|
1054
|
+
// onEvent is also the bridge's synchronous busy-edge sampling point. The
|
|
1055
|
+
// query has already ended here, so publish the failure only after clearing
|
|
1056
|
+
// the turn. Otherwise a non-recoverable SDK error leaves the roster on its
|
|
1057
|
+
// last `busy: true` announce until a tab refocus asks for fresh state.
|
|
1058
|
+
turnActive = false
|
|
1054
1059
|
emit({ kind: 'error', message: msg })
|
|
1055
1060
|
// Auto-restart a transient upstream stream drop: re-run query() with
|
|
1056
1061
|
// resume so the conversation continues. Bounded by RESTART_MAX (reset on
|
|
@@ -1058,7 +1063,6 @@ export function startClaudeSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
1058
1063
|
// fresh input stream is needed because the throw killed the old iterator.
|
|
1059
1064
|
if (RECOVERABLE.test(msg) && restartCount < RESTART_MAX) {
|
|
1060
1065
|
restartCount++
|
|
1061
|
-
turnActive = false
|
|
1062
1066
|
emit({ kind: 'note', text: `connection dropped — reconnecting (${restartCount}/${RESTART_MAX})` })
|
|
1063
1067
|
// End the OLD input stream before swapping — else its generator leaks
|
|
1064
1068
|
// (suspended forever) and a turn pushed into it after the throw is silently
|
|
@@ -1076,7 +1080,6 @@ export function startClaudeSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
1076
1080
|
// of hard-failing. Bounded by RESTART_MAX. Loses only the resumed history, not the turn.
|
|
1077
1081
|
if (/no conversation found/i.test(msg) && sessionId && restartCount < RESTART_MAX) {
|
|
1078
1082
|
restartCount++
|
|
1079
|
-
turnActive = false
|
|
1080
1083
|
sessionId = null; persistedSessionId = null
|
|
1081
1084
|
const oldInput = input
|
|
1082
1085
|
input = makeInputStream()
|
|
@@ -1088,7 +1091,6 @@ export function startClaudeSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
1088
1091
|
// silently queuing into a dead stream. The bridge's onEvent self-heal
|
|
1089
1092
|
// still fires for "No conversation found" (it reopens fresh).
|
|
1090
1093
|
closed = true
|
|
1091
|
-
turnActive = false
|
|
1092
1094
|
} finally {
|
|
1093
1095
|
ac.signal.removeEventListener('abort', onParentAbort)
|
|
1094
1096
|
if (qAc === myAc) qAc = null
|
package/codex-session.mjs
CHANGED
|
@@ -393,6 +393,22 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
393
393
|
},
|
|
394
394
|
})
|
|
395
395
|
|
|
396
|
+
// The bridge samples `session.turnActive` synchronously inside onEvent to
|
|
397
|
+
// announce the roster's busy edge. Codex transports report their terminal
|
|
398
|
+
// boundary before their surrounding promise/process cleanup runs, so clearing
|
|
399
|
+
// turnActive only in `finally`/`close` publishes one last stale `busy: true`
|
|
400
|
+
// announce with no later event to correct it. A tab refocus re-announcement
|
|
401
|
+
// eventually healed the roster, but until then the room stayed on "thinking".
|
|
402
|
+
// Settle at the event boundary itself for every Codex transport.
|
|
403
|
+
const pushMappedEvent = (event) => {
|
|
404
|
+
if (event?.type === 'turn.completed' || event?.type === 'turn.failed') turnActive = false
|
|
405
|
+
mapper.push(event)
|
|
406
|
+
}
|
|
407
|
+
const emitTurnBoundary = (event) => {
|
|
408
|
+
turnActive = false
|
|
409
|
+
try { onEvent?.(event) } catch { /* noop */ }
|
|
410
|
+
}
|
|
411
|
+
|
|
396
412
|
const note = (text) => { try { onEvent?.({ kind: 'note', text }) } catch { /* noop */ } }
|
|
397
413
|
if (requestedResume && !resumeUsable) {
|
|
398
414
|
queueMicrotask(() => note('The stopped Codex turn had no resumable context; continuing in a fresh thread.'))
|
|
@@ -447,12 +463,12 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
447
463
|
return
|
|
448
464
|
}
|
|
449
465
|
}
|
|
450
|
-
|
|
466
|
+
pushMappedEvent({ type: method === 'item/started' ? 'item.started' : 'item.completed', item: appServerItemForMapper(raw) })
|
|
451
467
|
return
|
|
452
468
|
}
|
|
453
469
|
if (method === 'error') {
|
|
454
470
|
const message = params.error?.message || params.message || 'codex app-server error'
|
|
455
|
-
|
|
471
|
+
pushMappedEvent({ type: 'error', message })
|
|
456
472
|
}
|
|
457
473
|
}
|
|
458
474
|
|
|
@@ -507,7 +523,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
507
523
|
config: { 'features.default_mode_request_user_input': true },
|
|
508
524
|
})
|
|
509
525
|
appServerThreadReady = true
|
|
510
|
-
|
|
526
|
+
pushMappedEvent({ type: 'thread.started', thread_id: sessionId })
|
|
511
527
|
return true
|
|
512
528
|
} catch (error) {
|
|
513
529
|
// Fallback is safe only here: no turn/start request has been accepted, so
|
|
@@ -539,11 +555,11 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
539
555
|
const completed = await appServer.waitForTurn(activeTurnId)
|
|
540
556
|
const status = completed?.turn?.status
|
|
541
557
|
if (aborted || status === 'interrupted') {
|
|
542
|
-
|
|
558
|
+
emitTurnBoundary({ kind: 'result', subtype: 'aborted', sessionId, model: activeModel, costUsd: null, usage: null, numTurns: 1, durationMs: completed?.turn?.durationMs, denials: 0, resultText: null })
|
|
543
559
|
} else if (status === 'failed') {
|
|
544
|
-
|
|
560
|
+
pushMappedEvent({ type: 'turn.failed', message: completed?.turn?.error?.message || 'codex turn failed' })
|
|
545
561
|
} else {
|
|
546
|
-
|
|
562
|
+
pushMappedEvent({ type: 'turn.completed' })
|
|
547
563
|
}
|
|
548
564
|
} catch (error) {
|
|
549
565
|
// Once a started turn's transport fails we must not replay that prompt via
|
|
@@ -553,7 +569,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
553
569
|
appServerThreadReady = false
|
|
554
570
|
try { appServer?.end() } catch { /* noop */ }
|
|
555
571
|
appServer = null
|
|
556
|
-
if (!ended)
|
|
572
|
+
if (!ended) pushMappedEvent({ type: 'turn.failed', message: `codex app-server turn failed: ${error?.message || error}` })
|
|
557
573
|
} finally {
|
|
558
574
|
// A transport failure or interrupt can land after prose deltas but before
|
|
559
575
|
// item/completed. Preserve that partial prose as one durable row rather
|
|
@@ -614,7 +630,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
614
630
|
}
|
|
615
631
|
if (ev.type === 'thread.started' && ev.thread_id) sessionId = sessionId || ev.thread_id
|
|
616
632
|
if (ev.type === 'turn.completed' || ev.type === 'turn.failed') sawTerminalResult = true
|
|
617
|
-
|
|
633
|
+
pushMappedEvent(ev)
|
|
618
634
|
})
|
|
619
635
|
// stderr is codex's human progress channel; surface tail on failure only.
|
|
620
636
|
let stderrTail = ''
|
|
@@ -628,18 +644,17 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
628
644
|
child = null
|
|
629
645
|
turnActive = false
|
|
630
646
|
if (aborted && !sawTerminalResult) {
|
|
631
|
-
|
|
647
|
+
emitTurnBoundary({ kind: 'result', subtype: 'aborted', sessionId, model: activeModel, costUsd: null, usage: null, numTurns: 1, durationMs: undefined, denials: 0, resultText: null })
|
|
632
648
|
} else if (!aborted && !sawTerminalResult && code !== 0 && code != null) {
|
|
633
|
-
|
|
649
|
+
emitTurnBoundary({ kind: 'error', message: `codex exec exited ${code}${stderrTail ? ': ' + stderrTail.trim().slice(-300) : ''}`, recoverable: true })
|
|
634
650
|
}
|
|
635
651
|
resolve()
|
|
636
652
|
}
|
|
637
653
|
child.on('error', (e) => {
|
|
638
654
|
if (settled) return
|
|
639
655
|
settled = true
|
|
640
|
-
try { onEvent?.({ kind: 'error', message: `codex failed to start: ${e.message}`, recoverable: true }) } catch { /* noop */ }
|
|
641
656
|
child = null
|
|
642
|
-
|
|
657
|
+
emitTurnBoundary({ kind: 'error', message: `codex failed to start: ${e.message}`, recoverable: true })
|
|
643
658
|
resolve()
|
|
644
659
|
})
|
|
645
660
|
child.on('close', finish)
|