thinkpool-pair 0.7.223 → 0.7.225
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 +12 -4
- package/codex-session.mjs +25 -2
- package/package.json +1 -1
package/bridge.mjs
CHANGED
|
@@ -50,7 +50,7 @@ import { validateProviderSwitch, providerSwitchPlan, BUILTIN_PROVIDER } from './
|
|
|
50
50
|
import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk'
|
|
51
51
|
import { z } from 'zod'
|
|
52
52
|
import { startClaudeSession } from './claude-session.mjs'
|
|
53
|
-
import { startCodexSession, readCodexDefaultModel, readCodexModels, codexConfigForMode, defaultModeForRuntime } from './codex-session.mjs'
|
|
53
|
+
import { startCodexSession, readCodexDefaultModel, readCodexModels, codexConfigForMode, codexThreadCanResume, defaultModeForRuntime } from './codex-session.mjs'
|
|
54
54
|
import { canonicalRoomFilePath, waitForNativeImages } from './codex-images.mjs'
|
|
55
55
|
import { createManagedLaneWorktree, removeManagedLaneWorktree } from './lane-worktree.mjs'
|
|
56
56
|
import { commandOnPath } from './agent-detect.mjs'
|
|
@@ -103,7 +103,7 @@ const flowRedispatch = new Map()
|
|
|
103
103
|
// broadcasts; without persistent state the cap can never bite.
|
|
104
104
|
const flowBudgets = new Map()
|
|
105
105
|
import { formatPeek, PEEK, siblingsOf, resolveSibling, crossPostDecision, CROSSPOST, spawnDecision, SPAWN, CROSSROOM, formatPairRoster, crossRoomPostDecision, formatRoomNow, formatClosableHint, laneStatusOf } from './cross-terminal.mjs'
|
|
106
|
-
import { dispatchPendingRecap, recoverInterruptedTurn, recoverMissingResumeOnce,
|
|
106
|
+
import { dispatchPendingRecap, recoverInterruptedTurn, recoverMissingResumeOnce, sendInterruptedContinue, supersedeInterruptedResume } from './interrupted-resume.mjs'
|
|
107
107
|
import { turnInFlight } from './update-gate.mjs'
|
|
108
108
|
import { saveSession, flushSession, deleteSession, loadAll, canResume, loadPtyId, savePtyId, loadNames, saveNames, appendDurableEvents, seedDurableEvents, readDurablePage, readDurableOldestSeq, hasDurableArchive, servesHistoryPage } from './session-store.mjs'
|
|
109
109
|
import { stampEvent, makeSeqCounter, maxSeq, seqable, capReplayEvents, chunkReplayEvents, boundEventForBroadcast, inlineImageBlocks, usageReportLine, codexUsageReportLine, buildRecapFromLog, RECAP_CAP, trimmedBeforeSeq, firstSeq } from './event-id.mjs'
|
|
@@ -1671,8 +1671,11 @@ function openStructured({ id, runtime = 'claude', model, effort, resume, log, co
|
|
|
1671
1671
|
// `claude-fable-5` → 400 [1211][Unknown Model]. One value, one truth.
|
|
1672
1672
|
// undefined → omit the SDK option entirely and let the provider env/endpoint default.
|
|
1673
1673
|
const codexModels = runtime === 'codex' ? readCodexModels() : []
|
|
1674
|
+
const codexRequestedModel = runtime === 'codex' ? (model || readCodexDefaultModel() || undefined) : undefined
|
|
1674
1675
|
const laneModel = runtime === 'codex'
|
|
1675
|
-
?
|
|
1676
|
+
? (codexRequestedModel
|
|
1677
|
+
? resolveCodexModel(codexRequestedModel, codexModels)
|
|
1678
|
+
: flowLaneModelFor({ sliceType: 'feature', runtime: 'codex', catalog: codexModels }))
|
|
1676
1679
|
: effectiveLaneModel({ provider, model, configuredModel: resolveProviderEnv(provider)?.ANTHROPIC_MODEL })
|
|
1677
1680
|
// spawnedBy: set when this lane was Dispatched (spawn_terminal). Restored from the
|
|
1678
1681
|
// session store so the Ensemble flag survives a bridge restart (else a respin
|
|
@@ -1698,7 +1701,6 @@ function openStructured({ id, runtime = 'claude', model, effort, resume, log, co
|
|
|
1698
1701
|
flowReviewRound: Number.isInteger(flowReviewRound) && flowReviewRound >= 0 ? flowReviewRound : 0,
|
|
1699
1702
|
dispatchBaseSha: dispatchBaseSha || null,
|
|
1700
1703
|
revertTarget: revertTarget || null,
|
|
1701
|
-
cwd: cwd || null, managedWorktree: managedWorktree || null,
|
|
1702
1704
|
// Stable creation order — persisted so a bridge restart restores tabs in the SAME
|
|
1703
1705
|
// order (not readdir/filesystem order). Legacy recs (no openedAt) derive it from the
|
|
1704
1706
|
// first transcript event ts, so even the first post-fix restart is ordered right.
|
|
@@ -3627,7 +3629,13 @@ channel
|
|
|
3627
3629
|
all.sort((a, b) => okey(a) - okey(b))
|
|
3628
3630
|
if (all.length) for (const rec of all) {
|
|
3629
3631
|
const wasInterrupted = restoredTurnOpen(rec.log || [])
|
|
3632
|
+
// Codex can persist a thread id before its rollout receives the
|
|
3633
|
+
// first metadata row. A stop followed immediately by a restart
|
|
3634
|
+
// leaves that file at zero bytes; both App Server and exec reject
|
|
3635
|
+
// it. Treat only that provably-empty case as non-resumable so the
|
|
3636
|
+
// existing fresh-thread recap recovery handles it.
|
|
3630
3637
|
const resumable = canResume(rec)
|
|
3638
|
+
&& (rec.runtime !== 'codex' || codexThreadCanResume(rec.sessionId))
|
|
3631
3639
|
const recoveryRecap = wasInterrupted ? buildRecapFromLog(rec.log || [], RECAP_CAP) : ''
|
|
3632
3640
|
// FL-M6 — restore the flow context (id/role/cwd) so an in-flight flow survives a
|
|
3633
3641
|
// bridge restart: the conductor keeps its subagent-block + plan interception, and
|
package/codex-session.mjs
CHANGED
|
@@ -123,6 +123,22 @@ function codexRolloutPath(sessionId, { home, fsImpl }) {
|
|
|
123
123
|
} catch { return null }
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// A stopped App Server turn can publish its thread id before Codex has written
|
|
127
|
+
// even the session_meta row. If the bridge restarts in that window, both
|
|
128
|
+
// thread/resume and `codex exec resume` reject the zero-byte rollout. Missing
|
|
129
|
+
// files remain "unknown" (Codex may resolve them through another store), but a
|
|
130
|
+
// rollout we can prove is empty is never resumable.
|
|
131
|
+
export function codexThreadCanResume(sessionId, {
|
|
132
|
+
home = process.env.CODEX_HOME || path.join(os.homedir(), '.codex'),
|
|
133
|
+
fsImpl = fs,
|
|
134
|
+
} = {}) {
|
|
135
|
+
if (!sessionId) return false
|
|
136
|
+
const file = codexRolloutPath(sessionId, { home, fsImpl })
|
|
137
|
+
if (!file) return true
|
|
138
|
+
try { return Number(fsImpl.statSync(file).size) > 0 }
|
|
139
|
+
catch { return true }
|
|
140
|
+
}
|
|
141
|
+
|
|
126
142
|
// `codex exec --json` exposes lifetime thread totals on `turn.completed`. Those
|
|
127
143
|
// totals are useful for billing, but they are NOT context occupancy: an agentic
|
|
128
144
|
// turn can make hundreds of cached model calls and process many context windows.
|
|
@@ -365,8 +381,12 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
365
381
|
sandbox = normalizeCodexSandbox(sandbox || modeConfig.sandbox)
|
|
366
382
|
const publishAccess = codexPublishAccess({ cwd, env: { ...process.env, ...(env || {}) } })
|
|
367
383
|
const childEnv = publishAccess.env
|
|
368
|
-
|
|
369
|
-
|
|
384
|
+
const requestedResume = resume || null
|
|
385
|
+
const resumeUsable = !requestedResume || codexThreadCanResume(requestedResume, {
|
|
386
|
+
home: childEnv.CODEX_HOME || path.join(os.homedir(), '.codex'),
|
|
387
|
+
})
|
|
388
|
+
let sessionId = resumeUsable ? requestedResume : null
|
|
389
|
+
let turnNo = sessionId ? 1 : 0
|
|
370
390
|
let child = null
|
|
371
391
|
let aborted = false
|
|
372
392
|
let ended = false
|
|
@@ -398,6 +418,9 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
398
418
|
})
|
|
399
419
|
|
|
400
420
|
const note = (text) => { try { onEvent?.({ kind: 'note', text }) } catch { /* noop */ } }
|
|
421
|
+
if (requestedResume && !resumeUsable) {
|
|
422
|
+
queueMicrotask(() => note('The stopped Codex turn had no resumable context; continuing in a fresh thread.'))
|
|
423
|
+
}
|
|
401
424
|
if (!['0', 'false', 'off'].includes(String(childEnv.TP_CODEX_APP_SERVER || '').trim().toLowerCase()) && !appServerGateResult.enabled) {
|
|
402
425
|
queueMicrotask(() => note(`Codex App Server disabled: ${appServerGateResult.reason}`))
|
|
403
426
|
}
|