thinkpool-pair 0.7.229 → 0.7.231

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
@@ -66,7 +66,7 @@ import { writeLaneArtifact, digestSlice, appendDigest, resumeLane } from './flow
66
66
  import { createFlowWorktree, worktreeSpec } from './flow-worktree.mjs'
67
67
  import { startPreview, stopAllPreviews, previews } from './flow-preview.mjs'
68
68
  import { ViewportManager, createViewportTools, sharedViewportBrowser } from './viewport.mjs'
69
- import { deleteDesignAssetDraft, designPrompt, materializeDesignAsset, resolveDesignSource, resolveManifestDesignSource, restoreDesignSources, syncRealtimeAuth, validateDesignBatchRequest } from './design-edit.mjs'
69
+ import { deleteDesignAssetDraft, designPrompt, designTranscript, materializeDesignAsset, resolveDesignSource, resolveManifestDesignSource, restoreDesignSources, syncRealtimeAuth, validateDesignBatchRequest } from './design-edit.mjs'
70
70
  // FL-M9 — per-lane preview servers leak (one per done lane, never stopped until shutdown).
71
71
  // Lane previews are keyed `lane:<flowId>:<laneId>`; stop a whole flow's set when it assembles
72
72
  // (the assembled preview supersedes them) or when a lane is reverted.
@@ -1283,19 +1283,13 @@ function pumpDesign(term) {
1283
1283
  const active = { ...next, resultOk: false, proof: null, timer: null }
1284
1284
  designActive.set(term, active)
1285
1285
  designStatus({ previewId: next.record.previewId, requestId: next.request.cid, state: 'applying' })
1286
- // Safe transcript summary: never include host paths or the full locator packet.
1287
- const action = next.request.mode === 'text' ? 'replace text'
1288
- : next.request.mode === 'move' ? 'move element'
1289
- : next.request.mode === 'image' ? 'replace image'
1290
- : next.request.intent
1291
- const batchCount = next.request.mode === 'batch' ? next.request.edits.length : 0
1286
+ // Complete room-visible instructions, without host paths or locator packets.
1292
1287
  const visible = {
1293
1288
  kind: 'you',
1294
- text: batchCount
1295
- ? `Design batch · ${batchCount} pooled edit${batchCount === 1 ? '' : 's'}`
1296
- : `Design edit · ${next.request.target.name || next.request.target.tag}: ${action}`,
1289
+ text: designTranscript(next.request, { restore: !!next.restoreRecord }),
1297
1290
  cid: next.request.cid,
1298
1291
  by: next.by,
1292
+ design: true,
1299
1293
  }
1300
1294
  stampEvent(visible); pushLog(lane, visible); bcast('code-event', { term, evt: visible })
1301
1295
  try { lane.session.sendTurn(designPrompt({ record: next.record, request: next.request, by: next.by, restore: !!next.restoreRecord, priorRecord: next.restoreRecord })) }
@@ -4038,7 +4032,8 @@ designChannel
4038
4032
  materializedDrafts.push(edit.asset.path)
4039
4033
  }
4040
4034
  await Promise.all(materializedDrafts.map((assetPath) => deleteDesignAssetDraft({ webBase: WEB_BASE, token: codeAuthToken, room, path: assetPath })))
4041
- queue.push({ ...verdict, by: 'A room member' })
4035
+ const by = String(payload?.by || 'A room member').replace(/\0/g, '').trim().slice(0, 120) || 'A room member'
4036
+ queue.push({ ...verdict, by })
4042
4037
  const batchCount = verdict.request.mode === 'batch' ? verdict.request.edits.length : 1
4043
4038
  designStatus({ previewId, requestId, state: 'queued', message: `${batchCount} edit${batchCount === 1 ? '' : 's'} pooled to the producing lane.` })
4044
4039
  pumpDesign(term)
@@ -4061,7 +4056,8 @@ designChannel
4061
4056
  designStatus({ previewId, requestId, state: 'failed', message: 'Too many Design edits are queued for this lane.' })
4062
4057
  return
4063
4058
  }
4064
- queue.push({ record, restoreRecord: restore.priorRecord, request: { previewId, revision: record.revision, mode: 'restore', intent: 'restore previous verified revision', target: restore.request.target || { selector: 'html', tag: 'artifact', name: `${restore.request.edits?.length || 1} pooled edits` }, cid: requestId }, by: 'A room member' })
4059
+ const by = String(payload?.by || 'A room member').replace(/\0/g, '').trim().slice(0, 120) || 'A room member'
4060
+ queue.push({ record, restoreRecord: restore.priorRecord, request: { previewId, revision: record.revision, mode: 'restore', intent: 'restore previous verified revision', target: restore.request.target || { selector: 'html', tag: 'artifact', name: `${restore.request.edits?.length || 1} pooled edits` }, cid: requestId }, by })
4065
4061
  designStatus({ previewId, requestId, state: 'queued', message: 'Restore queued in the producing lane.' })
