thinkpool-pair 0.7.276 → 0.7.278
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 +3 -3
- package/claude-session.mjs +12 -5
- package/codex-session.mjs +10 -1
- package/package.json +1 -1
- package/pair-control-authority.mjs +10 -0
- package/question-response.mjs +8 -2
- package/thinkpool-capabilities.json +3 -2
package/bridge.mjs
CHANGED
|
@@ -3124,8 +3124,8 @@ function pendingResolution(pending, payload = {}) {
|
|
|
3124
3124
|
const decision = payload.decision || 'deny'
|
|
3125
3125
|
return pending?.payload?.answerFormat === 'dispatch'
|
|
3126
3126
|
? { decision, dispatchFingerprint: payload.dispatchFingerprint || null, controlItemId: payload.controlItemId || null }
|
|
3127
|
-
: (pending?.payload?.answerFormat === 'codex' || pending?.payload?.answerFormat === 'hermes')
|
|
3128
|
-
? { decision, answers: payload.answers || {} }
|
|
3127
|
+
: (pending?.payload?.risk === 'ask' || pending?.payload?.answerFormat === 'codex' || pending?.payload?.answerFormat === 'hermes')
|
|
3128
|
+
? { decision, answers: payload.answers || {}, responder: payload.responder || null }
|
|
3129
3129
|
: decision
|
|
3130
3130
|
}
|
|
3131
3131
|
|
|
@@ -3946,7 +3946,7 @@ channel
|
|
|
3946
3946
|
if (p.timer) clearTimeout(p.timer)
|
|
3947
3947
|
s.pending.delete(payload.id)
|
|
3948
3948
|
s.permNotifier?.resolve(payload.id)
|
|
3949
|
-
p.resolve(pendingResolution(p, { ...payload, decision: authority.decision, answers: authority.answers }))
|
|
3949
|
+
p.resolve(pendingResolution(p, { ...payload, decision: authority.decision, answers: authority.answers, responder: authority.responder }))
|
|
3950
3950
|
announce()
|
|
3951
3951
|
})
|
|
3952
3952
|
})
|
package/claude-session.mjs
CHANGED
|
@@ -99,18 +99,25 @@ export function autoAllow({ toolName, input, mode = 'default', alwaysAllow = new
|
|
|
99
99
|
// denies (PreToolUse can't inject a tool_result) and puts the human's pick in
|
|
100
100
|
// the deny reason, which IS what the model receives. Exported so the feedback
|
|
101
101
|
// contract is locked in a unit test (mock requestPermission → this output).
|
|
102
|
-
// `decision` is the requestPermission return: 'answer:<pick>'
|
|
103
|
-
//
|
|
104
|
-
// as "no selection".
|
|
102
|
+
// `decision` is the requestPermission return: either the legacy 'answer:<pick>'
|
|
103
|
+
// string or { decision, responder } from the durable room control. Anything else
|
|
104
|
+
// (including '' / dismissal / a broken path) is treated as "no selection".
|
|
105
105
|
export function askUserQuestionHookOutput(decision) {
|
|
106
|
-
const
|
|
106
|
+
const rawDecision = decision && typeof decision === 'object' ? decision.decision : decision
|
|
107
|
+
const ans = (typeof rawDecision === 'string' && rawDecision.startsWith('answer:')) ? rawDecision.slice(7) : ''
|
|
108
|
+
const responder = decision && typeof decision === 'object' ? decision.responder : null
|
|
109
|
+
const answeredBy = typeof responder?.name === 'string' && responder.name.trim()
|
|
110
|
+
? responder.name.trim()
|
|
111
|
+
: typeof responder?.id === 'string' && responder.id
|
|
112
|
+
? `room member ${responder.id}`
|
|
113
|
+
: 'The user'
|
|
107
114
|
return {
|
|
108
115
|
continue: true,
|
|
109
116
|
hookSpecificOutput: {
|
|
110
117
|
hookEventName: 'PreToolUse',
|
|
111
118
|
permissionDecision: 'deny',
|
|
112
119
|
permissionDecisionReason: ans
|
|
113
|
-
?
|
|
120
|
+
? `${answeredBy} answered in the ThinkPool room — ${ans}. Treat this as their selection and continue; do not call AskUserQuestion again for the same question.`
|
|
114
121
|
: 'The user dismissed the question in the ThinkPool room without selecting. Ask in plain prose, or proceed with a sensible default.',
|
|
115
122
|
},
|
|
116
123
|
}
|
package/codex-session.mjs
CHANGED
|
@@ -267,7 +267,16 @@ export function buildCodexExecArgs({ sessionId, model, effort, sandbox, approval
|
|
|
267
267
|
// Both fresh `exec` and `exec resume` accept repeatable --image flags. Only
|
|
268
268
|
// bridge-derived absolute paths reach this layer; relative values are ignored
|
|
269
269
|
// so a future caller cannot silently change the child's path interpretation.
|
|
270
|
-
|
|
270
|
+
let hasNativeImages = false
|
|
271
|
+
for (const imagePath of images) {
|
|
272
|
+
if (!path.isAbsolute(String(imagePath || ''))) continue
|
|
273
|
+
args.push('--image', String(imagePath))
|
|
274
|
+
hasNativeImages = true
|
|
275
|
+
}
|
|
276
|
+
// Codex CLI 0.144.1 defines --image as a variadic option. Without an option
|
|
277
|
+
// terminator it consumes the positional prompt (and, on resume, the thread
|
|
278
|
+
// id), then exits because stdin is intentionally /dev/null in the bridge.
|
|
279
|
+
if (hasNativeImages) args.push('--')
|
|
271
280
|
if (sessionId) args.push(sessionId)
|
|
272
281
|
args.push(String(prompt ?? ''))
|
|
273
282
|
return args
|
package/package.json
CHANGED
|
@@ -34,6 +34,15 @@ export function verifyPairControlDeliveryAuthority ({ item, attempts, payload, r
|
|
|
34
34
|
event?.actor_id === item.resolved_by && Number(event.item_version) === Number(item.version) &&
|
|
35
35
|
event.correlation_key === payload.attemptKey && event.payload?.data?.transport === 'code-perm') : []
|
|
36
36
|
if (matching.length !== 1) return denied('delivery_attempt_unverified')
|
|
37
|
+
const durableResponder = resolution.responder
|
|
38
|
+
const responder = durableResponder && durableResponder.id === item.resolved_by
|
|
39
|
+
? {
|
|
40
|
+
id: item.resolved_by,
|
|
41
|
+
name: typeof durableResponder.name === 'string' && durableResponder.name.trim()
|
|
42
|
+
? durableResponder.name.trim().slice(0, 120)
|
|
43
|
+
: null,
|
|
44
|
+
}
|
|
45
|
+
: { id: item.resolved_by, name: null }
|
|
37
46
|
return Object.freeze({
|
|
38
47
|
ok: true,
|
|
39
48
|
code: 'durable_delivery_authorized',
|
|
@@ -41,6 +50,7 @@ export function verifyPairControlDeliveryAuthority ({ item, attempts, payload, r
|
|
|
41
50
|
// App Server input is recovered from the durable resolution only. The public
|
|
42
51
|
// broadcast intentionally remains a wake-up hint and cannot supply answers.
|
|
43
52
|
answers: resolution.answers && typeof resolution.answers === 'object' ? resolution.answers : {},
|
|
53
|
+
responder: Object.freeze(responder),
|
|
44
54
|
itemId: item.id,
|
|
45
55
|
version: Number(item.version),
|
|
46
56
|
attemptKey: payload.attemptKey,
|
package/question-response.mjs
CHANGED
|
@@ -38,9 +38,15 @@ export function questionAnswerResponse (result, questions = []) {
|
|
|
38
38
|
if (values.length) answers[String(id)] = { answers: values }
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
|
|
41
|
+
const responder = result?.responder && typeof result.responder === 'object'
|
|
42
|
+
? {
|
|
43
|
+
id: typeof result.responder.id === 'string' ? result.responder.id : null,
|
|
44
|
+
name: typeof result.responder.name === 'string' ? result.responder.name : null,
|
|
45
|
+
}
|
|
46
|
+
: null
|
|
47
|
+
if (Object.keys(answers).length) return { answers, ...(responder ? { responder } : {}) }
|
|
42
48
|
const decision = result && typeof result === 'object' ? result.decision : result
|
|
43
|
-
return { answers: legacyQuestionAnswers(decision, questions) }
|
|
49
|
+
return { answers: legacyQuestionAnswers(decision, questions), ...(responder ? { responder } : {}) }
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
export function hermesUserInputResponse (result, questions = []) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"bundleVersion":
|
|
3
|
+
"bundleVersion": 6,
|
|
4
4
|
"contracts": [
|
|
5
5
|
{
|
|
6
6
|
"id": "room-coordination",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
"id": "room-question",
|
|
68
|
-
"version":
|
|
68
|
+
"version": 2,
|
|
69
69
|
"routes": [
|
|
70
70
|
{
|
|
71
71
|
"id": "room-question",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"prompt": "When a missing choice genuinely blocks useful progress, use request_user_input so the question is answerable in the room; otherwise make a safe in-scope assumption and continue."
|
|
75
75
|
}
|
|
76
76
|
],
|
|
77
|
+
"resultContract": "A resolved room question returns the selected answers plus the durable responder identity {id, name}; the id must match the pair-control row's resolved_by authority.",
|
|
77
78
|
"impact": [
|
|
78
79
|
{"path": "bridge/bridge.mjs", "diffPattern": "request_user_input"},
|
|
79
80
|
{"path": "bridge/claude-session.mjs", "diffPattern": "AskUserQuestion|request_user_input"},
|