thinkpool-pair 0.7.272 → 0.7.274
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/codex-app-server.mjs +53 -0
- package/codex-session.mjs +8 -2
- package/package.json +1 -1
- package/thinkpool-capabilities.json +11 -7
package/codex-app-server.mjs
CHANGED
|
@@ -99,6 +99,8 @@ export class CodexAppServerClient {
|
|
|
99
99
|
this.pending = new Map()
|
|
100
100
|
this.turnWaiters = new Map()
|
|
101
101
|
this.completedTurns = new Map()
|
|
102
|
+
this.mcpStartup = new Map()
|
|
103
|
+
this.mcpWaiters = new Map()
|
|
102
104
|
this.closedError = null
|
|
103
105
|
this.stderrTail = ''
|
|
104
106
|
}
|
|
@@ -196,6 +198,25 @@ export class CodexAppServerClient {
|
|
|
196
198
|
}
|
|
197
199
|
}
|
|
198
200
|
}
|
|
201
|
+
if (message.method === 'mcpServer/startupStatus/updated') {
|
|
202
|
+
const params = message.params || {}
|
|
203
|
+
const threadId = String(params.threadId || '')
|
|
204
|
+
const name = String(params.name || '')
|
|
205
|
+
if (threadId && name) {
|
|
206
|
+
const key = `${threadId}\0${name}`
|
|
207
|
+
this.mcpStartup.set(key, params)
|
|
208
|
+
const waiter = this.mcpWaiters.get(key)
|
|
209
|
+
if (waiter && params.status === 'ready') {
|
|
210
|
+
this.mcpWaiters.delete(key)
|
|
211
|
+
if (waiter.timer) clearTimeout(waiter.timer)
|
|
212
|
+
waiter.resolve(params)
|
|
213
|
+
} else if (waiter && (params.status === 'failed' || params.error || params.failureReason)) {
|
|
214
|
+
this.mcpWaiters.delete(key)
|
|
215
|
+
if (waiter.timer) clearTimeout(waiter.timer)
|
|
216
|
+
waiter.reject(new Error(`Codex MCP server ${name} failed to start: ${params.error || params.failureReason || params.status}`))
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
199
220
|
try { this.onNotification?.(message.method, message.params || {}) } catch { /* consumer isolation */ }
|
|
200
221
|
}
|
|
201
222
|
|
|
@@ -208,6 +229,12 @@ export class CodexAppServerClient {
|
|
|
208
229
|
for (const { reject } of this.turnWaiters.values()) reject(this.closedError)
|
|
209
230
|
this.turnWaiters.clear()
|
|
210
231
|
this.completedTurns.clear()
|
|
232
|
+
for (const { reject, timer } of this.mcpWaiters.values()) {
|
|
233
|
+
if (timer) clearTimeout(timer)
|
|
234
|
+
reject(this.closedError)
|
|
235
|
+
}
|
|
236
|
+
this.mcpWaiters.clear()
|
|
237
|
+
this.mcpStartup.clear()
|
|
211
238
|
try { this.onClose?.(this.closedError) } catch { /* consumer isolation */ }
|
|
212
239
|
}
|
|
213
240
|
|
|
@@ -221,6 +248,32 @@ export class CodexAppServerClient {
|
|
|
221
248
|
return id
|
|
222
249
|
}
|
|
223
250
|
|
|
251
|
+
waitForMcpServer({ threadId, name, timeoutMs = this.threadTimeoutMs } = {}) {
|
|
252
|
+
const safeThreadId = String(threadId || '')
|
|
253
|
+
const safeName = String(name || '')
|
|
254
|
+
if (!safeThreadId || !safeName) return Promise.reject(new Error('Codex MCP readiness needs a thread id and server name'))
|
|
255
|
+
const key = `${safeThreadId}\0${safeName}`
|
|
256
|
+
const current = this.mcpStartup.get(key)
|
|
257
|
+
if (current?.status === 'ready') return Promise.resolve(current)
|
|
258
|
+
if (current && (current.status === 'failed' || current.error || current.failureReason)) {
|
|
259
|
+
return Promise.reject(new Error(`Codex MCP server ${safeName} failed to start: ${current.error || current.failureReason || current.status}`))
|
|
260
|
+
}
|
|
261
|
+
if (this.mcpWaiters.has(key)) return this.mcpWaiters.get(key).promise
|
|
262
|
+
let timer = null
|
|
263
|
+
let resolveWaiter
|
|
264
|
+
let rejectWaiter
|
|
265
|
+
const promise = new Promise((resolve, reject) => {
|
|
266
|
+
resolveWaiter = resolve
|
|
267
|
+
rejectWaiter = reject
|
|
268
|
+
if (timeoutMs > 0) timer = setTimeout(() => {
|
|
269
|
+
this.mcpWaiters.delete(key)
|
|
270
|
+
reject(new Error(`Codex MCP server ${safeName} did not become ready within ${timeoutMs}ms`))
|
|
271
|
+
}, timeoutMs)
|
|
272
|
+
})
|
|
273
|
+
this.mcpWaiters.set(key, { promise, resolve: resolveWaiter, reject: rejectWaiter, timer })
|
|
274
|
+
return promise
|
|
275
|
+
}
|
|
276
|
+
|
|
224
277
|
async startTurn({ threadId, input, images, model, effort, approvalPolicy, collaborationMode } = {}) {
|
|
225
278
|
const result = await this.request('turn/start', {
|
|
226
279
|
threadId,
|
package/codex-session.mjs
CHANGED
|
@@ -352,7 +352,7 @@ function appServerItemForMapper(item) {
|
|
|
352
352
|
* @param {string} [o.providerConfig] optional -c overrides / provider block (M2: from the provider registry)
|
|
353
353
|
* @returns {{ sendTurn(text, options?), abort(), end(), readonly sessionId }}
|
|
354
354
|
*/
|
|
355
|
-
export function startCodexSession({ cwd, model, effort: initialEffort = 'high', resume, env, sandbox, mode = 'default', onEvent, providerConfig, terminalRolePrompt, rolePrompt, roomContext, mcpServers, prepareCwd = null, requestPermission, appServerGate = codexAppServerGate, appServerFactory = createCodexAppServer, spawnImpl = spawn }) {
|
|
355
|
+
export function startCodexSession({ cwd, model, effort: initialEffort = 'high', resume, env, sandbox, mode = 'default', onEvent, providerConfig, terminalRolePrompt, rolePrompt, roomContext, mcpServers, prepareCwd = null, requestPermission, appServerGate = codexAppServerGate, appServerFactory = createCodexAppServer, mcpHttpFactory = startCodexMcpHttp, spawnImpl = spawn }) {
|
|
356
356
|
let activeMode = CODEX_MODE_CONFIG[mode] ? mode : 'default'
|
|
357
357
|
let modeConfig = codexConfigForMode(activeMode)
|
|
358
358
|
sandbox = normalizeCodexSandbox(sandbox || modeConfig.sandbox)
|
|
@@ -424,7 +424,7 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
424
424
|
async function ensureMcpHttp() {
|
|
425
425
|
const thinkpool = mcpServers?.thinkpool
|
|
426
426
|
if (!thinkpool) return null
|
|
427
|
-
if (!mcpHttpPromise) mcpHttpPromise =
|
|
427
|
+
if (!mcpHttpPromise) mcpHttpPromise = mcpHttpFactory({ sdkServer: thinkpool })
|
|
428
428
|
return mcpHttpPromise
|
|
429
429
|
}
|
|
430
430
|
|
|
@@ -526,6 +526,12 @@ export function startCodexSession({ cwd, model, effort: initialEffort = 'high',
|
|
|
526
526
|
// independent ThinkPool sandbox / approval posture above.
|
|
527
527
|
config: { 'features.default_mode_request_user_input': true },
|
|
528
528
|
})
|
|
529
|
+
// App Server reports thread/start before its per-thread MCP clients finish
|
|
530
|
+
// initializing. Starting the first turn in that gap snapshots a toolset
|
|
531
|
+
// without ThinkPool even though the server becomes ready milliseconds later.
|
|
532
|
+
// Hold the turn boundary until the room MCP is actually ready; a timeout or
|
|
533
|
+
// startup failure drops into the required-MCP codex exec fallback below.
|
|
534
|
+
if (peer?.url) await appServer.waitForMcpServer({ threadId: sessionId, name: 'thinkpool' })
|
|
529
535
|
appServerThreadReady = true
|
|
530
536
|
pushMappedEvent({ type: 'thread.started', thread_id: sessionId })
|
|
531
537
|
return true
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"bundleVersion":
|
|
3
|
+
"bundleVersion": 5,
|
|
4
4
|
"contracts": [
|
|
5
5
|
{
|
|
6
6
|
"id": "room-coordination",
|
|
7
|
-
"version":
|
|
7
|
+
"version": 2,
|
|
8
8
|
"routes": [
|
|
9
9
|
{
|
|
10
10
|
"id": "room-awareness",
|
|
@@ -27,12 +27,16 @@
|
|
|
27
27
|
],
|
|
28
28
|
"impact": [
|
|
29
29
|
{"path": "bridge/cross-terminal.mjs"},
|
|
30
|
-
{"path": "bridge/bridge.mjs", "diffPattern": "read_terminal|list_sessions|read_session|post_to_terminal|post_to_session"}
|
|
30
|
+
{"path": "bridge/bridge.mjs", "diffPattern": "read_terminal|list_sessions|read_session|post_to_terminal|post_to_session"},
|
|
31
|
+
{"path": "bridge/codex-session.mjs", "diffPattern": "MCP|Mcp|mcp|read_terminal|list_sessions|read_session|post_to_terminal|post_to_session"},
|
|
32
|
+
{"path": "bridge/codex-app-server.mjs", "diffPattern": "MCP|Mcp|mcp"}
|
|
31
33
|
],
|
|
32
34
|
"evidence": [
|
|
33
35
|
{"path": "bridge/cross-terminal.mjs", "pattern": "read_terminal"},
|
|
34
36
|
{"path": "bridge/cross-terminal.mjs", "pattern": "post_to_session"},
|
|
35
|
-
{"path": "bridge/bridge.mjs", "pattern": "'list_sessions'"}
|
|
37
|
+
{"path": "bridge/bridge.mjs", "pattern": "'list_sessions'"},
|
|
38
|
+
{"path": "bridge/codex-session.mjs", "pattern": "waitForMcpServer"},
|
|
39
|
+
{"path": "bridge/codex-app-server.mjs", "pattern": "mcpServer/startupStatus/updated"}
|
|
36
40
|
]
|
|
37
41
|
},
|
|
38
42
|
{
|
|
@@ -161,9 +165,9 @@
|
|
|
161
165
|
},
|
|
162
166
|
{
|
|
163
167
|
"id": "design-workspace",
|
|
164
|
-
"version":
|
|
165
|
-
"interactionPrompt": "DESIGN EDITING MODEL: a trusted source-backed mockup card offers Edit in Design. That explicit action—not ordinary Preview or unrelated artifact delivery—arms a persistent room-level Design workspace, opens the artifact directly in editable mode, and adds Design · Page name
|
|
166
|
-
"turnReminder": "DESIGN ROUTE: authored HTML must produce a source-backed Thinkpool Design card with verified desktop and mobile renders. Edit in Design explicitly arms the persistent Design workspace and adds its virtual Design · Page Ensemble
|
|
168
|
+
"version": 3,
|
|
169
|
+
"interactionPrompt": "DESIGN EDITING MODEL: a trusted source-backed mockup card offers Edit in Design. That explicit action—not ordinary Preview or unrelated artifact delivery—arms a persistent room-level Design workspace, opens the artifact directly in editable mode, and adds Design · Page name beneath the producing terminal in its Ensemble row for both partners; it creates no terminal, agent runtime, or worker slot. The virtual lane can be closed from that Ensemble row without deleting the artifact. Once armed, the Design lane reopens the active artifact at its latest verified revision without another agent turn, history search, HTML/path request, or re-selection. Design is the default canvas there and Preview is only a temporary interaction mode. Direct text, supported movement, and selected-image changes auto-stage as optimistic drafts in the bounded changes queue; queued edits can be reopened, revised, or removed before Apply. Apply changes sends the ordered batch once to the producing lane, that lane edits the canonical authored HTML, and a successful desktop+mobile render advances the verified revision for the room. Never call a staged draft saved or live. A preview_capture card is visual evidence, not an editable Design artifact. For direction rounds, deliver every option as its own source-backed Design card; a multi-option gallery may accompany them for comparison but must never be the only editable artifact.",
|
|
170
|
+
"turnReminder": "DESIGN ROUTE: authored HTML must produce a source-backed Thinkpool Design card with verified desktop and mobile renders. Edit in Design explicitly arms the persistent Design workspace and adds its virtual Design · Page beneath the producing terminal in the Ensemble row; Preview alone does not arm it. For direction rounds, deliver every option as a separate source-backed Design card; gallery navigation is not a substitute for separately editable artifacts. Do not duplicate the card renders as inline PNGs. Use inline PNG evidence only when no interactive Design artifact is available.",
|
|
167
171
|
"impact": [
|
|
168
172
|
{"path": "src/pages/code/design/"},
|
|
169
173
|
{"path": "src/pages/code/structured.jsx", "diffPattern": "Edit in Design|openMockup|tp-mockup-view|sourceKnown"},
|