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.
Files changed (116) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +20 -0
  3. package/abort-turn-barrier.mjs +1 -23
  4. package/account.mjs +1 -1437
  5. package/acp-client.mjs +1 -140
  6. package/agent-detect.mjs +1 -28
  7. package/agent-notify.mjs +1 -142
  8. package/agent-visibility.mjs +1 -67
  9. package/auth-store.mjs +1 -120
  10. package/bridge.mjs +1 -6266
  11. package/byok-detect.mjs +1 -126
  12. package/claude-command-catalog.mjs +1 -91
  13. package/claude-session.mjs +1 -1519
  14. package/code-event-contract.mjs +1 -118
  15. package/codex-app-server.mjs +1 -340
  16. package/codex-commands.mjs +1 -83
  17. package/codex-event-mapper.mjs +1 -232
  18. package/codex-images.mjs +1 -69
  19. package/codex-mcp-http.mjs +1 -131
  20. package/codex-session.mjs +1 -1295
  21. package/command-catalog.mjs +1 -118
  22. package/command-guidance.mjs +1 -8
  23. package/context-contract.mjs +1 -95
  24. package/context-windows.mjs +1 -107
  25. package/cross-terminal.mjs +1 -789
  26. package/cumulative-event-relay.mjs +1 -53
  27. package/design-edit.mjs +1 -424
  28. package/design-source-contract.mjs +1 -4
  29. package/direct-pair-room.mjs +1 -57
  30. package/dispatch-lease.mjs +1 -37
  31. package/dispatch-permission-cleanup.mjs +1 -86
  32. package/edit-diff.mjs +1 -136
  33. package/error-recovery.mjs +1 -50
  34. package/event-bounds.mjs +1 -121
  35. package/event-delivery-queue.mjs +1 -60
  36. package/event-id.mjs +1 -549
  37. package/evidence-citations.mjs +1 -50
  38. package/evidence-compact.mjs +1 -11
  39. package/flow-assembly.mjs +1 -196
  40. package/flow-budget.mjs +1 -84
  41. package/flow-conductor.mjs +1 -259
  42. package/flow-context-store.mjs +1 -387
  43. package/flow-host-revert.mjs +1 -42
  44. package/flow-models.mjs +1 -139
  45. package/flow-preview.mjs +1 -148
  46. package/flow-receipt.mjs +1 -122
  47. package/flow-redispatch.mjs +1 -72
  48. package/flow-review-gate.mjs +1 -402
  49. package/flow-review-reflect.mjs +1 -115
  50. package/flow-review.mjs +1 -152
  51. package/flow-scope-evidence.mjs +1 -117
  52. package/flow-skill-registry.mjs +1 -140
  53. package/flow-task-graph.mjs +1 -562
  54. package/flow-worktree.mjs +1 -71
  55. package/git-diff-report.mjs +1 -121
  56. package/hermes-delegation-guard.mjs +1 -14
  57. package/hermes-event-mapper.mjs +1 -194
  58. package/hermes-isolation.mjs +1 -61
  59. package/hermes-model-cache.mjs +1 -54
  60. package/hermes-policy.mjs +1 -92
  61. package/hermes-probe.mjs +1 -57
  62. package/hermes-session.mjs +1 -683
  63. package/hermes-setup.mjs +1 -167
  64. package/host-memory.mjs +1 -116
  65. package/interrupted-resume.mjs +1 -95
  66. package/keep-awake.mjs +1 -148
  67. package/key-shape.mjs +1 -49
  68. package/lane-continuation.mjs +1 -83
  69. package/lane-lifecycle.mjs +1 -189
  70. package/lane-worktree.mjs +1 -77
  71. package/launcher.mjs +1 -354
  72. package/mcp-flight-recorder.mjs +1 -79
  73. package/mockup-delivery.mjs +1 -57
  74. package/model-prices.mjs +1 -113
  75. package/package.json +13 -4
  76. package/pair-bus.mjs +1 -98
  77. package/pair-control-authority.mjs +1 -89
  78. package/past-work-search.mjs +1 -105
  79. package/plan-meters.mjs +1 -144
  80. package/presence.mjs +1 -191
  81. package/privacy-report.mjs +1 -108
  82. package/provider-resilience.mjs +1 -356
  83. package/provider.mjs +1 -133
  84. package/providers.mjs +1 -491
  85. package/publish-guard.mjs +2 -0
  86. package/publish-manifest.json +129 -0
  87. package/question-response.mjs +1 -58
  88. package/reap-terminal.mjs +1 -68
  89. package/recap.mjs +1 -297
  90. package/replay-transport.mjs +1 -64
  91. package/repo-search.mjs +1 -190
  92. package/review-check.mjs +1 -182
  93. package/runtime-contract.mjs +1 -93
  94. package/runtime-registry.mjs +1 -64
  95. package/runtime-session.mjs +1 -20
  96. package/scheduled-run-admission.mjs +1 -364
  97. package/scheduled-runs.mjs +1 -268
  98. package/sdk-admission.mjs +1 -9
  99. package/sdk-smoke.mjs +1 -61
  100. package/serve-consent.mjs +1 -118
  101. package/serve-dir.mjs +1 -40
  102. package/service.mjs +1 -877
  103. package/session-store.mjs +1 -484
  104. package/side-lane.mjs +1 -63
  105. package/supabase-key.mjs +1 -176
  106. package/supervisor-ready.mjs +1 -57
  107. package/switch-provider.mjs +1 -84
  108. package/terminal-name.mjs +1 -359
  109. package/terminal-row-reconcile.mjs +1 -70
  110. package/thinkpool-prompt-contracts.mjs +1 -85
  111. package/thinkpool-room-prompt.mjs +1 -187
  112. package/transcript-sanitize.mjs +1 -332
  113. package/turn-stall.mjs +1 -61
  114. package/update-gate.mjs +1 -53
  115. package/viewport.mjs +1 -810
  116. 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 { reapTerminalRow } from './reap-terminal.mjs'
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 { createHash } from 'node:crypto'
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}))});