thinkpool-pair 0.7.10 → 0.7.12
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 +18 -2
- package/claude-session.mjs +13 -0
- package/package.json +1 -1
- package/session-store.mjs +12 -0
package/bridge.mjs
CHANGED
|
@@ -36,7 +36,7 @@ import { spawn } from 'node:child_process'
|
|
|
36
36
|
import { randomUUID } from 'node:crypto'
|
|
37
37
|
import { createClient } from '@supabase/supabase-js'
|
|
38
38
|
import { startClaudeSession } from './claude-session.mjs'
|
|
39
|
-
import { saveSession, flushSession, loadLatest, canResume } from './session-store.mjs'
|
|
39
|
+
import { saveSession, flushSession, loadLatest, canResume, loadPtyId, savePtyId } from './session-store.mjs'
|
|
40
40
|
|
|
41
41
|
// Public client creds (the same anon values the web app ships — safe to embed).
|
|
42
42
|
// Override with TP_SUPABASE_URL / TP_SUPABASE_ANON if you ever need to.
|
|
@@ -205,6 +205,15 @@ const askYesNo = (q, defaultYes = true) => new Promise((resolve) => {
|
|
|
205
205
|
let attachedCmd = null, attachedArgs = [], continuing = false
|
|
206
206
|
if (dashIdx >= 0) {
|
|
207
207
|
;[attachedCmd, ...attachedArgs] = argv.slice(dashIdx + 1)
|
|
208
|
+
// A token after `--` that's itself a flag (e.g. `-- --headless`) is a
|
|
209
|
+
// mis-placed own-flag, not a command to share — own-flags are already parsed
|
|
210
|
+
// from the full argv above, regardless of `--` position. Refuse to treat it
|
|
211
|
+
// as a command, else the subscribe handler auto-opens a junk PTY literally
|
|
212
|
+
// running `--headless` that immediately goes dormant (Maxbridge Main, 2026-06-17).
|
|
213
|
+
if (attachedCmd && attachedCmd.startsWith('-')) {
|
|
214
|
+
process.stderr.write(`\n ◇ ignoring "${attachedCmd}" after \`--\` — that's a flag, not a command to share.\n`)
|
|
215
|
+
attachedCmd = null; attachedArgs = []
|
|
216
|
+
}
|
|
208
217
|
} else if (!headless) {
|
|
209
218
|
if (installedAgents.length === 0) {
|
|
210
219
|
console.error(`\n No known coding-agent CLI found on your PATH.\n Install one (claude / codex / gemini / aider / cursor-agent / opencode …)\n or share a specific command: npx thinkpool-pair ${room} -- <your-command>\n`)
|
|
@@ -774,7 +783,14 @@ channel
|
|
|
774
783
|
if (prev && (prev.log?.length || prev.sessionId)) openStructured({ id: prev.id, resume: canResume(prev) ? prev.sessionId : undefined, log: prev.log, commands: prev.commands, mode: prev.mode })
|
|
775
784
|
else openStructured({ id: randomUUID() })
|
|
776
785
|
}
|
|
777
|
-
else
|
|
786
|
+
else {
|
|
787
|
+
// Reuse the prior auto-opened PTY id across restarts so a reconnecting
|
|
788
|
+
// bridge re-attaches to the SAME terminal tab instead of breeding a
|
|
789
|
+
// fresh dormant "Terminal N" on every reconnect.
|
|
790
|
+
const ptyId = loadPtyId(room) || randomUUID()
|
|
791
|
+
savePtyId(room, ptyId)
|
|
792
|
+
openTerm({ id: ptyId, cmd: startCmd, args: attachedArgs, attached: !autoAgent })
|
|
793
|
+
}
|
|
778
794
|
}
|
|
779
795
|
announce()
|
|
780
796
|
process.stderr.write(headless
|
package/claude-session.mjs
CHANGED
|
@@ -162,6 +162,19 @@ export function startClaudeSession({ cwd, model, resume, mode: initialMode = 'de
|
|
|
162
162
|
// (deny → stay planning). On approval we flip the SDK permission mode so
|
|
163
163
|
// subsequent tools actually execute.
|
|
164
164
|
if (toolName === 'ExitPlanMode') {
|
|
165
|
+
// GUARANTEE: a plan card only ever appears when the room is ACTUALLY in Plan
|
|
166
|
+
// mode (the user pressed ⇧⇥ → Plan). If the agent reaches ExitPlanMode while
|
|
167
|
+
// the room is in any other mode (default / acceptEdits / bypassPermissions),
|
|
168
|
+
// it entered plan mode on its own — the user never asked. Don't surface an
|
|
169
|
+
// unsolicited "PLAN READY" card that blocks them; re-assert the room's real
|
|
170
|
+
// mode and let the work proceed. This is the backstop for plan-mode leaking
|
|
171
|
+
// in regardless of source (sticky localStorage, SDK default, a host-global
|
|
172
|
+
// brainstorm nudge): in a ThinkPool room, plan is opt-in, never imposed.
|
|
173
|
+
if (mode !== 'plan') {
|
|
174
|
+
scheduleSdkMode(mode) // snap the SDK back out of plan, into the real mode
|
|
175
|
+
emit({ kind: 'mode', mode }) // keep the room chip honest
|
|
176
|
+
return { continue: true, hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow', permissionDecisionReason: `The ThinkPool room is in ${mode} mode, not Plan — the user did not ask for a plan. Do NOT call ExitPlanMode; proceed and make the changes directly. Only present a plan if the user switches the room to Plan mode or explicitly asks.` } }
|
|
177
|
+
}
|
|
165
178
|
let choice = 'keep'
|
|
166
179
|
try { choice = await requestPermission?.({ id: randomUUID(), toolName, input: toolInput, risk: 'plan', plan: toolInput?.plan || '' }) ?? 'keep' }
|
|
167
180
|
catch { choice = 'keep' }
|
package/package.json
CHANGED
package/session-store.mjs
CHANGED
|
@@ -55,3 +55,15 @@ export function loadLatest(room) {
|
|
|
55
55
|
export function canResume(rec) {
|
|
56
56
|
return !!(rec && rec.sessionId && rec.savedAt && (Date.now() - rec.savedAt) < RESUME_MAX_AGE_MS)
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
// Last PTY terminal id auto-opened for an attached command. A reconnecting
|
|
60
|
+
// bridge reuses it so it re-attaches to the SAME terminal tab instead of
|
|
61
|
+
// minting a fresh UUID and breeding a new dormant "Terminal N" each restart.
|
|
62
|
+
// Stored as a dotfile (NOT *.json) so it never lands in listRecs/loadLatest.
|
|
63
|
+
const ptyFile = (room) => path.join(dir(room), '.pty-id')
|
|
64
|
+
export function loadPtyId(room) {
|
|
65
|
+
try { return fs.readFileSync(ptyFile(room), 'utf8').trim() || null } catch { return null }
|
|
66
|
+
}
|
|
67
|
+
export function savePtyId(room, id) {
|
|
68
|
+
try { ensureDir(room); fs.writeFileSync(ptyFile(room), String(id)) } catch { /* noop */ }
|
|
69
|
+
}
|