thinkpool-pair 0.7.201 → 0.7.202
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/bridge.mjs +9 -4
- package/interrupted-resume.mjs +29 -0
- package/package.json +2 -1
package/bridge.mjs
CHANGED
|
@@ -98,6 +98,7 @@ const flowRedispatch = new Map()
|
|
|
98
98
|
// broadcasts; without persistent state the cap can never bite.
|
|
99
99
|
const flowBudgets = new Map()
|
|
100
100
|
import { formatPeek, PEEK, siblingsOf, resolveSibling, crossPostDecision, CROSSPOST, spawnDecision, SPAWN, CROSSROOM, formatPairRoster, crossRoomPostDecision, formatRoomNow, formatClosableHint } from './cross-terminal.mjs'
|
|
101
|
+
import { armInterruptedResume, sendInterruptedContinue, supersedeInterruptedResume } from './interrupted-resume.mjs'
|
|
101
102
|
import { turnInFlight } from './update-gate.mjs'
|
|
102
103
|
import { saveSession, flushSession, deleteSession, loadAll, canResume, loadPtyId, savePtyId, loadNames, saveNames, appendDurableEvents, seedDurableEvents, readDurablePage, readDurableOldestSeq, hasDurableArchive, servesHistoryPage } from './session-store.mjs'
|
|
103
104
|
import { stampEvent, makeSeqCounter, maxSeq, seqable, capReplayEvents, chunkReplayEvents, boundEventForBroadcast, inlineImageBlocks, usageReportLine, buildRecapFromLog, RECAP_CAP, trimmedBeforeSeq, firstSeq } from './event-id.mjs'
|
|
@@ -2271,9 +2272,7 @@ function openStructured({ id, runtime = 'claude', model, effort, resume, log, co
|
|
|
2271
2272
|
// resume gets its single "continue" HERE — not on a blind timer that raced the ~40s
|
|
2272
2273
|
// resume and dropped the turn into a dead input stream. Fires exactly once.
|
|
2273
2274
|
if (evt.kind === 'system' && entry.pendingAutoResume) {
|
|
2274
|
-
entry.
|
|
2275
|
-
try { entry.session?.sendTurn('continue') } catch { /* not resumable — stays idle, user can continue manually */ }
|
|
2276
|
-
process.stderr.write(`\n ◆ auto-resumed interrupted turn (${id.slice(0, 8)}) — sent continue.\n`)
|
|
2275
|
+
if (sendInterruptedContinue(entry)) process.stderr.write(`\n ◆ auto-resumed interrupted turn (${id.slice(0, 8)}) — sent continue.\n`)
|
|
2277
2276
|
}
|
|
2278
2277
|
// Context-carry (2026-07-08): a lane re-opened WITHOUT an SDK resume (provider switch,
|
|
2279
2278
|
// non-resumable restart) starts blank. Fire the text-layer recap as its first turn the
|
|
@@ -2851,6 +2850,11 @@ channel
|
|
|
2851
2850
|
s.hop = 0
|
|
2852
2851
|
s.roomHop = 0 // a human turn is room-hop 0 — clears any injected cross-room hop depth
|
|
2853
2852
|
const text = String(payload.text)
|
|
2853
|
+
// A human turn supersedes a restart's still-pending auto-continue. Without this,
|
|
2854
|
+
// their message starts the cold runtime; its init event then queues a second stale
|
|
2855
|
+
// `continue` behind the request. Codex normally resumes immediately at restore, while
|
|
2856
|
+
// Claude holds this flag only until its input stream is live.
|
|
2857
|
+
supersedeInterruptedResume(s)
|
|
2854
2858
|
// Echo the turn so BOTH readers (and late joiners, via the log) show who
|
|
2855
2859
|
// said what — UNLESS silent (an @pool fire, which renders its own line).
|
|
2856
2860
|
const echoYou = () => {
|
|
@@ -3203,7 +3207,8 @@ channel
|
|
|
3203
3207
|
// instant it's live. openStructured already closed the interrupted turn idle, so a
|
|
3204
3208
|
// continue that can't be delivered just leaves it idle (recoverable) — never a hang.
|
|
3205
3209
|
if (re && !rec.flowSessionId && canResume(rec) && restoredTurnOpen(rec.log || [])) {
|
|
3206
|
-
|
|
3210
|
+
const recovery = armInterruptedResume(re)
|
|
3211
|
+
if (recovery === 'sent') process.stderr.write(`\n ◆ auto-resumed interrupted Codex turn (${rec.id.slice(0, 8)}) — sent continue.\n`)
|
|
3207
3212
|
}
|
|
3208
3213
|
// Context-carry (2026-07-08): the SAME mid-turn case but the SDK context CANNOT
|
|
3209
3214
|
// resume (canResume false — a stale/expired session). A bare "continue" would land
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Runtime-specific bridge-restart recovery.
|
|
2
|
+
//
|
|
3
|
+
// Claude owns a long-lived input stream: its continue must wait for the init system
|
|
4
|
+
// event proving that stream is consumed. Codex is one process per turn and has no idle
|
|
5
|
+
// init event: sending the prompt IS what launches the resumed process.
|
|
6
|
+
|
|
7
|
+
export function sendInterruptedContinue(entry) {
|
|
8
|
+
if (!entry) return false
|
|
9
|
+
entry.pendingAutoResume = false
|
|
10
|
+
try { return entry.session?.sendTurn?.('continue') !== false }
|
|
11
|
+
catch { return false }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function armInterruptedResume(entry) {
|
|
15
|
+
if (!entry?.session) return 'unavailable'
|
|
16
|
+
if (entry.runtime === 'codex') return sendInterruptedContinue(entry) ? 'sent' : 'unavailable'
|
|
17
|
+
entry.pendingAutoResume = true
|
|
18
|
+
return 'armed'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// A real person speaking always wins the race. Otherwise a human turn that starts a
|
|
22
|
+
// cold Claude/Codex runtime can trigger its init event and enqueue a stale second
|
|
23
|
+
// `continue` behind the person's actual request.
|
|
24
|
+
export function supersedeInterruptedResume(entry) {
|
|
25
|
+
if (!entry?.pendingAutoResume) return false
|
|
26
|
+
entry.pendingAutoResume = false
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinkpool-pair",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.202",
|
|
4
4
|
"description": "Share a local coding-agent CLI (Claude Code, Codex, Gemini, Aider, …) into a ThinkPool Code room, live.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"transcript-sanitize.mjs",
|
|
28
28
|
"session-store.mjs",
|
|
29
29
|
"cross-terminal.mjs",
|
|
30
|
+
"interrupted-resume.mjs",
|
|
30
31
|
"flow-conductor.mjs",
|
|
31
32
|
"flow-worktree.mjs",
|
|
32
33
|
"flow-task-graph.mjs",
|