4066
4062
  pumpDesign(record.term)
4067
4063
  })
@@ -59,7 +59,14 @@ export function codexAppServerGate({ env = process.env, execFile = execFileSync
59
59
  export function buildCodexAppServerArgs({ providerConfig, mcpUrl, writableDirs = [] } = {}) {
60
60
  const args = ['app-server', '--stdio']
61
61
  if (providerConfig) args.push('-c', providerConfig)
62
- if (mcpUrl) args.push('-c', `mcp_servers.thinkpool.url=${JSON.stringify(mcpUrl)}`)
62
+ if (mcpUrl) {
63
+ args.push('-c', `mcp_servers.thinkpool.url=${JSON.stringify(mcpUrl)}`)
64
+ // The room prompt promises spawn/read/close_terminal as native room tools.
65
+ // Fail startup instead of silently running a tool-less Codex turn when the
66
+ // private loopback MCP handshake is unavailable. Existing MCP approval
67
+ // policy still owns the room permission card for each call.
68
+ args.push('-c', 'mcp_servers.thinkpool.required=true')
69
+ }
63
70
  if (writableDirs.length) args.push('-c', `sandbox_workspace_write.writable_roots=${JSON.stringify(writableDirs)}`)
64
71
  return args
65
72
  }
package/codex-session.mjs CHANGED
@@ -248,7 +248,13 @@ export function buildCodexExecArgs({ sessionId, model, effort, sandbox, approval
248
248
  // accept --add-dir itself. Supply these only when creating the thread.
249
249
  if (!sessionId) for (const dir of writableDirs) args.push('--add-dir', dir)
250
250
  // Per-lane override only: never mutate the person's global Codex config.
251
- if (mcpUrl) args.push('-c', `mcp_servers.thinkpool.url=${JSON.stringify(mcpUrl)}`)
251
+ if (mcpUrl) {
252
+ args.push('-c', `mcp_servers.thinkpool.url=${JSON.stringify(mcpUrl)}`)
253
+ // Room tools are part of the ThinkPool execution contract, not optional
254
+ // enrichment. A failed handshake must abort this invocation so Codex
255
+ // cannot continue after spawn_terminal vanished from its callable set.
256
+ args.push('-c', 'mcp_servers.thinkpool.required=true')
257
+ }
252
258
  // Both fresh `exec` and `exec resume` accept repeatable --image flags. Only
253
259
  // bridge-derived absolute paths reach this layer; relative values are ignored
254
260
  // so a future caller cannot silently change the child's path interpretation.
package/design-edit.mjs CHANGED
@@ -223,6 +223,32 @@ function designAction(request) {
223
223
  return `Apply this visual change to only the selected element unless the instruction explicitly requires its immediate context:\n${request.intent}`
224
224
  }
225
225
 
226
+ const designModeLabel = (mode) => ({ text: 'Text', prompt: 'Prompt', move: 'Move', image: 'Image' })[mode] || 'Edit'
227
+
228
+ function visibleDesignAction(request) {
229
+ if (request.mode === 'text') return `Replacement text:\n${clean(request.intent, MAX_TEXT)}`
230
+ if (request.mode === 'prompt') return `Instruction:\n${clean(request.intent, MAX_INTENT)}`
231
+ if (request.mode === 'move') return `Move from position ${request.movement.fromIndex + 1} to position ${request.movement.toIndex + 1}.`
232
+ if (request.mode === 'image') return `Replacement image: ${clean(request.asset?.name || 'uploaded image', 240)}`
233
+ return ''
234
+ }
235
+
236
+ // Complete, room-safe transcript text for the visible Design dispatch. Keep the
237
+ // user instructions and order; omit host-only paths and locator packets.
238
+ export function designTranscript(request, { restore = false } = {}) {
239
+ if (restore) return 'Thinkpool Design · restore the previous verified revision'
240
+ const edits = request?.mode === 'batch' ? request.edits : [request]
241
+ const valid = Array.isArray(edits) ? edits.filter(Boolean).slice(0, MAX_BATCH_EDITS) : []
242
+ const heading = request?.mode === 'batch'
243
+ ? `Thinkpool Design · ${valid.length} pooled edit${valid.length === 1 ? '' : 's'}`
244
+ : 'Thinkpool Design · 1 edit'
245
+ const details = valid.map((edit, index) => {
246
+ const target = clean(edit.target?.name || edit.target?.tag || 'element', 240)
247
+ return `${index + 1}. ${designModeLabel(edit.mode)} · ${target}\n${visibleDesignAction(edit)}`
248
+ })
249
+ return [heading, ...details].join('\n\n')
250
+ }
251
+
226
252
  export function designPrompt({ record, request, by, restore = false, priorRecord = null }) {
227
253
  const batch = request.mode === 'batch' ? request.edits : null
228
254
  const work = restore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.229",
3
+ "version": "0.7.231",
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": {