thinkpool-pair 0.7.264 → 0.7.265

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
@@ -60,6 +60,7 @@ import { canonicalRoomFilePath, waitForNativeImages } from './codex-images.mjs'
60
60
  import { createManagedLaneWorktree, removeManagedLaneWorktree } from './lane-worktree.mjs'
61
61
  import { commandOnPath } from './agent-detect.mjs'
62
62
  import { requiresSdkAdmission, sdkSmokePassed } from './sdk-admission.mjs'
63
+ import { hermesUserInputResponse } from './question-response.mjs'
63
64
 
64
65
  const STRUCTURED_MODES = new Set(['default', 'acceptEdits', 'plan', 'review', 'bypassPermissions'])
65
66
  import { FLOW_CONDUCTOR_PROMPT, FLOW_LANE_PROMPT, FLOW_CODEX_CONDUCTOR_PROMPT, FLOW_CODEX_LANE_PROMPT, buildConductorEnv, assembleCrossWaveContext, buildLanePrompt } from './flow-conductor.mjs'
@@ -2189,10 +2190,7 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
2189
2190
  answerFormat: 'hermes',
2190
2191
  autoResolutionMs: args.autoResolutionMs,
2191
2192
  })
2192
- const decision = result && typeof result === 'object' ? result.decision : result
2193
- const answers = result && typeof result === 'object' && result.answers && typeof result.answers === 'object' ? result.answers : {}
2194
- const status = String(decision || '').startsWith('answer:') ? 'answered' : decision === 'timeout' ? 'timed_out' : 'dismissed'
2195
- return { content: [{ type: 'text', text: JSON.stringify({ status, answers }) }] }
2193
+ return { content: [{ type: 'text', text: JSON.stringify(hermesUserInputResponse(result, questions)) }] }
2196
2194
  },
2197
2195
  )] : []),
2198
2196
  ...createViewportTools({ tool, z, manager: entry.viewport }),
package/codex-session.mjs CHANGED
@@ -31,6 +31,7 @@ import { buildCodexAppServerArgs, codexAppServerGate, createCodexAppServer } fro
31
31
  import { CodexEventMapper } from './codex-event-mapper.mjs'
32
32
  import { startCodexMcpHttp } from './codex-mcp-http.mjs'
33
33
  import { CODEX_THINKPOOL_FIRST_TURN_PREAMBLE, buildThinkPoolTurnGuidance, createRoomContextSelector, usesFullThinkPoolReminder } from './thinkpool-room-prompt.mjs'
34
+ import { questionAnswerResponse } from './question-response.mjs'
34
35
 
35
36
  const DEFAULT_SANDBOX = 'workspace-write'
36
37
  const SAFE_SANDBOXES = new Set(['read-only', 'workspace-write', 'danger-full-access'])
@@ -316,46 +317,8 @@ export function codexApprovalResponse(decision) {
316
317
  return { decision: decision === 'always' ? 'acceptForSession' : decision === 'allow' ? 'accept' : 'decline' }
317
318
  }
318
319
 
319
- function legacyQuestionAnswers(decision, questions) {
320
- if (typeof decision !== 'string' || !decision.startsWith('answer:')) return {}
321
- const text = decision.slice(7).trim()
322
- if (!text || !questions.length) return {}
323
- if (questions.length === 1) {
324
- const question = questions[0]
325
- const prefix = `${question.header || 'Q1'}: `
326
- const value = text.startsWith(prefix) ? text.slice(prefix.length) : text
327
- return { [String(question.id || 0)]: { answers: [value] } }
328
- }
329
- const answers = {}
330
- questions.forEach((question, index) => {
331
- const id = String(question.id || index)
332
- const marker = `${index ? '; ' : ''}${question.header || `Q${index + 1}`}: `
333
- const start = text.indexOf(marker)
334
- if (start < 0) return
335
- const valueStart = start + marker.length
336
- const nextQuestion = questions[index + 1]
337
- const nextMarker = nextQuestion ? `; ${nextQuestion.header || `Q${index + 2}`}: ` : null
338
- const end = nextMarker ? text.indexOf(nextMarker, valueStart) : -1
339
- const value = text.slice(valueStart, end < 0 ? undefined : end).trim()
340
- if (value) answers[id] = { answers: [value] }
341
- })
342
- return answers
343
- }
344
-
345
320
  export function codexUserInputResponse(result, questions = []) {
346
- const supplied = result && typeof result === 'object' ? result.answers : null
347
- const answers = {}
348
- if (supplied && typeof supplied === 'object') {
349
- const allowed = new Set(questions.map((question, index) => String(question?.id || index)))
350
- for (const [id, value] of Object.entries(supplied)) {
351
- if (!allowed.has(String(id)) || !Array.isArray(value?.answers)) continue
352
- const values = value.answers.map((answer) => String(answer)).filter(Boolean)
353
- if (values.length) answers[String(id)] = { answers: values }
354
- }
355
- }
356
- if (Object.keys(answers).length) return { answers }
357
- const decision = result && typeof result === 'object' ? result.decision : result
358
- return { answers: legacyQuestionAnswers(decision, questions) }
321
+ return questionAnswerResponse(result, questions)
359
322
  }
