thinkpool-pair 0.7.357 → 0.7.358
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 +106 -54
- package/codex-session.mjs +25 -5
- package/package.json +1 -1
package/bridge.mjs
CHANGED
|
@@ -1755,6 +1755,11 @@ const designArtifacts = new Map() // previewId → trusted source record
|
|
|
1755
1755
|
const designQueues = new Map() // producer term → validated requests
|
|
1756
1756
|
const designActive = new Map() // producer term → request awaiting result + proof
|
|
1757
1757
|
const designRestore = new Map() // current previewId → immediately previous verified record/edit
|
|
1758
|
+
// Realtime can carry the settled DOM for ordinary authored mockups. Including
|
|
1759
|
+
// that already-uploaded snapshot in the correlated verified-live event lets an
|
|
1760
|
+
// open Design lane advance revisions without a second auth/sign/download round
|
|
1761
|
+
// trip. Large artifacts keep the normal signed-storage path.
|
|
1762
|
+
const MAX_INLINE_DESIGN_REVISION_BYTES = 128 * 1024
|
|
1758
1763
|
const designStatus = (payload) => designChannel.send({
|
|
1759
1764
|
type: 'broadcast', event: 'design-status', payload: { ts: Date.now(), ...payload },
|
|
1760
1765
|
})
|
|
@@ -1777,7 +1782,12 @@ const finishDesign = (term, state, extra = {}) => {
|
|
|
1777
1782
|
const maybeVerifyDesign = (term) => {
|
|
1778
1783
|
const active = designActive.get(term)
|
|
1779
1784
|
if (!active?.resultOk || !active.proof) return
|
|
1780
|
-
finishDesign(term, 'verified-live', {
|
|
1785
|
+
finishDesign(term, 'verified-live', {
|
|
1786
|
+
message: 'Verified at desktop and mobile.',
|
|
1787
|
+
artifact: active.proof.artifact,
|
|
1788
|
+
...(active.proof.artifactHtml ? { artifactHtml: active.proof.artifactHtml } : {}),
|
|
1789
|
+
canRestore: active.record.sourceKind !== 'preview',
|
|
1790
|
+
})
|
|
1781
1791
|
}
|
|
1782
1792
|
|
|
1783
1793
|
function pumpDesign(term) {
|
|
@@ -1949,7 +1959,10 @@ const handleManifest = async ({ box, term, trustedDesignSource = false, slug, m
|
|
|
1949
1959
|
: designRecord.sourcePath === active?.record?.sourcePath
|
|
1950
1960
|
if (correlated && dual && sameTarget && revisionProvesChange) {
|
|
1951
1961
|
if (active.record.sourceKind !== 'preview') designRestore.set(designRecord.previewId, { priorRecord: active.record, request: active.request })
|
|
1952
|
-
|
|
1962
|
+
const artifactHtml = Buffer.byteLength(displaySource, 'utf8') <= MAX_INLINE_DESIGN_REVISION_BYTES
|
|
1963
|
+
? displaySource
|
|
1964
|
+
: null
|
|
1965
|
+
active.proof = { artifact, artifactHtml }
|
|
1953
1966
|
maybeVerifyDesign(term)
|
|
1954
1967
|
}
|
|
1955
1968
|
}
|
|
@@ -2238,6 +2251,80 @@ function restoredTurnOpen(log) {
|
|
|
2238
2251
|
return false
|
|
2239
2252
|
}
|
|
2240
2253
|
|
|
2254
|
+
function emitCodexCompactionControl(entry, text, by = null) {
|
|
2255
|
+
const evt = { kind: 'control', text, by }
|
|
2256
|
+
pushLog(entry, evt)
|
|
2257
|
+
bcast('code-event', { term: entry.id, evt })
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
async function runCodexCompaction(entry, request = {}) {
|
|
2261
|
+
const recap = buildRecapFromLog(entry.log, RECAP_CAP, { reason: 'compact' })
|
|
2262
|
+
if (!recap) {
|
|
2263
|
+
emitCodexCompactionControl(entry, 'nothing to compact — context unchanged', request.by)
|
|
2264
|
+
return
|
|
2265
|
+
}
|
|
2266
|
+
const preTokens = entry.lastUsage?.ctx?.used || null
|
|
2267
|
+
bcast('code-event', { term: entry.id, evt: { kind: 'compact', status: 'start', ts: Date.now() } })
|
|
2268
|
+
let nativeCompacted = false
|
|
2269
|
+
try {
|
|
2270
|
+
nativeCompacted = await entry.session.compactContext?.()
|
|
2271
|
+
} catch {
|
|
2272
|
+
// compactContext classifies delivery certainty. A truly unexpected throw
|
|
2273
|
+
// remains non-replayable here; resetting after an unknown native request can
|
|
2274
|
+
// execute compaction twice against two different threads.
|
|
2275
|
+
nativeCompacted = null
|
|
2276
|
+
} finally {
|
|
2277
|
+
// Native compaction emits usage while compactActive=true, so onEvent
|
|
2278
|
+
// legitimately announces a busy edge. It has no ordinary result event;
|
|
2279
|
+
// close that borrowed edge after compactContext clears compactActive.
|
|
2280
|
+
if (!entry.session.turnActive && settleLaneControl(entry)) announce()
|
|
2281
|
+
}
|
|
2282
|
+
if (nativeCompacted === true) {
|
|
2283
|
+
const evt = { kind: 'compaction', trigger: 'manual', preTokens, by: request.by, native: true }
|
|
2284
|
+
pushLog(entry, evt)
|
|
2285
|
+
bcast('code-event', { term: entry.id, evt })
|
|
2286
|
+
bcast('code-event', { term: entry.id, evt: { kind: 'compact', status: 'done', ts: Date.now() } })
|
|
2287
|
+
entry.flush?.()
|
|
2288
|
+
return
|
|
2289
|
+
}
|
|
2290
|
+
if (nativeCompacted == null) {
|
|
2291
|
+
bcast('code-event', { term: entry.id, evt: { kind: 'compact', status: 'done', ts: Date.now() } })
|
|
2292
|
+
emitCodexCompactionControl(entry, 'Native compaction is still settling; existing context was preserved.', request.by)
|
|
2293
|
+
entry.flush?.()
|
|
2294
|
+
return
|
|
2295
|
+
}
|
|
2296
|
+
if (entry.session.clearContext?.() === false) {
|
|
2297
|
+
bcast('code-event', { term: entry.id, evt: { kind: 'compact', status: 'done', ts: Date.now() } })
|
|
2298
|
+
emitCodexCompactionControl(entry, 'Codex context compaction unavailable right now', request.by)
|
|
2299
|
+
return
|
|
2300
|
+
}
|
|
2301
|
+
entry.pendingRecap = recap
|
|
2302
|
+
const evt = { kind: 'compaction', trigger: 'manual', preTokens, by: request.by }
|
|
2303
|
+
pushLog(entry, evt)
|
|
2304
|
+
bcast('code-event', { term: entry.id, evt })
|
|
2305
|
+
// The new native thread has only the bounded recap queued for the next human
|
|
2306
|
+
// turn. Reset the visible meter to that conservative text estimate.
|
|
2307
|
+
if (entry.lastUsage?.ctx?.max) {
|
|
2308
|
+
const used = Math.ceil(recap.length / 4)
|
|
2309
|
+
const max = entry.lastUsage.ctx.max
|
|
2310
|
+
const usage = { kind: 'usage', ctx: { ...entry.lastUsage.ctx, used, max, pct: Math.min(100, Math.round((used / max) * 100)), over: used > max } }
|
|
2311
|
+
entry.lastUsage = usage
|
|
2312
|
+
bcast('code-event', { term: entry.id, evt: usage })
|
|
2313
|
+
}
|
|
2314
|
+
bcast('code-event', { term: entry.id, evt: { kind: 'compact', status: 'done', ts: Date.now() } })
|
|
2315
|
+
entry.flush?.()
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
function startCodexCompaction(entry, request = {}) {
|
|
2319
|
+
if (!entry || entry.runtime !== 'codex' || entry.codexCompactionPromise) return false
|
|
2320
|
+
const job = runCodexCompaction(entry, request)
|
|
2321
|
+
entry.codexCompactionPromise = job
|
|
2322
|
+
void job.finally(() => {
|
|
2323
|
+
if (entry.codexCompactionPromise === job) entry.codexCompactionPromise = null
|
|
2324
|
+
}).catch(() => { /* runCodexCompaction already surfaced classified failures */ })
|
|
2325
|
+
return true
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2241
2328
|
// Active-worktree snapshot for the per-turn ROOM NOW tail. Cached ~60s per cwd —
|
|
2242
2329
|
// a subprocess on every turn of every lane would be waste, and worktrees change on
|
|
2243
2330
|
// minutes-scale. Plain `git worktree list` output (path · sha · [branch]) is the
|
|
@@ -3656,6 +3743,15 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
3656
3743
|
pushLog(entry, ce)
|
|
3657
3744
|
bcast('code-event', { term: id, evt: ce })
|
|
3658
3745
|
}
|
|
3746
|
+
// A busy Codex lane accepts one deferred /compact intent. Claim the
|
|
3747
|
+
// compaction synchronously at the terminal result boundary so a new human
|
|
3748
|
+
// turn cannot slip into the gap, and coalesce every repeated tap into this
|
|
3749
|
+
// one job.
|
|
3750
|
+
if ((evt.kind === 'result' || evt.kind === 'error') && entry.runtime === 'codex' && entry.pendingCodexCompaction) {
|
|
3751
|
+
const request = entry.pendingCodexCompaction
|
|
3752
|
+
entry.pendingCodexCompaction = null
|
|
3753
|
+
startCodexCompaction(entry, request)
|
|
3754
|
+
}
|
|
3659
3755
|
// ── Slice 3: the turn settled. Two things happen, in this order.
|
|
3660
3756
|
// 1. Every outstanding permission card on this lane is now dead (the SDK
|
|
3661
3757
|
// won't ask again for a turn that ended) — clearAll retracts any
|
|
@@ -4678,6 +4774,7 @@ channel
|
|
|
4678
4774
|
}
|
|
4679
4775
|
if (/^\/clear\s*$/.test(text)) {
|
|
4680
4776
|
s.pendingRecap = null // /clear means forget context — drop any un-fired carry recap
|
|
4777
|
+
s.pendingCodexCompaction = null // explicit clear supersedes a deferred compact
|
|
4681
4778
|
if (s.runtime === 'codex' || s.runtime === 'hermes') s.session.clearContext?.()
|
|
4682
4779
|
else s.session.sendTurn(text)
|
|
4683
4780
|
// The seq reset is load-bearing on BOTH ends: the client's clear handler sets
|
|
@@ -4698,61 +4795,16 @@ channel
|
|
|
4698
4795
|
// No persisted ctl line — the live indicator + the CompactionCard are the record.
|
|
4699
4796
|
if (/^\/compact\s*$/.test(text)) {
|
|
4700
4797
|
if (s.runtime === 'codex') {
|
|
4798
|
+
// One lifecycle owns every distinct press. During an ordinary turn the
|
|
4799
|
+
// first request is queued and truthfully runs at its result boundary;
|
|
4800
|
+
// later taps, including taps during native compaction, coalesce silently.
|
|
4801
|
+
if (s.codexCompactionPromise || s.pendingCodexCompaction) return
|
|
4701
4802
|
if (s.session.turnActive) {
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
}
|
|
4705
|
-
const recap = buildRecapFromLog(s.log, RECAP_CAP, { reason: 'compact' })
|
|
4706
|
-
if (!recap) {
|
|
4707
|
-
ctlLine('nothing to compact — context unchanged')
|
|
4708
|
-
return
|
|
4709
|
-
}
|
|
4710
|
-
const preTokens = s.lastUsage?.ctx?.used || null
|
|
4711
|
-
bcast('code-event', { term: payload.term, evt: { kind: 'compact', status: 'start', ts: Date.now() } })
|
|
4712
|
-
let nativeCompacted = false
|
|
4713
|
-
try {
|
|
4714
|
-
nativeCompacted = await s.session.compactContext?.()
|
|
4715
|
-
} catch {
|
|
4716
|
-
// The bounded recap below is the safe fallback for a native transport
|
|
4717
|
-
// failure. The roster edge is still settled in finally.
|
|
4718
|
-
} finally {
|
|
4719
|
-
// Native compaction emits usage while compactActive=true, so onEvent
|
|
4720
|
-
// legitimately announces a busy edge. It has no ordinary result event;
|
|
4721
|
-
// close that borrowed edge here after compactContext's finally clears
|
|
4722
|
-
// compactActive, or "Compacting…" degrades into stale "Thinking…".
|
|
4723
|
-
// A browser turn may have arrived while compactContext awaited App
|
|
4724
|
-
// Server readiness. Never let control cleanup settle that ordinary
|
|
4725
|
-
// turn's busy edge; the normal result boundary owns it.
|
|
4726
|
-
if (!s.session.turnActive && settleLaneControl(s)) announce()
|
|
4727
|
-
}
|
|
4728
|
-
if (nativeCompacted) {
|
|
4729
|
-
const ce = { kind: 'compaction', trigger: 'manual', preTokens, by: payload.by, native: true }
|
|
4730
|
-
pushLog(s, ce)
|
|
4731
|
-
bcast('code-event', { term: payload.term, evt: ce })
|
|
4732
|
-
bcast('code-event', { term: payload.term, evt: { kind: 'compact', status: 'done', ts: Date.now() } })
|
|
4733
|
-
s.flush?.()
|
|
4803
|
+
s.pendingCodexCompaction = { by: payload.by, cid: payload.cid }
|
|
4804
|
+
ctlLine('Compaction queued for after this turn.', { tone: 'caution' })
|
|
4734
4805
|
return
|
|
4735
4806
|
}
|
|
4736
|
-
|
|
4737
|
-
bcast('code-event', { term: payload.term, evt: { kind: 'compact', status: 'done', ts: Date.now() } })
|
|
4738
|
-
ctlLine('Codex context compaction unavailable right now')
|
|
4739
|
-
return
|
|
4740
|
-
}
|
|
4741
|
-
s.pendingRecap = recap
|
|
4742
|
-
const ce = { kind: 'compaction', trigger: 'manual', preTokens, by: payload.by }
|
|
4743
|
-
pushLog(s, ce)
|
|
4744
|
-
bcast('code-event', { term: payload.term, evt: ce })
|
|
4745
|
-
// The new native thread has only the bounded recap queued for the next
|
|
4746
|
-
// human turn. Reset the visible meter to that conservative text estimate.
|
|
4747
|
-
if (s.lastUsage?.ctx?.max) {
|
|
4748
|
-
const used = Math.ceil(recap.length / 4)
|
|
4749
|
-
const max = s.lastUsage.ctx.max
|
|
4750
|
-
const usage = { kind: 'usage', ctx: { ...s.lastUsage.ctx, used, max, pct: Math.min(100, Math.round((used / max) * 100)), over: used > max } }
|
|
4751
|
-
s.lastUsage = usage
|
|
4752
|
-
bcast('code-event', { term: payload.term, evt: usage })
|
|
4753
|
-
}
|
|
4754
|
-
bcast('code-event', { term: payload.term, evt: { kind: 'compact', status: 'done', ts: Date.now() } })
|
|
4755
|
-
s.flush?.()
|
|
4807
|
+
startCodexCompaction(s, { by: payload.by, cid: payload.cid })
|
|
4756
4808
|
return
|
|
4757
4809
|
}
|
|
4758
4810
|
s.compacting = true
|
package/codex-session.mjs
CHANGED
|
@@ -1153,7 +1153,11 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
1153
1153
|
// synchronously after sendTurn(), so this is the edge that makes Thinking,
|
|
1154
1154
|
// Stop, and the tab spinner appear at dispatch time rather than at the
|
|
1155
1155
|
// first provider event many seconds later.
|
|
1156
|
-
|
|
1156
|
+
// Native compaction already owns that public busy edge. A human turn
|
|
1157
|
+
// accepted during it stays queued without flipping the ordinary-turn
|
|
1158
|
+
// latch; otherwise compactContext's post-bootstrap recheck mistakes the
|
|
1159
|
+
// queued turn for an active one and abandons a compaction it already owns.
|
|
1160
|
+
if (!turnActive && !compactActive) {
|
|
1157
1161
|
aborted = false
|
|
1158
1162
|
armTurnLiveness()
|
|
1159
1163
|
}
|
|
@@ -1229,13 +1233,20 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
1229
1233
|
},
|
|
1230
1234
|
async compactContext() {
|
|
1231
1235
|
if (turnActive || compactActive) return false
|
|
1232
|
-
|
|
1233
|
-
//
|
|
1234
|
-
//
|
|
1235
|
-
|
|
1236
|
+
// A Stop latch belongs to the turn it cancelled. sendTurn clears it when
|
|
1237
|
+
// accepting new work, but /compact enters through this direct control path.
|
|
1238
|
+
// Leaving it set makes appServerNotification discard the real
|
|
1239
|
+
// turn/started event, then the five-second waiter falsely declares failure
|
|
1240
|
+
// while Codex continues compacting the old thread in the background.
|
|
1241
|
+
aborted = false
|
|
1236
1242
|
compactActive = true
|
|
1237
1243
|
let startTimer = null
|
|
1244
|
+
let compactAccepted = false
|
|
1238
1245
|
try {
|
|
1246
|
+
const readyAppServer = await ensureAppServer()
|
|
1247
|
+
// compactActive claims the lane before bootstrap, so sendTurn queues
|
|
1248
|
+
// behind us. An independently active ordinary turn still wins safely.
|
|
1249
|
+
if (!readyAppServer || turnActive) return false
|
|
1239
1250
|
const compactTurn = new Promise((resolve, reject) => {
|
|
1240
1251
|
startTimer = setTimeout(() => {
|
|
1241
1252
|
compactStartWaiter = null
|
|
@@ -1244,6 +1255,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
1244
1255
|
compactStartWaiter = { resolve, reject }
|
|
1245
1256
|
})
|
|
1246
1257
|
await appServer.compact({ threadId: sessionId })
|
|
1258
|
+
compactAccepted = true
|
|
1247
1259
|
const turnId = await compactTurn
|
|
1248
1260
|
if (startTimer) clearTimeout(startTimer)
|
|
1249
1261
|
const completed = await appServer.waitForTurn(turnId)
|
|
@@ -1252,6 +1264,14 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
1252
1264
|
if (status === 'completed') return true
|
|
1253
1265
|
throw new Error(completed?.turn?.error?.message || `compaction ${status || 'failed'}`)
|
|
1254
1266
|
} catch (error) {
|
|
1267
|
+
// Once thread/compact/start was accepted, replaying via a bounded-recap
|
|
1268
|
+
// reset is unsafe: the native compaction may still complete (the exact
|
|
1269
|
+
// production failure this guard closes). Preserve the existing thread
|
|
1270
|
+
// and report uncertainty instead of executing a second compaction path.
|
|
1271
|
+
if (compactAccepted || !['rejected', 'not_sent'].includes(error?.delivery)) {
|
|
1272
|
+
note(`Native Codex compaction status is uncertain; existing context was preserved: ${error?.message || error}`)
|
|
1273
|
+
return null
|
|
1274
|
+
}
|
|
1255
1275
|
note(`Native Codex compaction unavailable; using bounded recap fallback: ${error?.message || error}`)
|
|
1256
1276
|
return false
|
|
1257
1277
|
} finally {
|