thinkpool-pair 0.7.238 → 0.7.239
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 +3 -1
- package/codex-mcp-http.mjs +53 -12
- package/hermes-isolation.mjs +3 -1
- package/package.json +1 -1
package/bridge.mjs
CHANGED
|
@@ -50,6 +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 { readCodexDefaultModel, readCodexModels, codexConfigForMode, codexThreadCanResume } from './codex-session.mjs'
|
|
53
|
+
import { withMcpSessionFactory } from './codex-mcp-http.mjs'
|
|
53
54
|
import { startStructuredSession } from './runtime-session.mjs'
|
|
54
55
|
import { defaultStructuredMode, shouldDeferStructuredRuntime, structuredRuntimeForCommand, structuredRuntimeMetadata, structuredRuntimeSupportsMode } from './runtime-registry.mjs'
|
|
55
56
|
import { probeHermesRuntime } from './hermes-probe.mjs'
|
|
@@ -2052,7 +2053,7 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
2052
2053
|
// own `id` (excluded) + `entry` (per-turn call counter, reset by the code-turn
|
|
2053
2054
|
// handler). Auto-allowed by the PreToolUse gate (mcp__thinkpool__ prefix).
|
|
2054
2055
|
// Spec: docs/specs/2026-06-28-cross-terminal-awareness.md
|
|
2055
|
-
const
|
|
2056
|
+
const createPeekServer = () => createSdkMcpServer({
|
|
2056
2057
|
name: 'thinkpool',
|
|
2057
2058
|
version: '1.0.0',
|
|
2058
2059
|
tools: [
|
|
@@ -2420,6 +2421,7 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
|
|
|
2420
2421
|
)] : []),
|
|
2421
2422
|
],
|
|
2422
2423
|
})
|
|
2424
|
+
const peekServer = withMcpSessionFactory(createPeekServer)
|
|
2423
2425
|
// S5 (slice 1b) — the review-lane write-block, wired into the live PreToolUse path.
|
|
2424
2426
|
// Null for builder lanes (no reviewSliceRoots) → their toolset is UNCHANGED. For a review
|
|
2425
2427
|
// lane, deny a mutating tool call if it targets ANY reviewed slice's worktree, allowing the
|
package/codex-mcp-http.mjs
CHANGED
|
@@ -6,18 +6,47 @@ import http from 'node:http'
|
|
|
6
6
|
import { randomUUID } from 'node:crypto'
|
|
7
7
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
|
|
8
8
|
|
|
9
|
+
export function withMcpSessionFactory(createSessionServer) {
|
|
10
|
+
if (typeof createSessionServer !== 'function') throw new TypeError('MCP session factory must be a function')
|
|
11
|
+
return Object.assign(createSessionServer(), { createSessionServer })
|
|
12
|
+
}
|
|
13
|
+
|
|
9
14
|
export async function startCodexMcpHttp({ sdkServer, host = '127.0.0.1' } = {}) {
|
|
10
|
-
const
|
|
11
|
-
|
|
15
|
+
const createSessionServer = sdkServer?.createSessionServer
|
|
16
|
+
const initialInstance = sdkServer?.instance
|
|
17
|
+
if (!initialInstance?.connect || typeof createSessionServer !== 'function') {
|
|
18
|
+
throw new TypeError('Codex MCP requires an SDK MCP server and createSessionServer factory')
|
|
19
|
+
}
|
|
12
20
|
|
|
13
21
|
const secretPath = `/mcp/${randomUUID()}`
|
|
14
|
-
// Stateful sessions
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
// Stateful sessions are required: this SDK's stateless transport rejects the
|
|
23
|
+
// `notifications/initialized` POST sent by every MCP client. A transport is
|
|
24
|
+
// single-initialize, though, while Codex App Server -> exec fallback and Hermes
|
|
25
|
+
// crash recovery both start a new client against the same lane endpoint.
|
|
26
|
+
// Keep exactly one active child session per lane. A new initialize atomically
|
|
27
|
+
// retires the old server+transport before receiving its own isolated instance.
|
|
28
|
+
let active = null
|
|
29
|
+
|
|
30
|
+
const retireActive = async () => {
|
|
31
|
+
const current = active
|
|
32
|
+
active = null
|
|
33
|
+
if (!current) return
|
|
34
|
+
await Promise.allSettled([
|
|
35
|
+
current.instance.close(),
|
|
36
|
+
current.transport.close(),
|
|
37
|
+
])
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const beginSession = async () => {
|
|
41
|
+
await retireActive()
|
|
42
|
+
const next = createSessionServer()
|
|
43
|
+
const instance = next?.instance
|
|
44
|
+
if (!instance?.connect) throw new TypeError('Codex MCP session factory did not return an SDK MCP server instance')
|
|
45
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() })
|
|
46
|
+
await instance.connect(transport)
|
|
47
|
+
active = { instance, transport }
|
|
48
|
+
return active
|
|
49
|
+
}
|
|
21
50
|
|
|
22
51
|
const server = http.createServer(async (req, res) => {
|
|
23
52
|
if (req.url !== secretPath) {
|
|
@@ -25,7 +54,19 @@ export async function startCodexMcpHttp({ sdkServer, host = '127.0.0.1' } = {})
|
|
|
25
54
|
return
|
|
26
55
|
}
|
|
27
56
|
try {
|
|
28
|
-
|
|
57
|
+
const sessionId = req.headers['mcp-session-id']
|
|
58
|
+
let target = active
|
|
59
|
+
if (!sessionId) {
|
|
60
|
+
if (req.method !== 'POST') {
|
|
61
|
+
res.writeHead(400).end('missing session')
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
target = await beginSession()
|
|
65
|
+
} else if (!target?.transport?.sessionId || sessionId !== target.transport.sessionId) {
|
|
66
|
+
res.writeHead(404).end('session not found')
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
await target.transport.handleRequest(req, res)
|
|
29
70
|
} catch {
|
|
30
71
|
if (!res.headersSent) res.writeHead(500)
|
|
31
72
|
res.end()
|
|
@@ -38,7 +79,7 @@ export async function startCodexMcpHttp({ sdkServer, host = '127.0.0.1' } = {})
|
|
|
38
79
|
server.listen(0, host, resolve)
|
|
39
80
|
})
|
|
40
81
|
} catch (error) {
|
|
41
|
-
await
|
|
82
|
+
await retireActive()
|
|
42
83
|
throw error
|
|
43
84
|
}
|
|
44
85
|
const address = server.address()
|
|
@@ -51,7 +92,7 @@ export async function startCodexMcpHttp({ sdkServer, host = '127.0.0.1' } = {})
|
|
|
51
92
|
if (closed) return
|
|
52
93
|
closed = true
|
|
53
94
|
await Promise.allSettled([
|
|
54
|
-
|
|
95
|
+
retireActive(),
|
|
55
96
|
new Promise((resolve) => server.close(resolve)),
|
|
56
97
|
])
|
|
57
98
|
},
|
package/hermes-isolation.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'node:path'
|
|
|
4
4
|
import { randomUUID } from 'node:crypto'
|
|
5
5
|
import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk'
|
|
6
6
|
import { startHermesSession } from './hermes-session.mjs'
|
|
7
|
+
import { withMcpSessionFactory } from './codex-mcp-http.mjs'
|
|
7
8
|
|
|
8
9
|
const waitFor = (predicate, timeoutMs = 25_000) => new Promise((resolve, reject) => {
|
|
9
10
|
const started = Date.now()
|
|
@@ -36,10 +37,11 @@ export async function verifyHermesMcpIsolation({ rounds = 2, sessionFactory = st
|
|
|
36
37
|
for (let i = 0; i < 2; i++) {
|
|
37
38
|
const cwd = path.join(root, i === 0 ? 'alpha' : 'beta')
|
|
38
39
|
fs.mkdirSync(cwd)
|
|
39
|
-
const
|
|
40
|
+
const createMarkerServer = () => createSdkMcpServer({
|
|
40
41
|
name: 'thinkpool', version: '1.0.0',
|
|
41
42
|
tools: [tool(markers[i], 'Synthetic ThinkPool lane-isolation marker.', {}, async () => ({ content: [{ type: 'text', text: markers[i] }] }))],
|
|
42
43
|
})
|
|
44
|
+
const sdkServer = withMcpSessionFactory(createMarkerServer)
|
|
43
45
|
sessions.push(sessionFactory({ cwd, lazy: true, mcpServers: { thinkpool: sdkServer }, onEvent: (event) => events[i].push(event) }))
|
|
44
46
|
}
|
|
45
47
|
sessions.forEach((session) => session.sendTurn('/tools'))
|