360
323
 
361
324
  function appServerItemForMapper(item) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.264",
3
+ "version": "0.7.265",
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": {
@@ -16,6 +16,7 @@
16
16
  "context-windows.mjs",
17
17
  "claude-session.mjs",
18
18
  "codex-session.mjs",
19
+ "question-response.mjs",
19
20
  "thinkpool-room-prompt.mjs",
20
21
  "thinkpool-prompt-contracts.mjs",
21
22
  "thinkpool-capabilities.json",
@@ -0,0 +1,52 @@
1
+ function legacyQuestionAnswers (decision, questions) {
2
+ if (typeof decision !== 'string' || !decision.startsWith('answer:')) return {}
3
+ const text = decision.slice(7).trim()
4
+ if (!text || !questions.length) return {}
5
+ if (questions.length === 1) {
6
+ const question = questions[0]
7
+ const prefix = `${question.header || 'Q1'}: `
8
+ const value = text.startsWith(prefix) ? text.slice(prefix.length) : text
9
+ return { [String(question.id || 0)]: { answers: [value] } }
10
+ }
11
+ const answers = {}
12
+ questions.forEach((question, index) => {
13
+ const id = String(question.id || index)
14
+ const marker = `${index ? '; ' : ''}${question.header || `Q${index + 1}`}: `
15
+ const start = text.indexOf(marker)
16
+ if (start < 0) return
17
+ const valueStart = start + marker.length
18
+ const nextQuestion = questions[index + 1]
19
+ const nextMarker = nextQuestion ? `; ${nextQuestion.header || `Q${index + 2}`}: ` : null
20
+ const end = nextMarker ? text.indexOf(nextMarker, valueStart) : -1
21
+ const value = text.slice(valueStart, end < 0 ? undefined : end).trim()
22
+ if (value) answers[id] = { answers: [value] }
23
+ })
24
+ return answers
25
+ }
26
+
27
+ // Codex and Hermes use different native tool protocols, but the room answer is
28
+ // one contract. Keep filtering, multi-select preservation, and legacy fallback
29
+ // in one pure adapter so the two runtimes cannot drift independently.
30
+ export function questionAnswerResponse (result, questions = []) {
31
+ const supplied = result && typeof result === 'object' ? result.answers : null
32
+ const answers = {}
33
+ if (supplied && typeof supplied === 'object') {
34
+ const allowed = new Set(questions.map((question, index) => String(question?.id || index)))
35
+ for (const [id, value] of Object.entries(supplied)) {
36
+ if (!allowed.has(String(id)) || !Array.isArray(value?.answers)) continue
37
+ const values = value.answers.map((answer) => String(answer)).filter(Boolean)
38
+ if (values.length) answers[String(id)] = { answers: values }
39
+ }
40
+ }
41
+ if (Object.keys(answers).length) return { answers }
42
+ const decision = result && typeof result === 'object' ? result.decision : result
43
+ return { answers: legacyQuestionAnswers(decision, questions) }
44
+ }
45
+
46
+ export function hermesUserInputResponse (result, questions = []) {
47
+ const decision = result && typeof result === 'object' ? result.decision : result
48
+ return {
49
+ status: String(decision || '').startsWith('answer:') ? 'answered' : decision === 'timeout' ? 'timed_out' : 'dismissed',
50
+ ...questionAnswerResponse(result, questions),
51
+ }
52
+ }