thinkpool-pair 0.7.283 → 0.7.285
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 +45 -7
- package/codex-session.mjs +14 -11
- package/cumulative-event-relay.mjs +48 -0
- package/hermes-session.mjs +5 -3
- package/package.json +2 -1
package/bridge.mjs
CHANGED
|
@@ -929,6 +929,37 @@ const terms = new Map()
|
|
|
929
929
|
// PTY `terms` map so none of the byte-relay code paths (flush, resize,
|
|
930
930
|
// pty-in, scrollback) ever touch them. id → { session, cmd, log, pending }.
|
|
931
931
|
const sessions = new Map()
|
|
932
|
+
|
|
933
|
+
// One monotonically increasing authority per bridge process. Realtime preserves
|
|
934
|
+
// messages best-effort, not total order across a reconnect; clients use this to
|
|
935
|
+
// reject a late snapshot from an older channel while bridge_id distinguishes a
|
|
936
|
+
// real process restart (where the counter intentionally begins again).
|
|
937
|
+
let announceRev = 0
|
|
938
|
+
|
|
939
|
+
function syncStructuredTurn(entry, now = Date.now()) {
|
|
940
|
+
const busy = entry?.session?.turnActive ?? false
|
|
941
|
+
const changed = busy !== entry?._busyAnn
|
|
942
|
+
if (!changed) return false
|
|
943
|
+
if (busy) {
|
|
944
|
+
entry._turnRev = (Number(entry._turnRev) || 0) + 1
|
|
945
|
+
entry._turnStart = now
|
|
946
|
+
}
|
|
947
|
+
entry._busyAnn = busy
|
|
948
|
+
return true
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function beginStructuredTurn(entry, now = Date.now()) {
|
|
952
|
+
if (entry?._busyAnn === true) return false
|
|
953
|
+
entry._turnRev = (Number(entry._turnRev) || 0) + 1
|
|
954
|
+
entry._turnStart = now
|
|
955
|
+
entry._busyAnn = true
|
|
956
|
+
return true
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function stampStructuredTurn(entry, event) {
|
|
960
|
+
if (event && event.turnRev == null && Number(entry?._turnRev) > 0) event.turnRev = entry._turnRev
|
|
961
|
+
return event
|
|
962
|
+
}
|
|
932
963
|
let attachedId = null
|
|
933
964
|
let shuttingDown = false
|
|
934
965
|
|
|
@@ -1058,8 +1089,9 @@ const announce = () => {
|
|
|
1058
1089
|
// (name-only, NEVER key/baseUrl) so a cross-device viewer can badge a lane opened
|
|
1059
1090
|
// on a custom provider without the owner's account-channel registry.
|
|
1060
1091
|
const provNames = providerNameMap()
|
|
1092
|
+
const rev = ++announceRev
|
|
1061
1093
|
return bcast('bridge', {
|
|
1062
|
-
v: 2, name, bridge_id: BRIDGE_ID, repo: repoLabel, branch: readBranch(),
|
|
1094
|
+
v: 2, name, bridge_id: BRIDGE_ID, rev, repo: repoLabel, branch: readBranch(),
|
|
1063
1095
|
// sdkWarn: the auto-pulled agent SDK failed its boot compatibility smoke test —
|
|
1064
1096
|
// surfaced so the room can show a banner (turns may misbehave; pin a good SDK).
|
|
1065
1097
|
...(sdkStatus.ok === false ? { sdkWarn: `${sdkStatus.version}: ${sdkStatus.reason}` } : {}),
|
|
@@ -1130,7 +1162,7 @@ const announce = () => {
|
|
|
1130
1162
|
// laneStatusOf: authoritative busy/idle + last-action timestamp/age +
|
|
1131
1163
|
// STUCK/BLOCKED alert. The bridge owns the turn and permission state, so
|
|
1132
1164
|
// every roster consumer reads one status instead of reconstructing it.
|
|
1133
|
-
...[...sessions.entries()].map(([id, s]) => ({ id, cmd: s.cmd, kind: 'structured', runtime: s.runtime || 'claude', alive: true, ...laneStatusOf(s), hasTranscript: s.log.length > 0, commands: s.commands, mode: s.mode || undefined, effort: s.effort || undefined, name: termNames[id] || undefined, model: s.model || undefined, capabilities: { ...structuredRuntimeMetadata(s.runtime || 'claude'), modes: structuredModesForLane(s.runtime || 'claude', s) }, ...(s.runtime === 'codex' ? { approvalPolicy: codexConfigForMode(s.mode).approvalPolicy, models: s.models || [], canSteer: s.session?.canSteer ?? false } : s.runtime === 'hermes' ? { models: s.models || [], canSteer: s.session?.canSteer ?? false } : {}), ...(s.archiveOldestSeq != null ? { oldestSeq: s.archiveOldestSeq } : {}), ...(s.spawnedBy ? { spawned: true, spawnedBy: s.spawnedBy } : {}), ...(s.sideParent ? { sideParent: s.sideParent, sideTask: s.sideTask || undefined, sideHandback: !!s.sideHandback } : {}), ...(s.flowSessionId ? { flowId: s.flowSessionId, flowRole: s.flowTaskKey ? 'lane' : 'conductor' } : {}),
|
|
1165
|
+
...[...sessions.entries()].map(([id, s]) => ({ id, cmd: s.cmd, kind: 'structured', runtime: s.runtime || 'claude', alive: true, ...laneStatusOf(s), turnRev: Number(s._turnRev) || 0, ...(s.session?.turnActive && s._turnStart ? { turnStartedAt: s._turnStart } : {}), hasTranscript: s.log.length > 0, commands: s.commands, mode: s.mode || undefined, effort: s.effort || undefined, name: termNames[id] || undefined, model: s.model || undefined, capabilities: { ...structuredRuntimeMetadata(s.runtime || 'claude'), modes: structuredModesForLane(s.runtime || 'claude', s) }, ...(s.runtime === 'codex' ? { approvalPolicy: codexConfigForMode(s.mode).approvalPolicy, models: s.models || [], canSteer: s.session?.canSteer ?? false } : s.runtime === 'hermes' ? { models: s.models || [], canSteer: s.session?.canSteer ?? false } : {}), ...(s.archiveOldestSeq != null ? { oldestSeq: s.archiveOldestSeq } : {}), ...(s.spawnedBy ? { spawned: true, spawnedBy: s.spawnedBy } : {}), ...(s.sideParent ? { sideParent: s.sideParent, sideTask: s.sideTask || undefined, sideHandback: !!s.sideHandback } : {}), ...(s.flowSessionId ? { flowId: s.flowSessionId, flowRole: s.flowTaskKey ? 'lane' : 'conductor' } : {}),
|
|
1134
1166
|
// provider: the registered LLM-provider this lane runs on, NAME-ONLY {id,name}
|
|
1135
1167
|
// (NEVER the key or baseUrl). Additive; older clients ignore it. Omitted for the
|
|
1136
1168
|
// built-in/default Claude path (no badge). Makes the lane's provider badge +
|
|
@@ -1684,6 +1716,7 @@ function pushLog(entry, evt) {
|
|
|
1684
1716
|
// every old user message stacks at the TOP of the timeline (room XE3IQ, 2026-06-25).
|
|
1685
1717
|
// A real ts makes ordering wall-clock — immune to eviction AND /clear seq resets.
|
|
1686
1718
|
// stampEvent is idempotent, so the callers that already stamp are unaffected.
|
|
1719
|
+
stampStructuredTurn(entry, evt)
|
|
1687
1720
|
stampEvent(evt)
|
|
1688
1721
|
entry.lastActionAt = evt.ts
|
|
1689
1722
|
entry.lastEvent = evt
|
|
@@ -1818,6 +1851,7 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
1818
1851
|
// order (not readdir/filesystem order). Legacy recs (no openedAt) derive it from the
|
|
1819
1852
|
// first transcript event ts, so even the first post-fix restart is ordered right.
|
|
1820
1853
|
openedAt: openedAt || (Array.isArray(log) ? (log.find((e) => e?.ts)?.ts || 0) : 0) || Date.now() }
|
|
1854
|
+
entry._turnRev = entry.log.reduce((max, event) => Math.max(max, Number(event?.turnRev) || 0), 0)
|
|
1821
1855
|
entry.interruptedRecap = restoredTurnOpen(entry.log) ? buildRecapFromLog(entry.log, RECAP_CAP) : null
|
|
1822
1856
|
// Slice 3 — a permission card left unanswered past the grace window pushes
|
|
1823
1857
|
// "<lane> — needs you: <what>"; answering it anywhere retracts the banner
|
|
@@ -2841,6 +2875,8 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
2841
2875
|
// replay-union dedupes ONLY by cid, and SDK events carry none — without an
|
|
2842
2876
|
// id, an event that arrives both live AND in a reconnect replay renders
|
|
2843
2877
|
// twice (the 2026-06-19 duplicate-message bug). See event-id.mjs.
|
|
2878
|
+
const busyChanged = syncStructuredTurn(entry)
|
|
2879
|
+
stampStructuredTurn(entry, evt)
|
|
2844
2880
|
stampEvent(evt)
|
|
2845
2881
|
const stalledChanged = evt.kind === 'stalled' ? !entry.stalled : !!entry.stalled
|
|
2846
2882
|
entry.stalled = evt.kind === 'stalled'
|
|
@@ -2856,10 +2892,7 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
2856
2892
|
// The RISING edge is also where a turn's wall clock starts — the only place the
|
|
2857
2893
|
// bridge sees "this lane just began working". `result`/`error` reads it to decide
|
|
2858
2894
|
// whether the turn was long enough that nobody was watching (Slice 3).
|
|
2859
|
-
|
|
2860
|
-
const busyChanged = busyNow !== entry._busyAnn
|
|
2861
|
-
if (busyChanged) { if (busyNow) entry._turnStart = Date.now(); entry._busyAnn = busyNow }
|
|
2862
|
-
if (busyChanged || stalledChanged) announce() }
|
|
2895
|
+
if (busyChanged || stalledChanged) announce()
|
|
2863
2896
|
// App Server steering is opt-in and can fall back to exec at runtime. Keep
|
|
2864
2897
|
// the roster capability authoritative so the browser never immediate-sends
|
|
2865
2898
|
// into an exec-only turn and loses its shared queue.
|
|
@@ -2910,7 +2943,7 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
2910
2943
|
// usage needs an explicit persist() here to survive a bridge restart.
|
|
2911
2944
|
if (evt.kind === 'usage') { entry.lastUsage = evt; persist() }
|
|
2912
2945
|
if (evt.kind === 'effort') {
|
|
2913
|
-
entry.effort = evt.effort ?? null
|
|
2946
|
+
entry.effort = evt.level ?? evt.effort ?? null
|
|
2914
2947
|
persist()
|
|
2915
2948
|
announce()
|
|
2916
2949
|
}
|
|
@@ -3909,7 +3942,12 @@ channel
|
|
|
3909
3942
|
const nativeImages = s.runtime === 'codex' || s.runtime === 'hermes'
|
|
3910
3943
|
? await waitForNativeImages(payload.files, { updir: UPDIR })
|
|
3911
3944
|
: []
|
|
3945
|
+
// Sample the runtime before dispatch so a missed prior falling edge cannot
|
|
3946
|
+
// make this genuinely new turn inherit the previous turn's revision.
|
|
3947
|
+
syncStructuredTurn(s)
|
|
3912
3948
|
const accepted = s.session.sendTurn(sendText, nativeImages.length ? { images: nativeImages } : undefined)
|
|
3949
|
+
if (accepted === false) syncStructuredTurn(s)
|
|
3950
|
+
else beginStructuredTurn(s)
|
|
3913
3951
|
echoYou()
|
|
3914
3952
|
if (accepted === false) {
|
|
3915
3953
|
// A runtime that did not accept a turn must still close the optimistic
|
package/codex-session.mjs
CHANGED
|
@@ -33,6 +33,7 @@ import { startCodexMcpHttp } from './codex-mcp-http.mjs'
|
|
|
33
33
|
import { CODEX_THINKPOOL_FIRST_TURN_PREAMBLE, buildThinkPoolTurnGuidance, createRoomContextSelector, usesFullThinkPoolReminder } from './thinkpool-room-prompt.mjs'
|
|
34
34
|
import { questionAnswerResponse } from './question-response.mjs'
|
|
35
35
|
import { codexReviewTarget, isCodexCompactCommand } from './codex-commands.mjs'
|
|
36
|
+
import { createCumulativeEventRelay } from './cumulative-event-relay.mjs'
|
|
36
37
|
|
|
37
38
|
const DEFAULT_SANDBOX = 'workspace-write'
|
|
38
39
|
const SAFE_SANDBOXES = new Set(['read-only', 'workspace-write', 'danger-full-access'])
|
|
@@ -402,8 +403,9 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
402
403
|
let chain = Promise.resolve()
|
|
403
404
|
const queue = []
|
|
404
405
|
|
|
406
|
+
const relayEvent = createCumulativeEventRelay((event) => { try { onEvent?.(event) } catch { /* consumer isolation */ } })
|
|
405
407
|
const mapper = new CodexEventMapper({
|
|
406
|
-
onEvent,
|
|
408
|
+
onEvent: relayEvent,
|
|
407
409
|
model: model || null,
|
|
408
410
|
usageSnapshotForSession: (id) => {
|
|
409
411
|
const snapshot = readCodexThreadUsage(id)
|
|
@@ -425,10 +427,10 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
425
427
|
}
|
|
426
428
|
const emitTurnBoundary = (event) => {
|
|
427
429
|
turnActive = false
|
|
428
|
-
|
|
430
|
+
relayEvent(event)
|
|
429
431
|
}
|
|
430
432
|
|
|
431
|
-
const note = (text) =>
|
|
433
|
+
const note = (text) => relayEvent({ kind: 'note', text })
|
|
432
434
|
|
|
433
435
|
const closeAppServerTurn = (turnId = activeTurnId) => {
|
|
434
436
|
const id = String(turnId || '')
|
|
@@ -472,7 +474,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
472
474
|
activeForm: step || '',
|
|
473
475
|
status: status === 'inProgress' ? 'in_progress' : status === 'completed' ? 'completed' : 'pending',
|
|
474
476
|
})).filter((todo) => todo.content)
|
|
475
|
-
|
|
477
|
+
relayEvent({ kind: 'assistant', blocks: [{ type: 'tool_use', id: `codex-plan:${params.turnId || 'active'}`, name: 'TodoWrite', input: { todos } }], parentToolUseId: null })
|
|
476
478
|
return
|
|
477
479
|
}
|
|
478
480
|
if (method === 'item/agentMessage/delta') {
|
|
@@ -484,7 +486,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
484
486
|
// Cumulative transient snapshot. The bridge broadcasts this chrome event
|
|
485
487
|
// without assigning transcript seq/archive rows; room.jsx replaces the
|
|
486
488
|
// stable cid in place. item/completed emits one durable assistant row.
|
|
487
|
-
|
|
489
|
+
relayEvent({ kind: 'assistant_stream', cid: state.cid, text: state.text, itemId, streaming: true })
|
|
488
490
|
return
|
|
489
491
|
}
|
|
490
492
|
if (method === 'item/started' || method === 'item/completed') {
|
|
@@ -496,7 +498,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
496
498
|
if (raw.type === 'agentMessage' && streamedAgentItems.has(raw.id)) {
|
|
497
499
|
const state = streamedAgentItems.get(raw.id)
|
|
498
500
|
streamedAgentItems.delete(raw.id)
|
|
499
|
-
|
|
501
|
+
relayEvent({ kind: 'assistant', blocks: [{ type: 'text', text: raw.text || state.text }], parentToolUseId: null, replacesCid: state.cid })
|
|
500
502
|
return
|
|
501
503
|
}
|
|
502
504
|
}
|
|
@@ -639,7 +641,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
639
641
|
// than leaving a replay-less streaming shell behind.
|
|
640
642
|
for (const state of streamedAgentItems.values()) {
|
|
641
643
|
if (!state.text) continue
|
|
642
|
-
|
|
644
|
+
relayEvent({ kind: 'assistant', blocks: [{ type: 'text', text: state.text }], parentToolUseId: null, replacesCid: state.cid })
|
|
643
645
|
}
|
|
644
646
|
activeTurnId = null
|
|
645
647
|
turnActive = false
|
|
@@ -683,7 +685,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
683
685
|
} finally {
|
|
684
686
|
for (const state of streamedAgentItems.values()) {
|
|
685
687
|
if (!state.text) continue
|
|
686
|
-
|
|
688
|
+
relayEvent({ kind: 'assistant', blocks: [{ type: 'text', text: state.text }], parentToolUseId: null, replacesCid: state.cid })
|
|
687
689
|
}
|
|
688
690
|
activeTurnId = null
|
|
689
691
|
turnActive = false
|
|
@@ -817,7 +819,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
817
819
|
if (!turnActive && queue.length === 0 && !appServerThreadReady && !child) {
|
|
818
820
|
const gate = typeof admitStart === 'function' ? admitStart() : { ok: true }
|
|
819
821
|
if (gate?.ok === false) {
|
|
820
|
-
|
|
822
|
+
relayEvent({ kind: 'error', message: gate.reason || 'Host memory is critically low. This Codex runtime was not started.', recoverable: true })
|
|
821
823
|
return false
|
|
822
824
|
}
|
|
823
825
|
}
|
|
@@ -848,7 +850,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
848
850
|
queue.push({ text, options, promptIndex, forceFullReminder: thisTurnForceFull })
|
|
849
851
|
if (!turnActive && queue.length === 1) pump()
|
|
850
852
|
} else {
|
|
851
|
-
|
|
853
|
+
relayEvent({ kind: 'error', message: 'Codex steering delivery is uncertain; the message was not replayed automatically.', recoverable: true })
|
|
852
854
|
}
|
|
853
855
|
})
|
|
854
856
|
return true
|
|
@@ -884,6 +886,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
884
886
|
end() {
|
|
885
887
|
ended = true
|
|
886
888
|
turnActive = false
|
|
889
|
+
relayEvent.cancel()
|
|
887
890
|
queue.length = 0
|
|
888
891
|
closeAppServerTurn(activeTurnId)
|
|
889
892
|
if (child) { try { child.kill() } catch { /* noop */ } }
|
|
@@ -912,7 +915,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
912
915
|
setEffort(nextEffort) {
|
|
913
916
|
if (turnActive || !EFFORT_LEVELS.has(nextEffort)) return false
|
|
914
917
|
activeEffort = nextEffort
|
|
915
|
-
|
|
918
|
+
relayEvent({ kind: 'effort', level: activeEffort })
|
|
916
919
|
return true
|
|
917
920
|
},
|
|
918
921
|
clearContext() {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Cumulative provider snapshots are replace-in-place UI state, not transcript
|
|
2
|
+
// facts. Deliver the first immediately and coalesce a burst to its latest frame;
|
|
3
|
+
// any durable event flushes the pending snapshot first to preserve wire order.
|
|
4
|
+
export function createCumulativeEventRelay(emit, waitMs = 150) {
|
|
5
|
+
let lastSentAt = 0
|
|
6
|
+
let pending = null
|
|
7
|
+
let timer = null
|
|
8
|
+
|
|
9
|
+
const send = (event) => {
|
|
10
|
+
lastSentAt = Date.now()
|
|
11
|
+
emit(event)
|
|
12
|
+
}
|
|
13
|
+
const flush = () => {
|
|
14
|
+
if (timer) clearTimeout(timer)
|
|
15
|
+
timer = null
|
|
16
|
+
if (!pending) return
|
|
17
|
+
const event = pending
|
|
18
|
+
pending = null
|
|
19
|
+
send(event)
|
|
20
|
+
}
|
|
21
|
+
const relay = (event) => {
|
|
22
|
+
if (event?.kind !== 'assistant_stream') {
|
|
23
|
+
flush()
|
|
24
|
+
emit(event)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
const remaining = waitMs - (Date.now() - lastSentAt)
|
|
28
|
+
if (!lastSentAt || remaining <= 0) {
|
|
29
|
+
pending = null
|
|
30
|
+
if (timer) clearTimeout(timer)
|
|
31
|
+
timer = null
|
|
32
|
+
send(event)
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
pending = event
|
|
36
|
+
if (!timer) {
|
|
37
|
+
timer = setTimeout(flush, remaining)
|
|
38
|
+
timer.unref?.()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
relay.flush = flush
|
|
42
|
+
relay.cancel = () => {
|
|
43
|
+
if (timer) clearTimeout(timer)
|
|
44
|
+
timer = null
|
|
45
|
+
pending = null
|
|
46
|
+
}
|
|
47
|
+
return relay
|
|
48
|
+
}
|
package/hermes-session.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import { startCodexMcpHttp } from './codex-mcp-http.mjs'
|
|
|
11
11
|
import { autoAllow, classifyRisk } from './claude-session.mjs'
|
|
12
12
|
import { crossPostNeedsCard } from './cross-terminal.mjs'
|
|
13
13
|
import { buildThinkPoolTurnGuidance, createRoomContextSelector, usesFullThinkPoolReminder } from './thinkpool-room-prompt.mjs'
|
|
14
|
+
import { createCumulativeEventRelay } from './cumulative-event-relay.mjs'
|
|
14
15
|
|
|
15
16
|
export const HERMES_COMMAND = 'thinkpool'
|
|
16
17
|
export const HERMES_ACP_PROTOCOL_VERSION = 1
|
|
@@ -112,7 +113,7 @@ export function startHermesSession({
|
|
|
112
113
|
? 'dont_ask'
|
|
113
114
|
: 'default'
|
|
114
115
|
|
|
115
|
-
const emit = (event) => { try { onEvent?.(event) } catch { /* consumer isolation */ } }
|
|
116
|
+
const emit = createCumulativeEventRelay((event) => { try { onEvent?.(event) } catch { /* consumer isolation */ } })
|
|
116
117
|
|
|
117
118
|
const thinkpoolPeerTool = (name) => {
|
|
118
119
|
const lower = String(name || '').toLowerCase()
|
|
@@ -479,7 +480,7 @@ export function startHermesSession({
|
|
|
479
480
|
: new RegExp(`Session reasoning set to ${requested}`, 'i').test(mapper.text)
|
|
480
481
|
if (confirmed) {
|
|
481
482
|
activeEffort = requested === 'reset' ? null : requested
|
|
482
|
-
emit({ kind: 'effort',
|
|
483
|
+
emit({ kind: 'effort', level: activeEffort })
|
|
483
484
|
}
|
|
484
485
|
}
|
|
485
486
|
// Reserve the idle-looking result boundary for the existing FIFO before
|
|
@@ -598,6 +599,7 @@ export function startHermesSession({
|
|
|
598
599
|
end() {
|
|
599
600
|
ended = true
|
|
600
601
|
turnActive = false
|
|
602
|
+
emit.cancel()
|
|
601
603
|
queuedTurns.length = 0
|
|
602
604
|
queueDrainPending = false
|
|
603
605
|
if (sessionId && client?.alive) void client.request('session/close', { sessionId }, 1500).catch(() => {}).finally(() => client?.end())
|
|
@@ -660,7 +662,7 @@ export function startHermesSession({
|
|
|
660
662
|
const requested = String(nextEffort)
|
|
661
663
|
void boot().then(() => client.request('session/set_config_option', { sessionId, configId: HERMES_EFFORT_CONFIG_ID, value: requested })).then(() => {
|
|
662
664
|
activeEffort = requested
|
|
663
|
-
emit({ kind: 'effort',
|
|
665
|
+
emit({ kind: 'effort', level: activeEffort })
|
|
664
666
|
}).catch((error) => emit({ kind: 'error', message: `Hermes reasoning change failed: ${error?.message || error}`, recoverable: true }))
|
|
665
667
|
return true
|
|
666
668
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinkpool-pair",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.285",
|
|
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
|
"codex-mcp-http.mjs",
|
|
28
28
|
"lane-worktree.mjs",
|
|
29
29
|
"codex-event-mapper.mjs",
|
|
30
|
+
"cumulative-event-relay.mjs",
|
|
30
31
|
"codex-commands.mjs",
|
|
31
32
|
"acp-client.mjs",
|
|
32
33
|
"hermes-session.mjs",
|