thinkpool-pair 0.7.364 → 0.7.366
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/LICENSE +18 -0
- package/README.md +20 -0
- package/abort-turn-barrier.mjs +1 -23
- package/account.mjs +1 -1437
- package/acp-client.mjs +1 -140
- package/agent-detect.mjs +1 -28
- package/agent-notify.mjs +1 -142
- package/agent-visibility.mjs +1 -67
- package/auth-store.mjs +1 -120
- package/bridge.mjs +1 -6266
- package/byok-detect.mjs +1 -126
- package/claude-command-catalog.mjs +1 -91
- package/claude-session.mjs +1 -1519
- package/code-event-contract.mjs +1 -118
- package/codex-app-server.mjs +1 -340
- package/codex-commands.mjs +1 -83
- package/codex-event-mapper.mjs +1 -232
- package/codex-images.mjs +1 -69
- package/codex-mcp-http.mjs +1 -131
- package/codex-session.mjs +1 -1295
- package/command-catalog.mjs +1 -118
- package/command-guidance.mjs +1 -8
- package/context-contract.mjs +1 -95
- package/context-windows.mjs +1 -107
- package/cross-terminal.mjs +1 -789
- package/cumulative-event-relay.mjs +1 -53
- package/design-edit.mjs +1 -424
- package/design-source-contract.mjs +1 -4
- package/direct-pair-room.mjs +1 -57
- package/dispatch-lease.mjs +1 -37
- package/dispatch-permission-cleanup.mjs +1 -86
- package/edit-diff.mjs +1 -136
- package/error-recovery.mjs +1 -50
- package/event-bounds.mjs +1 -121
- package/event-delivery-queue.mjs +1 -60
- package/event-id.mjs +1 -549
- package/evidence-citations.mjs +1 -50
- package/evidence-compact.mjs +1 -11
- package/flow-assembly.mjs +1 -196
- package/flow-budget.mjs +1 -84
- package/flow-conductor.mjs +1 -259
- package/flow-context-store.mjs +1 -387
- package/flow-host-revert.mjs +1 -42
- package/flow-models.mjs +1 -139
- package/flow-preview.mjs +1 -148
- package/flow-receipt.mjs +1 -122
- package/flow-redispatch.mjs +1 -72
- package/flow-review-gate.mjs +1 -402
- package/flow-review-reflect.mjs +1 -115
- package/flow-review.mjs +1 -152
- package/flow-scope-evidence.mjs +1 -117
- package/flow-skill-registry.mjs +1 -140
- package/flow-task-graph.mjs +1 -562
- package/flow-worktree.mjs +1 -71
- package/git-diff-report.mjs +1 -121
- package/hermes-delegation-guard.mjs +1 -14
- package/hermes-event-mapper.mjs +1 -194
- package/hermes-isolation.mjs +1 -61
- package/hermes-model-cache.mjs +1 -54
- package/hermes-policy.mjs +1 -92
- package/hermes-probe.mjs +1 -57
- package/hermes-session.mjs +1 -683
- package/hermes-setup.mjs +1 -167
- package/host-memory.mjs +1 -116
- package/interrupted-resume.mjs +1 -95
- package/keep-awake.mjs +1 -148
- package/key-shape.mjs +1 -49
- package/lane-continuation.mjs +1 -83
- package/lane-lifecycle.mjs +1 -189
- package/lane-worktree.mjs +1 -77
- package/launcher.mjs +1 -354
- package/mcp-flight-recorder.mjs +1 -79
- package/mockup-delivery.mjs +1 -57
- package/model-prices.mjs +1 -113
- package/package.json +13 -4
- package/pair-bus.mjs +1 -98
- package/pair-control-authority.mjs +1 -89
- package/past-work-search.mjs +1 -105
- package/plan-meters.mjs +1 -144
- package/presence.mjs +1 -191
- package/privacy-report.mjs +1 -108
- package/provider-resilience.mjs +1 -356
- package/provider.mjs +1 -133
- package/providers.mjs +1 -491
- package/publish-guard.mjs +2 -0
- package/publish-manifest.json +129 -0
- package/question-response.mjs +1 -58
- package/reap-terminal.mjs +1 -68
- package/recap.mjs +1 -297
- package/replay-transport.mjs +1 -64
- package/repo-search.mjs +1 -190
- package/review-check.mjs +1 -182
- package/runtime-contract.mjs +1 -93
- package/runtime-registry.mjs +1 -64
- package/runtime-session.mjs +1 -20
- package/scheduled-run-admission.mjs +1 -364
- package/scheduled-runs.mjs +1 -268
- package/sdk-admission.mjs +1 -9
- package/sdk-smoke.mjs +1 -61
- package/serve-consent.mjs +1 -118
- package/serve-dir.mjs +1 -40
- package/service.mjs +1 -877
- package/session-store.mjs +1 -484
- package/side-lane.mjs +1 -63
- package/supabase-key.mjs +1 -176
- package/supervisor-ready.mjs +1 -57
- package/switch-provider.mjs +1 -84
- package/terminal-name.mjs +1 -359
- package/terminal-row-reconcile.mjs +1 -70
- package/thinkpool-prompt-contracts.mjs +1 -85
- package/thinkpool-room-prompt.mjs +1 -187
- package/transcript-sanitize.mjs +1 -332
- package/turn-stall.mjs +1 -61
- package/update-gate.mjs +1 -53
- package/viewport.mjs +1 -810
- package/worker-completion.mjs +1 -57
package/terminal-name.mjs
CHANGED
|
@@ -1,359 +1 @@
|
|
|
1
|
-
import { nativeClaudeProviderAccessFailed } from './cross-terminal.mjs'
|
|
2
|
-
import nlp from 'compromise/two'
|
|
3
|
-
|
|
4
|
-
const SKIP = new Set([
|
|
5
|
-
'a', 'an', 'and', 'are', 'at', 'be', 'can', 'could', 'for', 'from', 'how', 'i',
|
|
6
|
-
'in', 'is', 'it', 'just', 'like', 'maybe', 'me', 'my', 'of', 'on', 'or', 'our',
|
|
7
|
-
'please', 'something', 'that', 'the', 'this', 'to', 'we', 'with', 'would', 'you',
|
|
8
|
-
'do', 'much', 'better', 'really', 'need', 'want', 'your', 'their', 'its',
|
|
9
|
-
'also', 'all', 'away', 'before', 'cause', 'does', 'dont', "don't", 'fully', 'has',
|
|
10
|
-
'have', 'now', 'occasionally', 'return', 'second', 'so', 'sometimes', 'still',
|
|
11
|
-
'than', 'then', 'there', 'what', 'when', 'where', 'which', 'who', 'whenever',
|
|
12
|
-
'why', 'working',
|
|
13
|
-
])
|
|
14
|
-
|
|
15
|
-
const GENERIC = /^(?:new )?(?:agent |coding )?(?:terminal|task|lane|session|work)$/i
|
|
16
|
-
const INTENT_HEADING = /^(?:#{1,6}\s*)?(?:task|goal|objective|request|mission|assignment|problem(?: to solve)?|what to do)(?:\s*:\s*(.*)|\s*)$/i
|
|
17
|
-
const ANY_HEADING = /^(?:#{1,6}\s+|(?:context|background|constraints?|inputs?|outputs?|acceptance|success criteria|steps?|assumptions?|open questions?|notes?|references?|environment)\s*:)/i
|
|
18
|
-
const NOISE = /^(?:context|background|for reference|here(?:'s| is)|note|current(?:ly)?|example|environment|room now|constraints?|acceptance|success criteria)\b/i
|
|
19
|
-
const EXPLANATION = /^(?:because|cause|since|so that|this is because)\b/i
|
|
20
|
-
const CONSTRAINT = /^(?:users? can still|keep|must|never|should|without)\b/i
|
|
21
|
-
const DEPENDENT_FRAGMENT = /^(?:after|before|by|during|for|from|in|instead(?: of)?|on|through|until|with|without)\b/i
|
|
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
|
|
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
|
|
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
|
|
25
|
-
const HANDOFF_MARKER = /^---\s*(?:the person's next message|current person request(?:\s*\([^\n]*\))?|side task)\s*---\s*$/gim
|
|
26
|
-
const CURRENT_REQUEST_MARKER = /^(?:#{1,6}\s*)?CURRENT PERSON REQUEST(?:\s*\([^\n]*\))?\s*:?\s*/gim
|
|
27
|
-
const QUESTION_WORDS = new Set(['how', 'what', 'when', 'where', 'which', 'who', 'whom', 'whose', 'why'])
|
|
28
|
-
const PREFERENCE_WORDS = new Set(['avoid', 'choose', 'include', 'need', 'use', 'want'])
|
|
29
|
-
const PREFERENCE_OBJECT_WORDS = new Set(['avoid', 'choose', 'include', 'use'])
|
|
30
|
-
|
|
31
|
-
const conceptsForTerm = (term) => new Set(
|
|
32
|
-
[term?.text, term?.normal, term?.machine, term?.implicit]
|
|
33
|
-
.map((value) => String(value || '').trim().toLowerCase())
|
|
34
|
-
.filter(Boolean),
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
const analyzeGrammar = (value, suppliedTerms = null) => {
|
|
38
|
-
const text = String(value || '').trim()
|
|
39
|
-
const terms = suppliedTerms || nlp(text).json({ terms: true }).flatMap((sentence) => sentence.terms || [])
|
|
40
|
-
const visible = terms
|
|
41
|
-
.filter((term) => String(term?.text || '').trim())
|
|
42
|
-
.map((term) => ({ text: term.text, concepts: conceptsForTerm(term), tags: new Set(term.tags || []) }))
|
|
43
|
-
const hasQuestionWord = visible.some((term) => [...term.concepts].some((word) => QUESTION_WORDS.has(word)))
|
|
44
|
-
const taggedQuestionWord = visible.some((term) => term.tags.has('QuestionWord'))
|
|
45
|
-
const negative = terms.some((term) => Array.isArray(term?.tags) && term.tags.includes('Negative'))
|
|
46
|
-
const preference = terms.some((term) => [...conceptsForTerm(term)].some((word) => PREFERENCE_WORDS.has(word)))
|
|
47
|
-
return {
|
|
48
|
-
text,
|
|
49
|
-
visible,
|
|
50
|
-
informationRequest: hasQuestionWord && (taggedQuestionWord || /\?\s*$/.test(text)),
|
|
51
|
-
negativePreference: negative && preference,
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const parsedClauses = (value) => String(value || '')
|
|
56
|
-
.replace(/,\s*(?:but|however)\s+/gi, '\n')
|
|
57
|
-
.split(/\n+|;\s*/)
|
|
58
|
-
.flatMap((part) => {
|
|
59
|
-
const text = part.trim()
|
|
60
|
-
if (!text) return []
|
|
61
|
-
const sentences = nlp(text).json({ terms: true })
|
|
62
|
-
return sentences.length
|
|
63
|
-
? sentences.map((sentence) => analyzeGrammar(sentence.text, sentence.terms || []))
|
|
64
|
-
: [analyzeGrammar(text)]
|
|
65
|
-
})
|
|
66
|
-
.filter((clause) => clause.text)
|
|
67
|
-
|
|
68
|
-
const questionSubject = (analysis) => {
|
|
69
|
-
const index = analysis.visible.findIndex((term) => [...term.concepts].some((word) => QUESTION_WORDS.has(word)))
|
|
70
|
-
return index >= 0 ? analysis.visible.slice(index + 1).map((term) => term.text).join(' ') : analysis.text
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const negativePreferenceSubject = (analysis) => {
|
|
74
|
-
let index = -1
|
|
75
|
-
for (let i = 0; i < analysis.visible.length; i++) {
|
|
76
|
-
if ([...analysis.visible[i].concepts].some((word) => PREFERENCE_OBJECT_WORDS.has(word))) index = i
|
|
77
|
-
}
|
|
78
|
-
if (index < 0) {
|
|
79
|
-
index = analysis.visible.findIndex((term) => [...term.concepts].some((word) => word === 'need' || word === 'want'))
|
|
80
|
-
}
|
|
81
|
-
return index >= 0 ? analysis.visible.slice(index + 1).map((term) => term.text).join(' ') : analysis.text
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const ACTIONS = Object.freeze([
|
|
85
|
-
{ title: 'Fix', score: 100, re: /\b(?:fix|fixes|fixed|fixing|repair|repairs|repaired|repairing)\b/i },
|
|
86
|
-
{ title: 'Harden', score: 98, re: /\b(?:harden|hardens|hardened|hardening)\b/i },
|
|
87
|
-
{ title: 'Snap', score: 96, re: /\b(?:snap|snaps|snapped|snapping)\b/i },
|
|
88
|
-
{ title: 'Improve', score: 94, re: /\b(?:improve|improves|improved|improving|optimi[sz](?:e|es|ed|ing)|\bbetter\b)\b/i },
|
|
89
|
-
{ title: 'Rework', score: 93, re: /\b(?:rework|reworks|reworked|reworking)\b/i },
|
|
90
|
-
{ title: 'Implement', score: 92, re: /\b(?:implement|implements|implemented|implementing)\b/i },
|
|
91
|
-
{ title: 'Build', score: 90, re: /\b(?:build|builds|built|building|create|creates|created|creating)\b/i },
|
|
92
|
-
{ title: 'Add', score: 88, re: /\b(?:add|adds|added|adding|wire|wires|wired|wiring)\b/i },
|
|
93
|
-
{ title: 'Replace', score: 87, re: /\b(?:replace|replaces|replaced|replacing|migrate|migrates|migrated|migrating)\b/i },
|
|
94
|
-
{ title: 'Rename', score: 86, re: /\b(?:rename|renames|renamed|renaming)\b/i },
|
|
95
|
-
{ title: 'Name', score: 85, re: /\b(?:name|names|named|naming)\b/i },
|
|
96
|
-
{ title: 'Refactor', score: 84, re: /\b(?:refactor|refactors|refactored|refactoring|simplify|simplifies|simplified|simplifying)\b/i },
|
|
97
|
-
{ title: 'Remove', score: 83, re: /\b(?:remove|removes|removed|removing)\b/i },
|
|
98
|
-
{ title: 'Update', score: 82, re: /\b(?:update|updates|updated|updating|change|changes|changed|changing)\b/i },
|
|
99
|
-
{ title: 'Debug', score: 76, re: /\b(?:debug|debugs|debugged|debugging|diagnose|diagnoses|diagnosed|diagnosing|trace|traces|traced|tracing)\b/i },
|
|
100
|
-
{ title: 'Audit', score: 70, re: /\b(?:audit|audits|audited|auditing|check|checks|checked|checking|investigate|investigates|investigated|investigating|review|reviews|reviewed|reviewing|test|tests|tested|testing|verify|verifies|verified|verifying)\b/i },
|
|
101
|
-
])
|
|
102
|
-
|
|
103
|
-
const bestAction = (value) => {
|
|
104
|
-
const text = String(value || '')
|
|
105
|
-
let best = null
|
|
106
|
-
for (const action of ACTIONS) {
|
|
107
|
-
const match = text.match(action.re)
|
|
108
|
-
if (!match) continue
|
|
109
|
-
const candidate = { ...action, index: match.index, match: match[0] }
|
|
110
|
-
if (!best || candidate.score > best.score || (candidate.score === best.score && candidate.index < best.index)) best = candidate
|
|
111
|
-
}
|
|
112
|
-
return best
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const withoutSecrets = (value) => String(value || '').replace(SECRET, ' ')
|
|
116
|
-
const containsSecret = (value) => {
|
|
117
|
-
SECRET.lastIndex = 0
|
|
118
|
-
const found = SECRET.test(String(value || ''))
|
|
119
|
-
SECRET.lastIndex = 0
|
|
120
|
-
return found
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const pathLabel = (path) => {
|
|
124
|
-
const clean = path.replace(/[?#].*$/, '').replace(/[),.;:]+$/, '')
|
|
125
|
-
const segments = clean.split(/[\\/]/).filter(Boolean)
|
|
126
|
-
const leaf = segments.at(-1) || ''
|
|
127
|
-
return leaf
|
|
128
|
-
.replace(/\.(?:[cm]?[jt]sx?|tsx?|json|md|html?|css|scss|py|rb|rs|go|java|kt|swift|sh|ya?ml|toml)$/i, '')
|
|
129
|
-
.replace(/[-_.]+/g, ' ')
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const withoutHostReferences = (value) => String(value || '')
|
|
133
|
-
.replace(/\bhttps?:\/\/[^\s<>"'`]+/gi, ' ')
|
|
134
|
-
.replace(/(^|[\s("'`])((?:\.{0,2}\/)[^\s<>"'`]+)/g, (_all, lead, path) => `${lead}${pathLabel(path)}`)
|
|
135
|
-
.replace(/\b[A-Za-z]:\\[^\s<>"'`]+/g, (path) => pathLabel(path))
|
|
136
|
-
.replace(/\b(?:[\p{L}\p{N}_.-]+\/)+[\p{L}\p{N}_.-]+\.(?:[cm]?[jt]sx?|tsx?|json|md|html?|css|scss|py|rb|rs|go|java|kt|swift|sh|ya?ml|toml)\b/giu, (path) => pathLabel(path))
|
|
137
|
-
|
|
138
|
-
const stripPromptNoise = (value) => {
|
|
139
|
-
let text = withoutSecrets(String(value || '').slice(0, 24000))
|
|
140
|
-
// A carried recap or Side handoff puts the new request after one of these markers.
|
|
141
|
-
// The suffix is the task; everything before it is context the lane still needs, but
|
|
142
|
-
// the title does not.
|
|
143
|
-
const markers = [...text.matchAll(HANDOFF_MARKER)]
|
|
144
|
-
const marker = markers.at(-1)
|
|
145
|
-
if (marker) text = text.slice(marker.index + marker[0].length)
|
|
146
|
-
return text
|
|
147
|
-
.replace(/<(?:thinkpool-context|system-reminder|environment_context|recommended_plugins)\b[^>]*>[\s\S]*?<\/(?:thinkpool-context|system-reminder|environment_context|recommended_plugins)>/gi, ' ')
|
|
148
|
-
.replace(/^\s*\[[^\]\n]{1,300}\]\s*/g, '')
|
|
149
|
-
.replace(/```[^\n]*\n[\s\S]*?(?:```|$)/g, ' ')
|
|
150
|
-
.replace(/\r/g, '')
|
|
151
|
-
.trim()
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const explicitIntent = (text) => {
|
|
155
|
-
const lines = text.split('\n')
|
|
156
|
-
const intents = []
|
|
157
|
-
for (let i = 0; i < lines.length; i++) {
|
|
158
|
-
const match = lines[i].trim().match(INTENT_HEADING)
|
|
159
|
-
if (!match) continue
|
|
160
|
-
const picked = []
|
|
161
|
-
if (match[1]?.trim()) picked.push(match[1].trim())
|
|
162
|
-
for (let j = i + 1; j < lines.length && picked.join(' ').length < 1200; j++) {
|
|
163
|
-
const line = lines[j].trim()
|
|
164
|
-
if (ANY_HEADING.test(line) || INTENT_HEADING.test(line)) break
|
|
165
|
-
if (line) picked.push(line.replace(/^[-*]\s*/, ''))
|
|
166
|
-
}
|
|
167
|
-
const intent = picked.join(' ').trim()
|
|
168
|
-
if (intent) intents.push(intent)
|
|
169
|
-
}
|
|
170
|
-
// Long handoffs often include an old structured prompt before the current
|
|
171
|
-
// objective. The final explicit intent is the closest one to the handoff.
|
|
172
|
-
return intents.at(-1) || null
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const bestIntentClause = (text) => {
|
|
176
|
-
const clauses = parsedClauses(text)
|
|
177
|
-
let best = null
|
|
178
|
-
for (const parsed of clauses) {
|
|
179
|
-
const clause = parsed.text.replace(/^[-*#>\d.)\s]+/, '').trim()
|
|
180
|
-
if (!clause || /^(?:https?:\/\/|\/|[A-Za-z]:\\)/.test(clause)) continue
|
|
181
|
-
const grammar = clause === parsed.text ? parsed : analyzeGrammar(clause)
|
|
182
|
-
const action = bestAction(clause)
|
|
183
|
-
let score = action?.score || 0
|
|
184
|
-
if (REQUEST.test(clause)) score += 18
|
|
185
|
-
if (grammar.informationRequest) score += 36
|
|
186
|
-
if (ISSUE.test(clause)) score += /\b(?:crash|fail|flash|stuck|wrong)\w*\b/i.test(clause) ? 36 : 26
|
|
187
|
-
if (grammar.visible.length >= 3 && grammar.visible.length <= 24) score += 10
|
|
188
|
-
else if (grammar.visible.length > 40) score -= 14
|
|
189
|
-
if (NOISE.test(clause)) score -= 30
|
|
190
|
-
if (EXPLANATION.test(clause)) score -= 34
|
|
191
|
-
if (CONSTRAINT.test(clause)) score -= 45
|
|
192
|
-
if (grammar.negativePreference) score -= 70
|
|
193
|
-
// A short dependent tail such as "From ground up" or "On mobile" adds
|
|
194
|
-
// scope to the preceding request; it is not a standalone task. Recency is
|
|
195
|
-
// deliberately not a signal here: the title should represent the main
|
|
196
|
-
// theme, not whichever sentence happened to come last.
|
|
197
|
-
if (!action && !REQUEST.test(clause) && !grammar.informationRequest && DEPENDENT_FRAGMENT.test(clause)) score -= 24
|
|
198
|
-
if (!best || score > best.score) best = { clause, score }
|
|
199
|
-
}
|
|
200
|
-
return best?.clause || clauses[0]?.text || null
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export function extractTerminalTask(value) {
|
|
204
|
-
const text = stripPromptNoise(value)
|
|
205
|
-
if (!text) return null
|
|
206
|
-
return (explicitIntent(text) || bestIntentClause(text) || text).slice(0, 1600).trim() || null
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export function cleanTerminalName(value) {
|
|
210
|
-
const raw = String(value || '')
|
|
211
|
-
if (containsSecret(raw)) return null
|
|
212
|
-
let name = raw
|
|
213
|
-
.trim()
|
|
214
|
-
.split(/\r?\n/, 1)[0]
|
|
215
|
-
.replace(/^\s*(?:[-*#>]+|title\s*:?)\s*/i, '')
|
|
216
|
-
.replace(/^["'`]+|["'`]+$/g, '')
|
|
217
|
-
.replace(/[^\p{L}\p{N}+#&.' -]+/gu, ' ')
|
|
218
|
-
.replace(/\s+/g, ' ')
|
|
219
|
-
.replace(/[.,;:!?]+$/u, '')
|
|
220
|
-
.trim()
|
|
221
|
-
if (!name || GENERIC.test(name) || nativeClaudeProviderAccessFailed(name)) return null
|
|
222
|
-
if (/^(?:error|failed|sorry|unable|i (?:cannot|can't)|rate limit|request failed)\b/i.test(name)) return null
|
|
223
|
-
if (name.length > 48) name = name.slice(0, 48).replace(/\s+\S*$/, '').trim()
|
|
224
|
-
const words = name.split(/\s+/).filter(Boolean)
|
|
225
|
-
const compactCjk = words.length === 1
|
|
226
|
-
&& /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u.test(name)
|
|
227
|
-
if ((words.length < 2 && !compactCjk) || words.length > 3) return null
|
|
228
|
-
return name || null
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const titleWord = (word) => {
|
|
232
|
-
const lower = word.toLowerCase()
|
|
233
|
-
if (lower === 'thinkpool' || lower === 'thinkpool.io') return 'ThinkPool'
|
|
234
|
-
if (lower === 'serverside') return 'Server-Side'
|
|
235
|
-
if (/^(?:api|ci|css|db|html|llm|mcp|npm|sdk|ui|ux)$/i.test(word)) return lower.toUpperCase()
|
|
236
|
-
if (/^[A-Z\d+#.-]{2,}$/.test(word) || /\d/.test(word)) return word
|
|
237
|
-
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const contentWords = (value, excludedFamilies = []) => {
|
|
241
|
-
const words = String(value || '').match(/[\p{L}\p{N}][\p{L}\p{N}+#.'-]*/gu) || []
|
|
242
|
-
const seen = new Set()
|
|
243
|
-
const result = []
|
|
244
|
-
for (let word of words) {
|
|
245
|
-
const lower = word.toLowerCase()
|
|
246
|
-
if (SKIP.has(lower) || /^https?$/i.test(word)) continue
|
|
247
|
-
// ACTIONS intentionally folds synonyms into one title verb (review/test/verify
|
|
248
|
-
// all become Audit). Once that verb is chosen, words from the same family are
|
|
249
|
-
// not objects. Keeping them spent the title budget on "Audit Reviews Audits"
|
|
250
|
-
// before the extractor reached the actual scope.
|
|
251
|
-
if (excludedFamilies.some((family) => family.test(word))) continue
|
|
252
|
-
if (lower === 'flashes' || lower === 'flashing') word = 'flash'
|
|
253
|
-
if (lower === 'failing' || lower === 'failed' || lower === 'fails') word = 'failure'
|
|
254
|
-
const key = word.toLowerCase().replace(/(?:s|ed|ing)$/i, '')
|
|
255
|
-
if (!key || seen.has(key)) continue
|
|
256
|
-
seen.add(key)
|
|
257
|
-
result.push(word)
|
|
258
|
-
}
|
|
259
|
-
return result
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const taskTitle = (value) => {
|
|
263
|
-
let body = String(value || '')
|
|
264
|
-
.replace(/^\s*(?:hey|hi|okay|ok|so)\b[,:!]?\s*/i, '')
|
|
265
|
-
.replace(/^\s*(?:return to (?:working on )?|can|could|would|will)\s+(?:we\s+|you\s+)?/i, '')
|
|
266
|
-
.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, '')
|
|
267
|
-
.trim()
|
|
268
|
-
if (!body) return null
|
|
269
|
-
const grammar = analyzeGrammar(body)
|
|
270
|
-
if (grammar.negativePreference) {
|
|
271
|
-
const subject = contentWords(negativePreferenceSubject(grammar)).slice(0, 2)
|
|
272
|
-
return subject.length ? cleanTerminalName(['Avoid', ...subject.map(titleWord)].join(' ')) : null
|
|
273
|
-
}
|
|
274
|
-
const informationRequest = grammar.informationRequest
|
|
275
|
-
if (informationRequest) body = questionSubject(grammar).trim()
|
|
276
|
-
const signalText = body
|
|
277
|
-
body = body
|
|
278
|
-
.replace(/\s*,?\s+(?:so (?:that|you|we|it)\b|because\b|cause\b|if you know what i mean\b)[\s\S]*$/i, '')
|
|
279
|
-
.replace(/\s+for\s+(?:a|an|one|\d+)\s+(?:moment|second|minute|hour)s?\b[\s\S]*$/i, '')
|
|
280
|
-
.trim()
|
|
281
|
-
|
|
282
|
-
const issue = ISSUE.test(body)
|
|
283
|
-
const action = bestAction(body)
|
|
284
|
-
let verb = issue ? 'Fix' : action?.title
|
|
285
|
-
if (!verb && /\bclos(?:e|es|ed|ing)\b/i.test(body)) verb = 'Close'
|
|
286
|
-
if (!verb && /\bopen(?:s|ed|ing)?\b/i.test(body)) verb = 'Open'
|
|
287
|
-
|
|
288
|
-
if (/\bautomatic(?:ally)?\b[\s\S]{0,24}\bnam(?:e|ing)\b/i.test(signalText) && /\bterminals?\b/i.test(signalText)) {
|
|
289
|
-
if (/\bcontext\b[\s\S]{0,24}\broom\b|\broom\b[\s\S]{0,24}\bcontext\b/i.test(signalText)) return 'Contextual Terminal Names'
|
|
290
|
-
verb = 'Auto-Name'
|
|
291
|
-
}
|
|
292
|
-
if (verb === 'Snap' && /\bterminal row\b/i.test(signalText) && /\bview\b/i.test(signalText)) return 'Snap Terminal Row'
|
|
293
|
-
if (verb === 'Snap' && /\bclicked terminal\b/i.test(signalText) && /\bview\b/i.test(signalText)) return 'Snap Clicked Terminal'
|
|
294
|
-
|
|
295
|
-
let domain = []
|
|
296
|
-
let objectText = body
|
|
297
|
-
if (action) {
|
|
298
|
-
const before = body.slice(0, action.index)
|
|
299
|
-
objectText = body.slice(action.index + action.match.length)
|
|
300
|
-
const weakLead = bestAction(before)
|
|
301
|
-
if (weakLead?.score <= 76) domain = contentWords(before.slice(weakLead.index + weakLead.match.length), [weakLead.re]).slice(0, 3)
|
|
302
|
-
objectText = objectText.split(/\s*,?\s+(?:and|then)\s+(?:(?:also)\s+)?(?=(?:add|build|create|implement|remove|replace|update|wire)\b)/i, 1)[0]
|
|
303
|
-
objectText = objectText.replace(/\b(?:and|then)\s+(?:audit|check|debug|investigate|review|test|verify)\b/gi, ' ')
|
|
304
|
-
}
|
|
305
|
-
const excludedObjectFamilies = action ? [action.re] : []
|
|
306
|
-
let object = contentWords(objectText, excludedObjectFamilies)
|
|
307
|
-
if (!object.length) object = contentWords(body, excludedObjectFamilies)
|
|
308
|
-
if (informationRequest) {
|
|
309
|
-
const available = object.findIndex((word) => /^available$/i.test(word))
|
|
310
|
-
if (available > 0) object = [object[available], ...object.slice(0, available), ...object.slice(available + 1)]
|
|
311
|
-
}
|
|
312
|
-
const picked = [...domain, ...object]
|
|
313
|
-
.filter((word, index, all) => all.findIndex((other) => other.toLowerCase() === word.toLowerCase()) === index)
|
|
314
|
-
.slice(0, verb ? 2 : 3)
|
|
315
|
-
if (!picked.length) return null
|
|
316
|
-
return cleanTerminalName([verb, ...picked.map(titleWord)].filter(Boolean).join(' '))
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
export function fallbackTerminalName(text) {
|
|
320
|
-
const body = withoutHostReferences(withoutSecrets(extractTerminalTask(text) || ''))
|
|
321
|
-
.replace(/[`*_>#()[\]{}]/g, ' ')
|
|
322
|
-
.slice(0, 1600)
|
|
323
|
-
return taskTitle(body)
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const MODEL_INPUT_MAX_BYTES = 1600
|
|
327
|
-
|
|
328
|
-
const truncateUtf8 = (value, maxBytes = MODEL_INPUT_MAX_BYTES) => {
|
|
329
|
-
let result = ''
|
|
330
|
-
let bytes = 0
|
|
331
|
-
for (const char of String(value || '')) {
|
|
332
|
-
const size = Buffer.byteLength(char, 'utf8')
|
|
333
|
-
if (bytes + size > maxBytes) break
|
|
334
|
-
result += char
|
|
335
|
-
bytes += size
|
|
336
|
-
}
|
|
337
|
-
return result.trim()
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const managedTaskText = (value) => {
|
|
341
|
-
let text = stripPromptNoise(value)
|
|
342
|
-
if (!text) return null
|
|
343
|
-
const currentRequests = [...text.matchAll(CURRENT_REQUEST_MARKER)]
|
|
344
|
-
const currentRequest = currentRequests.at(-1)
|
|
345
|
-
if (currentRequest) text = text.slice(currentRequest.index + currentRequest[0].length)
|
|
346
|
-
return (explicitIntent(text) || text).trim() || null
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// The managed namer never receives room/system envelopes, secrets, URLs, or
|
|
350
|
-
// host paths. Preserve every clause of the current task so the model—not the
|
|
351
|
-
// brittle local title heuristic—chooses the primary outcome and constraints.
|
|
352
|
-
export function modelTerminalNameInput(text) {
|
|
353
|
-
const body = withoutHostReferences(withoutSecrets(managedTaskText(text) || ''))
|
|
354
|
-
.replace(/[`*_>#()[\]{}]/g, ' ')
|
|
355
|
-
.replace(/\s+/g, ' ')
|
|
356
|
-
.trim()
|
|
357
|
-
if (!body || containsSecret(body)) return null
|
|
358
|
-
return truncateUtf8(body)
|
|
359
|
-
}
|
|
1
|
+
import{nativeClaudeProviderAccessFailed as e}from"./cross-terminal.mjs";import t from"compromise/two";const i=new Set(["a","an","and","are","at","be","can","could","for","from","how","i","in","is","it","just","like","maybe","me","my","of","on","or","our","please","something","that","the","this","to","we","with","would","you","do","much","better","really","need","want","your","their","its","also","all","away","before","cause","does","dont","don't","fully","has","have","now","occasionally","return","second","so","sometimes","still","than","then","there","what","when","where","which","who","whenever","why","working"]),s=/^(?:new )?(?:agent |coding )?(?:terminal|task|lane|session|work)$/i,n=/^(?:#{1,6}\s*)?(?:task|goal|objective|request|mission|assignment|problem(?: to solve)?|what to do)(?:\s*:\s*(.*)|\s*)$/i,r=/^(?:#{1,6}\s+|(?:context|background|constraints?|inputs?|outputs?|acceptance|success criteria|steps?|assumptions?|open questions?|notes?|references?|environment)\s*:)/i,o=/^(?:context|background|for reference|here(?:'s| is)|note|current(?:ly)?|example|environment|room now|constraints?|acceptance|success criteria)\b/i,a=/^(?:because|cause|since|so that|this is because)\b/i,l=/^(?:users? can still|keep|must|never|should|without)\b/i,c=/^(?:after|before|by|during|for|from|in|instead(?: of)?|on|through|until|with|without)\b/i,m=/\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,u=/^(?: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,p=/(?<![\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,d=/^---\s*(?:the person's next message|current person request(?:\s*\([^\n]*\))?|side task)\s*---\s*$/gim,g=/^(?:#{1,6}\s*)?CURRENT PERSON REQUEST(?:\s*\([^\n]*\))?\s*:?\s*/gim,b=new Set(["how","what","when","where","which","who","whom","whose","why"]),h=new Set(["avoid","choose","include","need","use","want"]),f=new Set(["avoid","choose","include","use"]),w=e=>new Set([e?.text,e?.normal,e?.machine,e?.implicit].map(e=>String(e||"").trim().toLowerCase()).filter(Boolean)),v=(e,i=null)=>{const s=String(e||"").trim(),n=i||t(s).json({terms:!0}).flatMap(e=>e.terms||[]),r=n.filter(e=>String(e?.text||"").trim()).map(e=>({text:e.text,concepts:w(e),tags:new Set(e.tags||[])})),o=r.some(e=>[...e.concepts].some(e=>b.has(e))),a=r.some(e=>e.tags.has("QuestionWord")),l=n.some(e=>Array.isArray(e?.tags)&&e.tags.includes("Negative")),c=n.some(e=>[...w(e)].some(e=>h.has(e)));return{text:s,visible:r,informationRequest:o&&(a||/\?\s*$/.test(s)),negativePreference:l&&c}},x=Object.freeze([{title:"Fix",score:100,re:/\b(?:fix|fixes|fixed|fixing|repair|repairs|repaired|repairing)\b/i},{title:"Harden",score:98,re:/\b(?:harden|hardens|hardened|hardening)\b/i},{title:"Snap",score:96,re:/\b(?:snap|snaps|snapped|snapping)\b/i},{title:"Improve",score:94,re:/\b(?:improve|improves|improved|improving|optimi[sz](?:e|es|ed|ing)|\bbetter\b)\b/i},{title:"Rework",score:93,re:/\b(?:rework|reworks|reworked|reworking)\b/i},{title:"Implement",score:92,re:/\b(?:implement|implements|implemented|implementing)\b/i},{title:"Build",score:90,re:/\b(?:build|builds|built|building|create|creates|created|creating)\b/i},{title:"Add",score:88,re:/\b(?:add|adds|added|adding|wire|wires|wired|wiring)\b/i},{title:"Replace",score:87,re:/\b(?:replace|replaces|replaced|replacing|migrate|migrates|migrated|migrating)\b/i},{title:"Rename",score:86,re:/\b(?:rename|renames|renamed|renaming)\b/i},{title:"Name",score:85,re:/\b(?:name|names|named|naming)\b/i},{title:"Refactor",score:84,re:/\b(?:refactor|refactors|refactored|refactoring|simplify|simplifies|simplified|simplifying)\b/i},{title:"Remove",score:83,re:/\b(?:remove|removes|removed|removing)\b/i},{title:"Update",score:82,re:/\b(?:update|updates|updated|updating|change|changes|changed|changing)\b/i},{title:"Debug",score:76,re:/\b(?:debug|debugs|debugged|debugging|diagnose|diagnoses|diagnosed|diagnosing|trace|traces|traced|tracing)\b/i},{title:"Audit",score:70,re:/\b(?:audit|audits|audited|auditing|check|checks|checked|checking|investigate|investigates|investigated|investigating|review|reviews|reviewed|reviewing|test|tests|tested|testing|verify|verifies|verified|verifying)\b/i}]),k=e=>{const t=String(e||"");let i=null;for(const e of x){const s=t.match(e.re);if(!s)continue;const n={...e,index:s.index,match:s[0]};(!i||n.score>i.score||n.score===i.score&&n.index<i.index)&&(i=n)}return i},S=e=>String(e||"").replace(p," "),y=e=>{p.lastIndex=0;const t=p.test(String(e||""));return p.lastIndex=0,t},_=e=>(e.replace(/[?#].*$/,"").replace(/[),.;:]+$/,"").split(/[\\/]/).filter(Boolean).at(-1)||"").replace(/\.(?:[cm]?[jt]sx?|tsx?|json|md|html?|css|scss|py|rb|rs|go|java|kt|swift|sh|ya?ml|toml)$/i,"").replace(/[-_.]+/g," "),$=e=>String(e||"").replace(/\bhttps?:\/\/[^\s<>"'`]+/gi," ").replace(/(^|[\s("'`])((?:\.{0,2}\/)[^\s<>"'`]+)/g,(e,t,i)=>`${t}${_(i)}`).replace(/\b[A-Za-z]:\\[^\s<>"'`]+/g,e=>_(e)).replace(/\b(?:[\p{L}\p{N}_.-]+\/)+[\p{L}\p{N}_.-]+\.(?:[cm]?[jt]sx?|tsx?|json|md|html?|css|scss|py|rb|rs|go|java|kt|swift|sh|ya?ml|toml)\b/giu,e=>_(e)),j=e=>{let t=S(String(e||"").slice(0,24e3));const i=[...t.matchAll(d)].at(-1);return i&&(t=t.slice(i.index+i[0].length)),t.replace(/<(?:thinkpool-context|system-reminder|environment_context|recommended_plugins)\b[^>]*>[\s\S]*?<\/(?:thinkpool-context|system-reminder|environment_context|recommended_plugins)>/gi," ").replace(/^\s*\[[^\]\n]{1,300}\]\s*/g,"").replace(/```[^\n]*\n[\s\S]*?(?:```|$)/g," ").replace(/\r/g,"").trim()},z=e=>{const t=e.split("\n"),i=[];for(let e=0;e<t.length;e++){const s=t[e].trim().match(n);if(!s)continue;const o=[];s[1]?.trim()&&o.push(s[1].trim());for(let i=e+1;i<t.length&&o.join(" ").length<1200;i++){const e=t[i].trim();if(r.test(e)||n.test(e))break;e&&o.push(e.replace(/^[-*]\s*/,""))}const a=o.join(" ").trim();a&&i.push(a)}return i.at(-1)||null};export function extractTerminalTask(e){const i=j(e);return i&&(z(i)||(e=>{const i=String(e||"").replace(/,\s*(?:but|however)\s+/gi,"\n").split(/\n+|;\s*/).flatMap(e=>{const i=e.trim();if(!i)return[];const s=t(i).json({terms:!0});return s.length?s.map(e=>v(e.text,e.terms||[])):[v(i)]}).filter(e=>e.text);let s=null;for(const e of i){const t=e.text.replace(/^[-*#>\d.)\s]+/,"").trim();if(!t||/^(?:https?:\/\/|\/|[A-Za-z]:\\)/.test(t))continue;const i=t===e.text?e:v(t),n=k(t);let r=n?.score||0;u.test(t)&&(r+=18),i.informationRequest&&(r+=36),m.test(t)&&(r+=/\b(?:crash|fail|flash|stuck|wrong)\w*\b/i.test(t)?36:26),i.visible.length>=3&&i.visible.length<=24?r+=10:i.visible.length>40&&(r-=14),o.test(t)&&(r-=30),a.test(t)&&(r-=34),l.test(t)&&(r-=45),i.negativePreference&&(r-=70),n||u.test(t)||i.informationRequest||!c.test(t)||(r-=24),(!s||r>s.score)&&(s={clause:t,score:r})}return s?.clause||i[0]?.text||null})(i)||i).slice(0,1600).trim()||null}export function cleanTerminalName(t){const i=String(t||"");if(y(i))return null;let n=i.trim().split(/\r?\n/,1)[0].replace(/^\s*(?:[-*#>]+|title\s*:?)\s*/i,"").replace(/^["'`]+|["'`]+$/g,"").replace(/[^\p{L}\p{N}+#&.' -]+/gu," ").replace(/\s+/g," ").replace(/[.,;:!?]+$/u,"").trim();if(!n||s.test(n)||e(n))return null;if(/^(?:error|failed|sorry|unable|i (?:cannot|can't)|rate limit|request failed)\b/i.test(n))return null;n.length>48&&(n=n.slice(0,48).replace(/\s+\S*$/,"").trim());const r=n.split(/\s+/).filter(Boolean),o=1===r.length&&/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u.test(n);return r.length<2&&!o||r.length>3?null:n||null}const N=e=>{const t=e.toLowerCase();return"thinkpool"===t||"thinkpool.io"===t?"ThinkPool":"serverside"===t?"Server-Side":/^(?:api|ci|css|db|html|llm|mcp|npm|sdk|ui|ux)$/i.test(e)?t.toUpperCase():/^[A-Z\d+#.-]{2,}$/.test(e)||/\d/.test(e)?e:e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()},A=(e,t=[])=>{const s=String(e||"").match(/[\p{L}\p{N}][\p{L}\p{N}+#.'-]*/gu)||[],n=new Set,r=[];for(let e of s){const s=e.toLowerCase();if(i.has(s)||/^https?$/i.test(e))continue;if(t.some(t=>t.test(e)))continue;"flashes"!==s&&"flashing"!==s||(e="flash"),"failing"!==s&&"failed"!==s&&"fails"!==s||(e="failure");const o=e.toLowerCase().replace(/(?:s|ed|ing)$/i,"");o&&!n.has(o)&&(n.add(o),r.push(e))}return r};export function fallbackTerminalName(e){return(e=>{let t=String(e||"").replace(/^\s*(?:hey|hi|okay|ok|so)\b[,:!]?\s*/i,"").replace(/^\s*(?:return to (?:working on )?|can|could|would|will)\s+(?:we\s+|you\s+)?/i,"").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,"").trim();if(!t)return null;const i=v(t);if(i.negativePreference){const e=A((e=>{let t=-1;for(let i=0;i<e.visible.length;i++)[...e.visible[i].concepts].some(e=>f.has(e))&&(t=i);return t<0&&(t=e.visible.findIndex(e=>[...e.concepts].some(e=>"need"===e||"want"===e))),t>=0?e.visible.slice(t+1).map(e=>e.text).join(" "):e.text})(i)).slice(0,2);return e.length?cleanTerminalName(["Avoid",...e.map(N)].join(" ")):null}const s=i.informationRequest;s&&(t=(e=>{const t=e.visible.findIndex(e=>[...e.concepts].some(e=>b.has(e)));return t>=0?e.visible.slice(t+1).map(e=>e.text).join(" "):e.text})(i).trim());const n=t;t=t.replace(/\s*,?\s+(?:so (?:that|you|we|it)\b|because\b|cause\b|if you know what i mean\b)[\s\S]*$/i,"").replace(/\s+for\s+(?:a|an|one|\d+)\s+(?:moment|second|minute|hour)s?\b[\s\S]*$/i,"").trim();const r=m.test(t),o=k(t);let a=r?"Fix":o?.title;if(!a&&/\bclos(?:e|es|ed|ing)\b/i.test(t)&&(a="Close"),!a&&/\bopen(?:s|ed|ing)?\b/i.test(t)&&(a="Open"),/\bautomatic(?:ally)?\b[\s\S]{0,24}\bnam(?:e|ing)\b/i.test(n)&&/\bterminals?\b/i.test(n)){if(/\bcontext\b[\s\S]{0,24}\broom\b|\broom\b[\s\S]{0,24}\bcontext\b/i.test(n))return"Contextual Terminal Names";a="Auto-Name"}if("Snap"===a&&/\bterminal row\b/i.test(n)&&/\bview\b/i.test(n))return"Snap Terminal Row";if("Snap"===a&&/\bclicked terminal\b/i.test(n)&&/\bview\b/i.test(n))return"Snap Clicked Terminal";let l=[],c=t;if(o){const e=t.slice(0,o.index);c=t.slice(o.index+o.match.length);const i=k(e);i?.score<=76&&(l=A(e.slice(i.index+i.match.length),[i.re]).slice(0,3)),c=c.split(/\s*,?\s+(?:and|then)\s+(?:(?:also)\s+)?(?=(?:add|build|create|implement|remove|replace|update|wire)\b)/i,1)[0],c=c.replace(/\b(?:and|then)\s+(?:audit|check|debug|investigate|review|test|verify)\b/gi," ")}const u=o?[o.re]:[];let p=A(c,u);if(p.length||(p=A(t,u)),s){const e=p.findIndex(e=>/^available$/i.test(e));e>0&&(p=[p[e],...p.slice(0,e),...p.slice(e+1)])}const d=[...l,...p].filter((e,t,i)=>i.findIndex(t=>t.toLowerCase()===e.toLowerCase())===t).slice(0,a?2:3);return d.length?cleanTerminalName([a,...d.map(N)].filter(Boolean).join(" ")):null})($(S(extractTerminalTask(e)||"")).replace(/[`*_>#()[\]{}]/g," ").slice(0,1600))}export function modelTerminalNameInput(e){const t=$(S((e=>{let t=j(e);if(!t)return null;const i=[...t.matchAll(g)].at(-1);return i&&(t=t.slice(i.index+i[0].length)),(z(t)||t).trim()||null})(e)||"")).replace(/[`*_>#()[\]{}]/g," ").replace(/\s+/g," ").trim();return!t||y(t)?null:((e,t=1600)=>{let i="",s=0;for(const n of String(e||"")){const e=Buffer.byteLength(n,"utf8");if(s+e>t)break;i+=n,s+=e}return i.trim()})(t)}
|
|
@@ -1,70 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
/* Reconcile durable terminal rows after a bridge has restored its on-disk roster.
|
|
4
|
-
|
|
5
|
-
A clean close is reaped by reap-terminal.mjs. This module closes the other
|
|
6
|
-
lifecycle boundary: a crash/abort can remove the live session file without a
|
|
7
|
-
clean-close event, leaving a permanent dormant DB row. A stable public host id
|
|
8
|
-
makes the bridge authoritative only for its own rows; foreign/null-host rows are
|
|
9
|
-
deliberately preserved for handover and mixed-version compatibility. */
|
|
10
|
-
|
|
11
|
-
const outcome = (extra = {}) => ({ reaped: [], ...extra })
|
|
12
|
-
|
|
13
|
-
export async function reconcileTerminalRows ({
|
|
14
|
-
room,
|
|
15
|
-
hostId,
|
|
16
|
-
liveIds = [],
|
|
17
|
-
token,
|
|
18
|
-
supabaseUrl,
|
|
19
|
-
anonKey,
|
|
20
|
-
shuttingDown = false,
|
|
21
|
-
fetchImpl = fetch,
|
|
22
|
-
onFailure,
|
|
23
|
-
} = {}) {
|
|
24
|
-
if (shuttingDown) return outcome({ reason: 'shutdown' })
|
|
25
|
-
if (!token) return outcome({ reason: 'anon' })
|
|
26
|
-
if (!room) return outcome({ reason: 'no-room' })
|
|
27
|
-
if (!hostId) return outcome({ reason: 'no-host-id' })
|
|
28
|
-
|
|
29
|
-
const fail = (result) => {
|
|
30
|
-
try { onFailure?.(result) } catch { /* observability cannot break restore */ }
|
|
31
|
-
return outcome(result)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const query = `${supabaseUrl}/rest/v1/code_terminals?select=id,host_id` +
|
|
36
|
-
`&session_code=eq.${encodeURIComponent(room)}` +
|
|
37
|
-
`&host_id=eq.${encodeURIComponent(hostId)}`
|
|
38
|
-
const response = await fetchImpl(query, {
|
|
39
|
-
headers: { apikey: anonKey, Authorization: `Bearer ${token}` },
|
|
40
|
-
})
|
|
41
|
-
if (!response?.ok) {
|
|
42
|
-
return fail({ reason: 'error', error: `list HTTP ${response?.status ?? 'unknown'}`, status: response?.status })
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const rows = await response.json()
|
|
46
|
-
const live = new Set(liveIds || [])
|
|
47
|
-
const stale = (Array.isArray(rows) ? rows : [])
|
|
48
|
-
.filter((row) => row?.id && row.host_id === hostId && !live.has(row.id))
|
|
49
|
-
.map((row) => row.id)
|
|
50
|
-
|
|
51
|
-
const reaped = []
|
|
52
|
-
for (const id of stale) {
|
|
53
|
-
const deletion = await reapTerminalRow({
|
|
54
|
-
id, token, supabaseUrl, anonKey, fetchImpl,
|
|
55
|
-
})
|
|
56
|
-
if (!deletion.reaped) {
|
|
57
|
-
return fail({
|
|
58
|
-
reason: 'error',
|
|
59
|
-
error: `delete ${deletion.error || deletion.reason}`,
|
|
60
|
-
status: deletion.status,
|
|
61
|
-
reaped,
|
|
62
|
-
})
|
|
63
|
-
}
|
|
64
|
-
reaped.push(id)
|
|
65
|
-
}
|
|
66
|
-
return { reason: 'ok', reaped }
|
|
67
|
-
} catch (error) {
|
|
68
|
-
return fail({ reason: 'error', error: error?.message || String(error) })
|
|
69
|
-
}
|
|
70
|
-
}
|
|
1
|
+
import{reapTerminalRow as r}from"./reap-terminal.mjs";const e=(r={})=>({reaped:[],...r});export async function reconcileTerminalRows({room:o,hostId:t,liveIds:n=[],token:s,supabaseUrl:a,anonKey:i,shuttingDown:u=!1,fetchImpl:c=fetch,onFailure:d}={}){if(u)return e({reason:"shutdown"});if(!s)return e({reason:"anon"});if(!o)return e({reason:"no-room"});if(!t)return e({reason:"no-host-id"});const h=r=>{try{d?.(r)}catch{}return e(r)};try{const e=`${a}/rest/v1/code_terminals?select=id,host_id&session_code=eq.${encodeURIComponent(o)}&host_id=eq.${encodeURIComponent(t)}`,u=await c(e,{headers:{apikey:i,Authorization:`Bearer ${s}`}});if(!u?.ok)return h({reason:"error",error:`list HTTP ${u?.status??"unknown"}`,status:u?.status});const d=await u.json(),p=new Set(n||[]),f=(Array.isArray(d)?d:[]).filter(r=>r?.id&&r.host_id===t&&!p.has(r.id)).map(r=>r.id),l=[];for(const e of f){const o=await r({id:e,token:s,supabaseUrl:a,anonKey:i,fetchImpl:c});if(!o.reaped)return h({reason:"error",error:`delete ${o.error||o.reason}`,status:o.status,reaped:l});l.push(e)}return{reason:"ok",reaped:l}}catch(r){return h({reason:"error",error:r?.message||String(r)})}}
|
|
@@ -1,85 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { readFileSync } from 'node:fs'
|
|
3
|
-
|
|
4
|
-
const REGISTRY_URL = new URL('./thinkpool-capabilities.json', import.meta.url)
|
|
5
|
-
|
|
6
|
-
function deepFreeze(value) {
|
|
7
|
-
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value
|
|
8
|
-
Object.freeze(value)
|
|
9
|
-
for (const child of Object.values(value)) deepFreeze(child)
|
|
10
|
-
return value
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function stableValue(value) {
|
|
14
|
-
if (Array.isArray(value)) return value.map(stableValue)
|
|
15
|
-
if (!value || typeof value !== 'object') return value
|
|
16
|
-
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stableValue(value[key])]))
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function computePromptBundleHash(registry) {
|
|
20
|
-
return createHash('sha256').update(JSON.stringify(stableValue(registry))).digest('hex')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function validatePromptRegistry(registry) {
|
|
24
|
-
const errors = []
|
|
25
|
-
if (!Number.isInteger(registry?.schemaVersion) || registry.schemaVersion < 1) errors.push('schemaVersion must be a positive integer')
|
|
26
|
-
if (!Number.isInteger(registry?.bundleVersion) || registry.bundleVersion < 1) errors.push('bundleVersion must be a positive integer')
|
|
27
|
-
if (!Array.isArray(registry?.contracts) || registry.contracts.length === 0) errors.push('contracts must be a non-empty array')
|
|
28
|
-
const ids = new Set()
|
|
29
|
-
const routeIds = new Set()
|
|
30
|
-
for (const contract of registry?.contracts || []) {
|
|
31
|
-
if (!contract?.id || !/^[a-z0-9-]+$/.test(contract.id)) errors.push(`invalid contract id: ${contract?.id || '<missing>'}`)
|
|
32
|
-
else if (ids.has(contract.id)) errors.push(`duplicate contract id: ${contract.id}`)
|
|
33
|
-
else ids.add(contract.id)
|
|
34
|
-
if (!Number.isInteger(contract?.version) || contract.version < 1) errors.push(`${contract?.id || '<missing>'}: version must be a positive integer`)
|
|
35
|
-
if (!Array.isArray(contract?.impact)) errors.push(`${contract?.id || '<missing>'}: impact must be an array`)
|
|
36
|
-
if (!Array.isArray(contract?.evidence) || contract.evidence.length === 0) errors.push(`${contract?.id || '<missing>'}: evidence must be a non-empty array`)
|
|
37
|
-
if (!(contract?.routes?.length || contract?.globalPrompt || contract?.expandedPrompt || contract?.interactionPrompt)) {
|
|
38
|
-
errors.push(`${contract?.id || '<missing>'}: at least one compiled prompt surface is required`)
|
|
39
|
-
}
|
|
40
|
-
for (const impact of contract?.impact || []) {
|
|
41
|
-
if (!impact?.path || typeof impact.path !== 'string') errors.push(`${contract.id}: every impact entry needs a path`)
|
|
42
|
-
if (impact?.diffPattern) {
|
|
43
|
-
try { new RegExp(impact.diffPattern, 'i') } catch { errors.push(`${contract.id}: invalid impact regex for ${impact.path}`) }
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
for (const evidence of contract?.evidence || []) {
|
|
47
|
-
if (!evidence?.path || !evidence?.pattern) errors.push(`${contract.id}: every evidence entry needs path and pattern`)
|
|
48
|
-
else {
|
|
49
|
-
try { new RegExp(evidence.pattern, 'm') } catch { errors.push(`${contract.id}: invalid evidence regex for ${evidence.path}`) }
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
for (const route of contract?.routes || []) {
|
|
53
|
-
if (!route?.id || routeIds.has(route.id)) errors.push(`duplicate or missing route id: ${route?.id || '<missing>'}`)
|
|
54
|
-
else routeIds.add(route.id)
|
|
55
|
-
if (!Array.isArray(route?.tools) || route.tools.length === 0) errors.push(`${contract.id}/${route?.id}: tools must be non-empty`)
|
|
56
|
-
if (!route?.prompt || !route?.trigger) errors.push(`${contract.id}/${route?.id}: prompt and trigger are required`)
|
|
57
|
-
else {
|
|
58
|
-
try { new RegExp(route.trigger, 'i') } catch { errors.push(`${contract.id}/${route.id}: trigger is not a valid regex`) }
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return errors
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const parsed = JSON.parse(readFileSync(REGISTRY_URL, 'utf8'))
|
|
66
|
-
const registryErrors = validatePromptRegistry(parsed)
|
|
67
|
-
if (registryErrors.length) throw new Error(`Invalid ThinkPool prompt registry:\n- ${registryErrors.join('\n- ')}`)
|
|
68
|
-
|
|
69
|
-
export const THINKPOOL_PROMPT_REGISTRY = deepFreeze(parsed)
|
|
70
|
-
export const THINKPOOL_CAPABILITY_CONTRACTS = THINKPOOL_PROMPT_REGISTRY.contracts
|
|
71
|
-
export const THINKPOOL_CAPABILITY_ROUTES = deepFreeze(THINKPOOL_CAPABILITY_CONTRACTS.flatMap((contract) =>
|
|
72
|
-
(contract.routes || []).map(({ prompt, ...route }) => ({ ...route, rule: prompt }))))
|
|
73
|
-
|
|
74
|
-
export function thinkPoolCapabilityContract(id) {
|
|
75
|
-
const contract = THINKPOOL_CAPABILITY_CONTRACTS.find((entry) => entry.id === id)
|
|
76
|
-
if (!contract) throw new Error(`Unknown ThinkPool capability contract: ${id}`)
|
|
77
|
-
return contract
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export const THINKPOOL_PROMPT_BUNDLE = deepFreeze({
|
|
81
|
-
schemaVersion: THINKPOOL_PROMPT_REGISTRY.schemaVersion,
|
|
82
|
-
version: THINKPOOL_PROMPT_REGISTRY.bundleVersion,
|
|
83
|
-
hash: computePromptBundleHash(THINKPOOL_PROMPT_REGISTRY),
|
|
84
|
-
contracts: THINKPOOL_CAPABILITY_CONTRACTS.map(({ id, version }) => ({ id, version })),
|
|
85
|
-
})
|
|
1
|
+
import{createHash as e}from"node:crypto";import{readFileSync as t}from"node:fs";const r=new URL("./thinkpool-capabilities.json",import.meta.url);function i(e){if(!e||"object"!=typeof e||Object.isFrozen(e))return e;Object.freeze(e);for(const t of Object.values(e))i(t);return e}function n(e){return Array.isArray(e)?e.map(n):e&&"object"==typeof e?Object.fromEntries(Object.keys(e).sort().map(t=>[t,n(e[t])])):e}export function computePromptBundleHash(t){return e("sha256").update(JSON.stringify(n(t))).digest("hex")}export function validatePromptRegistry(e){const t=[];(!Number.isInteger(e?.schemaVersion)||e.schemaVersion<1)&&t.push("schemaVersion must be a positive integer"),(!Number.isInteger(e?.bundleVersion)||e.bundleVersion<1)&&t.push("bundleVersion must be a positive integer"),Array.isArray(e?.contracts)&&0!==e.contracts.length||t.push("contracts must be a non-empty array");const r=new Set,i=new Set;for(const n of e?.contracts||[]){n?.id&&/^[a-z0-9-]+$/.test(n.id)?r.has(n.id)?t.push(`duplicate contract id: ${n.id}`):r.add(n.id):t.push(`invalid contract id: ${n?.id||"<missing>"}`),(!Number.isInteger(n?.version)||n.version<1)&&t.push(`${n?.id||"<missing>"}: version must be a positive integer`),Array.isArray(n?.impact)||t.push(`${n?.id||"<missing>"}: impact must be an array`),Array.isArray(n?.evidence)&&0!==n.evidence.length||t.push(`${n?.id||"<missing>"}: evidence must be a non-empty array`),n?.routes?.length||n?.globalPrompt||n?.expandedPrompt||n?.interactionPrompt||t.push(`${n?.id||"<missing>"}: at least one compiled prompt surface is required`);for(const e of n?.impact||[])if(e?.path&&"string"==typeof e.path||t.push(`${n.id}: every impact entry needs a path`),e?.diffPattern)try{new RegExp(e.diffPattern,"i")}catch{t.push(`${n.id}: invalid impact regex for ${e.path}`)}for(const e of n?.evidence||[])if(e?.path&&e?.pattern)try{new RegExp(e.pattern,"m")}catch{t.push(`${n.id}: invalid evidence regex for ${e.path}`)}else t.push(`${n.id}: every evidence entry needs path and pattern`);for(const e of n?.routes||[])if(!e?.id||i.has(e.id)?t.push(`duplicate or missing route id: ${e?.id||"<missing>"}`):i.add(e.id),Array.isArray(e?.tools)&&0!==e.tools.length||t.push(`${n.id}/${e?.id}: tools must be non-empty`),e?.prompt&&e?.trigger)try{new RegExp(e.trigger,"i")}catch{t.push(`${n.id}/${e.id}: trigger is not a valid regex`)}else t.push(`${n.id}/${e?.id}: prompt and trigger are required`)}return t}const o=JSON.parse(t(r,"utf8")),s=validatePromptRegistry(o);if(s.length)throw new Error(`Invalid ThinkPool prompt registry:\n- ${s.join("\n- ")}`);export const THINKPOOL_PROMPT_REGISTRY=i(o);export const THINKPOOL_CAPABILITY_CONTRACTS=THINKPOOL_PROMPT_REGISTRY.contracts;export const THINKPOOL_CAPABILITY_ROUTES=i(THINKPOOL_CAPABILITY_CONTRACTS.flatMap(e=>(e.routes||[]).map(({prompt:e,...t})=>({...t,rule:e}))));export function thinkPoolCapabilityContract(e){const t=THINKPOOL_CAPABILITY_CONTRACTS.find(t=>t.id===e);if(!t)throw new Error(`Unknown ThinkPool capability contract: ${e}`);return t}export const THINKPOOL_PROMPT_BUNDLE=i({schemaVersion:THINKPOOL_PROMPT_REGISTRY.schemaVersion,version:THINKPOOL_PROMPT_REGISTRY.bundleVersion,hash:computePromptBundleHash(THINKPOOL_PROMPT_REGISTRY),contracts:THINKPOOL_CAPABILITY_CONTRACTS.map(({id:e,version:t})=>({id:e,version:t}))});
|