thinkpool-pair 0.7.191 → 0.7.192

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 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 } from './codex-session.mjs'
53
+ import { startCodexSession, readCodexDefaultModel, readCodexModels } from './codex-session.mjs'
54
54
  import { FLOW_CONDUCTOR_PROMPT, FLOW_LANE_PROMPT, buildConductorEnv, assembleCrossWaveContext, buildLanePrompt } from './flow-conductor.mjs'
55
55
  import { normalizePlanOutput, laneModelFor } from './flow-task-graph.mjs' // FL-B1 — validate the conductor's submit_flow_plan task-graph; laneModelFor — per-slice model tier (2026-07-03-flow-lane-model-tiers)
56
56
  // S1 (context-offload) — durable digest store. mark_flow_done digests a closed slice in;
@@ -1070,7 +1070,7 @@ const announce = () => {
1070
1070
  // turnActive). Authoritative — the bridge owns the turn — so the /code
1071
1071
  // dashboard reads "N agents working" off the roster instead of sniffing
1072
1072
  // the live code-event stream (which a passive dashboard channel misses).
1073
- ...[...sessions.entries()].map(([id, s]) => ({ id, cmd: s.cmd, kind: 'structured', runtime: s.runtime || 'claude', alive: true, busy: s.session?.turnActive ?? false, commands: s.commands, mode: s.mode || undefined, name: termNames[id] || undefined, model: s.model || undefined, ...(s.archiveOldestSeq != null ? { oldestSeq: s.archiveOldestSeq } : {}), ...(s.spawnedBy ? { spawned: true, spawnedBy: s.spawnedBy } : {}), ...(s.flowSessionId ? { flowId: s.flowSessionId, flowRole: s.flowTaskKey ? 'lane' : 'conductor' } : {}),
1073
+ ...[...sessions.entries()].map(([id, s]) => ({ id, cmd: s.cmd, kind: 'structured', runtime: s.runtime || 'claude', alive: true, busy: s.session?.turnActive ?? false, commands: s.commands, mode: s.mode || undefined, name: termNames[id] || undefined, model: s.model || undefined, ...(s.runtime === 'codex' ? { approvalPolicy: 'never', models: s.models || [] } : {}), ...(s.archiveOldestSeq != null ? { oldestSeq: s.archiveOldestSeq } : {}), ...(s.spawnedBy ? { spawned: true, spawnedBy: s.spawnedBy } : {}), ...(s.flowSessionId ? { flowId: s.flowSessionId, flowRole: s.flowTaskKey ? 'lane' : 'conductor' } : {}),
1074
1074
  // provider: the registered LLM-provider this lane runs on, NAME-ONLY {id,name}
1075
1075
  // (NEVER the key or baseUrl). Additive; older clients ignore it. Omitted for the
1076
1076
  // built-in/default Claude path (no badge). Makes the lane's provider badge +
@@ -1580,7 +1580,7 @@ function openStructured({ id, runtime = 'claude', model, resume, log, commands,
1580
1580
  // spawnedBy: set when this lane was Dispatched (spawn_terminal). Restored from the
1581
1581
  // session store so the Ensemble flag survives a bridge restart (else a respin
1582
1582
  // stripped it and the lane reverted to a plain tab — the t6 "no chip" bug).
1583
- const entry = { cmd: runtime, runtime, kind: 'structured', log: Array.isArray(log) ? log.slice(-STRUCTURED_LOG_MAX) : [], pending: new Map(), session: null, recovered: false, commands: Array.isArray(commands) ? commands : undefined, mode,
1583
+ const entry = { cmd: runtime, runtime, kind: 'structured', log: Array.isArray(log) ? log.slice(-STRUCTURED_LOG_MAX) : [], pending: new Map(), session: null, recovered: false, commands: Array.isArray(commands) ? commands : undefined, mode, models: runtime === 'codex' ? readCodexModels() : undefined,
1584
1584
  // model: truthful active-model label — now the SAME `laneModel` the SDK is given, so the
1585
1585
  // chip cannot disagree with the wire. When this lane runs on a custom (non-anthropic)
1586
1586
  // registered provider the SDK id is impersonated (see the onEvent guard below), so
package/codex-session.mjs CHANGED
@@ -43,6 +43,16 @@ export function readCodexDefaultModel({ home = process.env.CODEX_HOME || path.jo
43
43
  } catch { return null }
44
44
  }
45
45
 
46
+ export function readCodexModels({ home = process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), fsImpl = fs } = {}) {
47
+ try {
48
+ const data = JSON.parse(fsImpl.readFileSync(path.join(home, 'models_cache.json'), 'utf8'))
49
+ return (Array.isArray(data?.models) ? data.models : [])
50
+ .filter((m) => m?.slug && m.visibility !== 'hide')
51
+ .sort((a, b) => (Number(a.priority) || 999) - (Number(b.priority) || 999))
52
+ .map((m) => ({ value: m.slug, displayName: m.display_name || m.slug, description: m.description || '' }))
53
+ } catch { return [] }
54
+ }
55
+
46
56
  export function normalizeCodexSandbox(value) {
47
57
  return SAFE_SANDBOXES.has(value) ? value : DEFAULT_SANDBOX
48
58
  }
@@ -97,8 +107,6 @@ export function startCodexSession({ cwd, model, resume, env, sandbox = DEFAULT_S
97
107
 
98
108
  const mapper = new CodexEventMapper({ onEvent, model: model || null })
99
109
 
100
- const note = (text) => { try { onEvent?.({ kind: 'note', text }) } catch { /* noop */ } }
101
-
102
110
  function runExec(prompt) {
103
111
  // First turn: fresh exec. Later turns: resume THIS lane's captured thread.
104
112
  // `--last` is process-global and can cross-wire two concurrently-active Codex
@@ -123,19 +131,12 @@ export function startCodexSession({ cwd, model, resume, env, sandbox = DEFAULT_S
123
131
  child = spawn('codex', args, opts)
124
132
  if (stdinFd != null) { try { fs.closeSync(stdinFd) } catch { /* child owns its duplicated fd */ } }
125
133
 
126
- // G1 mitigation: while codex composes (before the first item event), show life.
127
- let sawFirst = false
128
- const composingTimer = setTimeout(() => {
129
- if (!sawFirst) note('codex composing…')
130
- }, 1200)
131
-
132
134
  const rl = readline.createInterface({ input: child.stdout, crlfDelay: Infinity })
133
135
  rl.on('line', (line) => {
134
136
  if (!line.trim()) return
135
137
  let ev
136
138
  try { ev = JSON.parse(line) } catch { return /* non-JSON progress line */
137
139
  }
138
- if (!sawFirst && ev.type && ev.type.startsWith('item')) sawFirst = true
139
140
  if (ev.type === 'thread.started' && ev.thread_id) sessionId = sessionId || ev.thread_id
140
141
  if (ev.type === 'turn.completed' || ev.type === 'turn.failed') sawTerminalResult = true
141
142
  mapper.push(ev)
@@ -149,7 +150,6 @@ export function startCodexSession({ cwd, model, resume, env, sandbox = DEFAULT_S
149
150
  const finish = (code) => {
150
151
  if (settled) return
151
152
  settled = true
152
- clearTimeout(composingTimer)
153
153
  child = null
154
154
  turnActive = false
155
155
  if (aborted && !sawTerminalResult) {
@@ -162,7 +162,6 @@ export function startCodexSession({ cwd, model, resume, env, sandbox = DEFAULT_S
162
162
  child.on('error', (e) => {
163
163
  if (settled) return
164
164
  settled = true
165
- clearTimeout(composingTimer)
166
165
  try { onEvent?.({ kind: 'error', message: `codex failed to start: ${e.message}`, recoverable: true }) } catch { /* noop */ }
167
166
  child = null
168
167
  turnActive = false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.191",
3
+ "version": "0.7.192",
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": {