thinkpool-pair 0.7.334 → 0.7.335
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/package.json +2 -1
- package/terminal-name.mjs +74 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thinkpool-pair",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.335",
|
|
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": {
|
|
@@ -113,6 +113,7 @@
|
|
|
113
113
|
"@anthropic-ai/claude-agent-sdk": "^0.3.198",
|
|
114
114
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
115
115
|
"@supabase/supabase-js": "^2.45.0",
|
|
116
|
+
"compromise": "14.16.0",
|
|
116
117
|
"yaml": "2.9.0",
|
|
117
118
|
"zod": "^4.4.3"
|
|
118
119
|
},
|
package/terminal-name.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { nativeClaudeProviderAccessFailed } from './cross-terminal.mjs'
|
|
2
|
+
import nlp from 'compromise/two'
|
|
2
3
|
|
|
3
4
|
const SKIP = new Set([
|
|
4
5
|
'a', 'an', 'and', 'are', 'at', 'be', 'can', 'could', 'for', 'from', 'how', 'i',
|
|
@@ -18,12 +19,66 @@ const NOISE = /^(?:context|background|for reference|here(?:'s| is)|note|current(
|
|
|
18
19
|
const EXPLANATION = /^(?:because|cause|since|so that|this is because)\b/i
|
|
19
20
|
const CONSTRAINT = /^(?:users? can still|keep|must|never|should|without)\b/i
|
|
20
21
|
const DEPENDENT_FRAGMENT = /^(?:after|before|by|during|for|from|in|instead(?: of)?|on|through|until|with|without)\b/i
|
|
21
|
-
const INFORMATION_REQUEST = /^(?:which|what|who|where|when|why|how)\b/i
|
|
22
|
-
const NEGATIVE_PREFERENCE = /^(?:(?:i|we)\s+)?(?:do not|don't|dont|would not|wouldn't|won't|wont)\s+(?:(?:want|wanna|need)(?:\s+to)?|use|include|choose)\b|^(?:(?:i|we)\s+)?(?:want|wanna|need)(?:\s+to)?\s+avoid\b/i
|
|
23
22
|
const ISSUE = /\b(?:broken|buggy|crash(?:es|ed|ing)?|duplicate|error|fail(?:s|ed|ing|ure)?|flash(?:es|ed|ing)?|missing|no animation|not working|out of (?:scrollable )?view|stuck|wrong)\b/i
|
|
24
23
|
const REQUEST = /^(?:please\s+)?(?:can|could|would|will)\s+(?:we|you)\b|^(?:please\s+)?(?:how about|let's|let us|we need to|i want you to)\b/i
|
|
25
24
|
const SECRET = /(?<![\p{L}\p{N}])(?:sk-(?:proj-)?[a-z0-9_-]{8,}|gsk_[a-z0-9_-]{8,}|xox[baprs]-[a-z0-9_-]{8,}|gh[pousr]_[a-z0-9_-]{8,}|glpat-[a-z0-9_-]{8,}|npm_[a-z0-9]{24,}|(?:sk|rk)_(?:live|test)_[a-z0-9]{8,}|whsec_[a-z0-9]{8,}|AIza[a-z0-9_-]{8,}|AKIA[A-Z0-9]{12,}|bearer\s+[a-z0-9._-]{8,}|eyJ[a-z0-9_-]{8,}\.[a-z0-9_-]{8,}\.[a-z0-9_-]{8,})(?![\p{L}\p{N}])/giu
|
|
26
25
|
const HANDOFF_MARKER = /^---\s*(?:the person's next message|current person request(?:\s*\([^\n]*\))?|side task)\s*---\s*$/gim
|
|
26
|
+
const QUESTION_WORDS = new Set(['how', 'what', 'when', 'where', 'which', 'who', 'whom', 'whose', 'why'])
|
|
27
|
+
const PREFERENCE_WORDS = new Set(['avoid', 'choose', 'include', 'need', 'use', 'want'])
|
|
28
|
+
const PREFERENCE_OBJECT_WORDS = new Set(['avoid', 'choose', 'include', 'use'])
|
|
29
|
+
|
|
30
|
+
const conceptsForTerm = (term) => new Set(
|
|
31
|
+
[term?.text, term?.normal, term?.machine, term?.implicit]
|
|
32
|
+
.map((value) => String(value || '').trim().toLowerCase())
|
|
33
|
+
.filter(Boolean),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
const analyzeGrammar = (value, suppliedTerms = null) => {
|
|
37
|
+
const text = String(value || '').trim()
|
|
38
|
+
const terms = suppliedTerms || nlp(text).json({ terms: true }).flatMap((sentence) => sentence.terms || [])
|
|
39
|
+
const visible = terms
|
|
40
|
+
.filter((term) => String(term?.text || '').trim())
|
|
41
|
+
.map((term) => ({ text: term.text, concepts: conceptsForTerm(term), tags: new Set(term.tags || []) }))
|
|
42
|
+
const hasQuestionWord = visible.some((term) => [...term.concepts].some((word) => QUESTION_WORDS.has(word)))
|
|
43
|
+
const taggedQuestionWord = visible.some((term) => term.tags.has('QuestionWord'))
|
|
44
|
+
const negative = terms.some((term) => Array.isArray(term?.tags) && term.tags.includes('Negative'))
|
|
45
|
+
const preference = terms.some((term) => [...conceptsForTerm(term)].some((word) => PREFERENCE_WORDS.has(word)))
|
|
46
|
+
return {
|
|
47
|
+
text,
|
|
48
|
+
visible,
|
|
49
|
+
informationRequest: hasQuestionWord && (taggedQuestionWord || /\?\s*$/.test(text)),
|
|
50
|
+
negativePreference: negative && preference,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const parsedClauses = (value) => String(value || '')
|
|
55
|
+
.replace(/,\s*(?:but|however)\s+/gi, '\n')
|
|
56
|
+
.split(/\n+|;\s*/)
|
|
57
|
+
.flatMap((part) => {
|
|
58
|
+
const text = part.trim()
|
|
59
|
+
if (!text) return []
|
|
60
|
+
const sentences = nlp(text).json({ terms: true })
|
|
61
|
+
return sentences.length
|
|
62
|
+
? sentences.map((sentence) => analyzeGrammar(sentence.text, sentence.terms || []))
|
|
63
|
+
: [analyzeGrammar(text)]
|
|
64
|
+
})
|
|
65
|
+
.filter((clause) => clause.text)
|
|
66
|
+
|
|
67
|
+
const questionSubject = (analysis) => {
|
|
68
|
+
const index = analysis.visible.findIndex((term) => [...term.concepts].some((word) => QUESTION_WORDS.has(word)))
|
|
69
|
+
return index >= 0 ? analysis.visible.slice(index + 1).map((term) => term.text).join(' ') : analysis.text
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const negativePreferenceSubject = (analysis) => {
|
|
73
|
+
let index = -1
|
|
74
|
+
for (let i = 0; i < analysis.visible.length; i++) {
|
|
75
|
+
if ([...analysis.visible[i].concepts].some((word) => PREFERENCE_OBJECT_WORDS.has(word))) index = i
|
|
76
|
+
}
|
|
77
|
+
if (index < 0) {
|
|
78
|
+
index = analysis.visible.findIndex((term) => [...term.concepts].some((word) => word === 'need' || word === 'want'))
|
|
79
|
+
}
|
|
80
|
+
return index >= 0 ? analysis.visible.slice(index + 1).map((term) => term.text).join(' ') : analysis.text
|
|
81
|
+
}
|
|
27
82
|
|
|
28
83
|
const ACTIONS = Object.freeze([
|
|
29
84
|
{ title: 'Fix', score: 100, re: /\b(?:fix|fixes|fixed|fixing|repair|repairs|repaired|repairing)\b/i },
|
|
@@ -117,35 +172,31 @@ const explicitIntent = (text) => {
|
|
|
117
172
|
}
|
|
118
173
|
|
|
119
174
|
const bestIntentClause = (text) => {
|
|
120
|
-
const clauses = text
|
|
121
|
-
.replace(/,\s*(?:but|however)\s+/gi, '\n')
|
|
122
|
-
.split(/\n+|(?<=[.!?;])\s+/)
|
|
123
|
-
.map((part) => part.trim())
|
|
124
|
-
.filter(Boolean)
|
|
175
|
+
const clauses = parsedClauses(text)
|
|
125
176
|
let best = null
|
|
126
|
-
for (
|
|
127
|
-
const clause =
|
|
177
|
+
for (const parsed of clauses) {
|
|
178
|
+
const clause = parsed.text.replace(/^[-*#>\d.)\s]+/, '').trim()
|
|
128
179
|
if (!clause || /^(?:https?:\/\/|\/|[A-Za-z]:\\)/.test(clause)) continue
|
|
129
|
-
const
|
|
180
|
+
const grammar = clause === parsed.text ? parsed : analyzeGrammar(clause)
|
|
130
181
|
const action = bestAction(clause)
|
|
131
182
|
let score = action?.score || 0
|
|
132
183
|
if (REQUEST.test(clause)) score += 18
|
|
133
|
-
if (
|
|
184
|
+
if (grammar.informationRequest) score += 36
|
|
134
185
|
if (ISSUE.test(clause)) score += /\b(?:crash|fail|flash|stuck|wrong)\w*\b/i.test(clause) ? 36 : 26
|
|
135
|
-
if (
|
|
136
|
-
else if (
|
|
186
|
+
if (grammar.visible.length >= 3 && grammar.visible.length <= 24) score += 10
|
|
187
|
+
else if (grammar.visible.length > 40) score -= 14
|
|
137
188
|
if (NOISE.test(clause)) score -= 30
|
|
138
189
|
if (EXPLANATION.test(clause)) score -= 34
|
|
139
190
|
if (CONSTRAINT.test(clause)) score -= 45
|
|
140
|
-
if (
|
|
191
|
+
if (grammar.negativePreference) score -= 70
|
|
141
192
|
// A short dependent tail such as "From ground up" or "On mobile" adds
|
|
142
193
|
// scope to the preceding request; it is not a standalone task. Recency is
|
|
143
194
|
// deliberately not a signal here: the title should represent the main
|
|
144
195
|
// theme, not whichever sentence happened to come last.
|
|
145
|
-
if (!action && !REQUEST.test(clause) && !
|
|
196
|
+
if (!action && !REQUEST.test(clause) && !grammar.informationRequest && DEPENDENT_FRAGMENT.test(clause)) score -= 24
|
|
146
197
|
if (!best || score > best.score) best = { clause, score }
|
|
147
198
|
}
|
|
148
|
-
return best?.clause || clauses[0] || null
|
|
199
|
+
return best?.clause || clauses[0]?.text || null
|
|
149
200
|
}
|
|
150
201
|
|
|
151
202
|
export function extractTerminalTask(value) {
|
|
@@ -210,9 +261,13 @@ const taskTitle = (value) => {
|
|
|
210
261
|
.replace(/^\s*(?:how about|i (?:do not|don't) know|i guess|i want you to|we need to|your task is to|let(?:'s| us))\s+/i, '')
|
|
211
262
|
.trim()
|
|
212
263
|
if (!body) return null
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
264
|
+
const grammar = analyzeGrammar(body)
|
|
265
|
+
if (grammar.negativePreference) {
|
|
266
|
+
const subject = contentWords(negativePreferenceSubject(grammar)).slice(0, 4)
|
|
267
|
+
return subject.length ? cleanTerminalName(['Avoid', ...subject.map(titleWord)].join(' ')) : null
|
|
268
|
+
}
|
|
269
|
+
const informationRequest = grammar.informationRequest
|
|
270
|
+
if (informationRequest) body = questionSubject(grammar).trim()
|
|
216
271
|
const signalText = body
|
|
217
272
|
body = body
|
|
218
273
|
.replace(/\s*,?\s+(?:so (?:that|you|we|it)\b|because\b|cause\b|if you know what i mean\b)[\s\S]*$/i, '')
|