thinkpool-pair 0.6.0 → 0.6.2
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 +21 -2
- package/claude-session.mjs +11 -3
- package/package.json +1 -1
package/bridge.mjs
CHANGED
|
@@ -124,6 +124,10 @@ const headless = argv.includes('--headless')
|
|
|
124
124
|
// relay. Default OFF — the PTY path is untouched. Only applies to `claude`.
|
|
125
125
|
// Spec: docs/specs/2026-06-11-code-structured-reader.md
|
|
126
126
|
const STRUCTURED = process.env.TP_STRUCTURED === '1' || argv.includes('--structured')
|
|
127
|
+
// Claude Code runs as a STRUCTURED Agent-SDK session by default now (the picked
|
|
128
|
+
// design — structured reader, risk-tiered permissions). Other CLIs keep the PTY
|
|
129
|
+
// relay. TP_PTY=1 forces claude back to the raw PTY if ever needed.
|
|
130
|
+
const wantStructured = (cmd) => /(^|[/\\])claude$/.test(cmd || '') && process.env.TP_PTY !== '1'
|
|
127
131
|
const installedAgents = KNOWN_AGENTS.filter(a => onPath(a.cmd))
|
|
128
132
|
const dashIdx = argv.indexOf('--')
|
|
129
133
|
// Own flags are only read from BEFORE `--`; after it, every token belongs
|
|
@@ -383,6 +387,11 @@ channel
|
|
|
383
387
|
const agent = KNOWN_AGENTS.find(a => a.cmd === payload.cmd)
|
|
384
388
|
if (agent?.resume) { try { if (agent.resume.probe()) args = [...agent.resume.args] } catch { /* fresh */ } }
|
|
385
389
|
}
|
|
390
|
+
if (wantStructured(payload.cmd)) {
|
|
391
|
+
openStructured({ id: payload.id })
|
|
392
|
+
process.stderr.write(`\n ◆ web opened a structured "${payload.cmd}" session.\n`)
|
|
393
|
+
return
|
|
394
|
+
}
|
|
386
395
|
openTerm({ id: payload.id, cmd: payload.cmd, args })
|
|
387
396
|
process.stderr.write(`\n ◆ web opened a "${payload.cmd}"${args.length ? ' (continue)' : ''} terminal (headless).\n`)
|
|
388
397
|
})
|
|
@@ -435,7 +444,17 @@ channel
|
|
|
435
444
|
})
|
|
436
445
|
.on('broadcast', { event: 'code-turn' }, ({ payload }) => {
|
|
437
446
|
const s = payload?.term && sessions.get(payload.term)
|
|
438
|
-
if (s && payload.text != null)
|
|
447
|
+
if (s && payload.text != null) {
|
|
448
|
+
s.session.sendTurn(payload.text)
|
|
449
|
+
// Echo the turn so BOTH readers (and late joiners, via the log) show who
|
|
450
|
+
// said what — UNLESS silent (an @pool synthesis, which renders as its own
|
|
451
|
+
// 'pool' line). The sender rendered it optimistically; partner gets this.
|
|
452
|
+
if (!payload.silent) {
|
|
453
|
+
const evt = { kind: 'you', text: payload.text, cid: payload.cid, by: payload.by }
|
|
454
|
+
s.log.push(evt); if (s.log.length > STRUCTURED_LOG_MAX) s.log.shift()
|
|
455
|
+
bcast('code-event', { term: payload.term, evt })
|
|
456
|
+
}
|
|
457
|
+
}
|
|
439
458
|
})
|
|
440
459
|
.on('broadcast', { event: 'code-perm' }, ({ payload }) => {
|
|
441
460
|
const s = payload?.term && sessions.get(payload.term)
|
|
@@ -456,7 +475,7 @@ channel
|
|
|
456
475
|
channel.track({ name, role: 'bridge' })
|
|
457
476
|
if (attachedCmd && !terms.size && !sessions.size) {
|
|
458
477
|
// Claude + structured mode → Agent SDK session; everything else → PTY.
|
|
459
|
-
if (
|
|
478
|
+
if (wantStructured(attachedCmd)) openStructured({ id: randomUUID() })
|
|
460
479
|
else openTerm({ id: randomUUID(), cmd: attachedCmd, args: attachedArgs, attached: true })
|
|
461
480
|
}
|
|
462
481
|
announce()
|
package/claude-session.mjs
CHANGED
|
@@ -18,7 +18,10 @@ import { query } from '@anthropic-ai/claude-agent-sdk'
|
|
|
18
18
|
// ── risk classification — the accent/danger tier of the permission card ──
|
|
19
19
|
// low (read-only) · medium (writes/runs) · network (leaves the machine) ·
|
|
20
20
|
// high (destructive, deny-first). See the permission spec + mockups.
|
|
21
|
-
|
|
21
|
+
// Any `rm`/`rmdir` with an argument is destructive (a bare `rm NOTES.md`
|
|
22
|
+
// deletes just as permanently as `rm -rf`). Plus force-push, hard reset,
|
|
23
|
+
// clean -f, DROP, mkfs/dd, sudo, /dev redirects, recursive chmod/chown, etc.
|
|
24
|
+
const DESTRUCTIVE = /\brm\s+\S|\brmdir\s+\S|\bgit\s+(push\s+(-f|--force)|reset\s+--hard|clean\s+-[a-z]*f)|\bdrop\s+(table|database)\b|\b(mkfs|dd)\b|\bsudo\b|>\s*\/dev\/|\bchmod\s+-R|\bchown\s+-R|\bkillall\b|\btruncate\b/i
|
|
22
25
|
const READONLY_TOOLS = new Set(['Read', 'Grep', 'Glob', 'NotebookRead', 'TodoRead', 'LS'])
|
|
23
26
|
const NETWORK_TOOLS = new Set(['WebFetch', 'WebSearch'])
|
|
24
27
|
const WRITE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'TodoWrite'])
|
|
@@ -96,12 +99,17 @@ export function startClaudeSession({ cwd, model, resume, onEvent, requestPermiss
|
|
|
96
99
|
try {
|
|
97
100
|
decision = await requestPermission?.({ id: randomUUID(), toolName, input: toolInput, risk }) ?? 'allow'
|
|
98
101
|
} catch { decision = 'deny' } // a broken permission path must fail safe (deny)
|
|
102
|
+
// On deny, permissionDecisionReason IS what the model receives as the
|
|
103
|
+
// tool error — make it a real instruction, not an opaque tag.
|
|
104
|
+
const denied = decision === 'deny'
|
|
99
105
|
return {
|
|
100
106
|
continue: true,
|
|
101
107
|
hookSpecificOutput: {
|
|
102
108
|
hookEventName: 'PreToolUse',
|
|
103
|
-
permissionDecision:
|
|
104
|
-
permissionDecisionReason:
|
|
109
|
+
permissionDecision: denied ? 'deny' : 'allow',
|
|
110
|
+
permissionDecisionReason: denied
|
|
111
|
+
? 'Denied by the user in the ThinkPool room. Do not retry this tool — ask what to do instead.'
|
|
112
|
+
: 'Approved in the ThinkPool room.',
|
|
105
113
|
},
|
|
106
114
|
}
|
|
107
115
|
}
|