thinkpool-pair 0.7.363 → 0.7.365

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 (117) 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 -6240
  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 -0
  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 -91
  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 -0
  73. package/mockup-delivery.mjs +1 -57
  74. package/model-prices.mjs +1 -113
  75. package/package.json +15 -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-capabilities.json +11 -2
  111. package/thinkpool-prompt-contracts.mjs +1 -85
  112. package/thinkpool-room-prompt.mjs +1 -187
  113. package/transcript-sanitize.mjs +1 -332
  114. package/turn-stall.mjs +1 -61
  115. package/update-gate.mjs +1 -53
  116. package/viewport.mjs +1 -810
  117. package/worker-completion.mjs +1 -57
package/pair-bus.mjs CHANGED
@@ -1,98 +1 @@
1
- // Deterministic request broker for the cross-machine pair bus.
2
- //
3
- // Supabase channel.send() is asynchronous and a channel is not usable until its
4
- // subscription callback reports SUBSCRIBED. Keeping those facts in one tested
5
- // primitive prevents a joining/dead channel from impersonating a reachable peer
6
- // and prevents a fast reply from arriving before its waiter exists.
7
-
8
- export const PAIR_BUS_STATUS = Object.freeze({
9
- JOINING: 'joining',
10
- SUBSCRIBED: 'subscribed',
11
- ERROR: 'error',
12
- CLOSED: 'closed',
13
- })
14
-
15
- export const pairBusStatusFromRealtime = (status) => {
16
- if (status === 'SUBSCRIBED') return PAIR_BUS_STATUS.SUBSCRIBED
17
- if (status === 'CLOSED') return PAIR_BUS_STATUS.CLOSED
18
- if (status === 'CHANNEL_ERROR' || status === 'TIMED_OUT') return PAIR_BUS_STATUS.ERROR
19
- return PAIR_BUS_STATUS.JOINING
20
- }
21
-
22
- export const subscribedPairChannels = (channels) =>
23
- [...(channels?.values?.() || [])].filter((entry) =>
24
- entry?.status === PAIR_BUS_STATUS.SUBSCRIBED && entry?.channel?.send)
25
-
26
- export async function sendPairBroadcast (channels, event, payload) {
27
- const results = await Promise.all(subscribedPairChannels(channels).map(async ({ channel }) => {
28
- try { return (await channel.send({ type: 'broadcast', event, payload })) === 'ok' }
29
- catch { return false }
30
- }))
31
- return results.filter(Boolean).length
32
- }
33
-
34
- export function createPairBusBroker ({ channels, randomId, setTimer = setTimeout, clearTimer = clearTimeout } = {}) {
35
- if (!channels?.values || typeof randomId !== 'function') throw new TypeError('channels and randomId are required')
36
- const waiters = new Map()
37
-
38
- const resolve = (payload) => {
39
- if (!payload?.nonce) return false
40
- const waiter = waiters.get(payload.nonce)
41
- if (!waiter) return false
42
- if (waiter.mode === 'collect') { waiter.acc.push(payload); return true }
43
- clearTimer(waiter.timer); waiters.delete(payload.nonce); waiter.finish(payload)
44
- return true
45
- }
46
-
47
- const requestFirst = (event, payload = {}, timeoutMs) => new Promise((finish) => {
48
- const nonce = randomId()
49
- const timer = setTimer(() => { waiters.delete(nonce); finish(null) }, timeoutMs)
50
- timer?.unref?.()
51
- waiters.set(nonce, { mode: 'first', finish, timer })
52
- void sendPairBroadcast(channels, event, { ...payload, nonce }).then((sent) => {
53
- if (sent || !waiters.has(nonce)) return
54
- clearTimer(timer); waiters.delete(nonce); finish(null)
55
- })
56
- })
57
-
58
- const requestCollect = (event, payload = {}, windowMs) => new Promise((finish) => {
59
- const nonce = randomId()
60
- const acc = []
61
- const timer = setTimer(() => { waiters.delete(nonce); finish(acc) }, windowMs)
62
- timer?.unref?.()
63
- waiters.set(nonce, { mode: 'collect', acc, finish, timer })
64
- void sendPairBroadcast(channels, event, { ...payload, nonce }).then((sent) => {
65
- if (sent || !waiters.has(nonce)) return
66
- clearTimer(timer); waiters.delete(nonce); finish(acc)
67
- })
68
- })
69
-
70
- const close = () => {
71
- for (const [nonce, waiter] of waiters) {
72
- clearTimer(waiter.timer)
73
- waiter.finish(waiter.mode === 'collect' ? waiter.acc : null)
74
- waiters.delete(nonce)
75
- }
76
- }
77
-
78
- return Object.freeze({ resolve, requestFirst, requestCollect, close, pendingCount: () => waiters.size })
79
- }
80
-
81
- // The database is the authorization/discovery source; bus replies are the live
82
- // reachability source. Paired rooms remain visible while their host bridge is
83
- // offline and become online only when that host actually answers on the bus.
84
- export function mergePairRoomRoster ({ authorized = [], replies = [], myUid, localCodes = [] } = {}) {
85
- const local = new Set(localCodes)
86
- const rooms = new Map()
87
- for (const row of authorized) {
88
- if (!row?.code || row.owner_id === myUid || local.has(row.code)) continue
89
- rooms.set(row.code, { code: row.code, name: row.name || null, online: false })
90
- }
91
- for (const reply of replies) {
92
- for (const row of Array.isArray(reply?.rooms) ? reply.rooms : []) {
93
- if (!row?.code || local.has(row.code)) continue
94
- rooms.set(row.code, { code: row.code, name: row.name || rooms.get(row.code)?.name || null, host: row.host || null, online: true })
95
- }
96
- }
97
- return [...rooms.values()]
98
- }
1
+ export const PAIR_BUS_STATUS=Object.freeze({JOINING:"joining",SUBSCRIBED:"subscribed",ERROR:"error",CLOSED:"closed"});export const pairBusStatusFromRealtime=e=>"SUBSCRIBED"===e?PAIR_BUS_STATUS.SUBSCRIBED:"CLOSED"===e?PAIR_BUS_STATUS.CLOSED:"CHANNEL_ERROR"===e||"TIMED_OUT"===e?PAIR_BUS_STATUS.ERROR:PAIR_BUS_STATUS.JOINING;export const subscribedPairChannels=e=>[...e?.values?.()||[]].filter(e=>e?.status===PAIR_BUS_STATUS.SUBSCRIBED&&e?.channel?.send);export async function sendPairBroadcast(e,n,o){return(await Promise.all(subscribedPairChannels(e).map(async({channel:e})=>{try{return"ok"===await e.send({type:"broadcast",event:n,payload:o})}catch{return!1}}))).filter(Boolean).length}export function createPairBusBroker({channels:e,randomId:n,setTimer:o=setTimeout,clearTimer:r=clearTimeout}={}){if(!e?.values||"function"!=typeof n)throw new TypeError("channels and randomId are required");const t=new Map;return Object.freeze({resolve:e=>{if(!e?.nonce)return!1;const n=t.get(e.nonce);return!!n&&("collect"===n.mode?(n.acc.push(e),!0):(r(n.timer),t.delete(e.nonce),n.finish(e),!0))},requestFirst:(s,c={},a)=>new Promise(i=>{const l=n(),d=o(()=>{t.delete(l),i(null)},a);d?.unref?.(),t.set(l,{mode:"first",finish:i,timer:d}),sendPairBroadcast(e,s,{...c,nonce:l}).then(e=>{!e&&t.has(l)&&(r(d),t.delete(l),i(null))})}),requestCollect:(s,c={},a)=>new Promise(i=>{const l=n(),d=[],u=o(()=>{t.delete(l),i(d)},a);u?.unref?.(),t.set(l,{mode:"collect",acc:d,finish:i,timer:u}),sendPairBroadcast(e,s,{...c,nonce:l}).then(e=>{!e&&t.has(l)&&(r(u),t.delete(l),i(d))})}),close:()=>{for(const[e,n]of t)r(n.timer),n.finish("collect"===n.mode?n.acc:null),t.delete(e)},pendingCount:()=>t.size})}export function mergePairRoomRoster({authorized:e=[],replies:n=[],myUid:o,localCodes:r=[]}={}){const t=new Set(r),s=new Map;for(const n of e)n?.code&&n.owner_id!==o&&!t.has(n.code)&&s.set(n.code,{code:n.code,name:n.name||null,online:!1});for(const e of n)for(const n of Array.isArray(e?.rooms)?e.rooms:[])n?.code&&!t.has(n.code)&&s.set(n.code,{code:n.code,name:n.name||s.get(n.code)?.name||null,host:n.host||null,online:!0});return[...s.values()]}
@@ -1,89 +1 @@
1
- // Durable authority check for the bridge's public `code-perm` transport.
2
- // Realtime is only a wake-up hint: the owner-authenticated REST reads below
3
- // must reproduce the resolved item and its unique delivery-attempt record before
4
- // a local pending action is allowed to move.
5
-
6
- const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
7
- const SAFE_KEY = /^[A-Za-z0-9][A-Za-z0-9._:@-]{7,159}$/
8
- const FINAL_STATUS = new Set(['resolved', 'denied'])
9
-
10
- const denied = (code) => Object.freeze({ ok: false, code })
11
-
12
- export function verifyPairControlDeliveryAuthority ({ item, attempts, payload, roomCode, bridgeId } = {}) {
13
- if (!item || !payload || !UUID.test(String(payload.controlItemId || '')) ||
14
- !SAFE_KEY.test(String(payload.attemptKey || '')) ||
15
- !Number.isSafeInteger(payload.controlVersion) || payload.controlVersion < 1) {
16
- return denied('invalid_delivery_hint')
17
- }
18
- const context = item.request_context
19
- const resolution = item.resolution_payload
20
- if (item.id !== payload.controlItemId || item.session_code !== String(roomCode || '').toUpperCase() ||
21
- item.item_kind !== 'approval' || !['approval', 'dispatch'].includes(item.action_kind) ||
22
- item.resolution_policy !== 'either_member' || item.required_actor_id != null ||
23
- !FINAL_STATUS.has(item.status) || Number(item.version) !== payload.controlVersion ||
24
- item.bridge_authority_id !== bridgeId || item.local_authority_id !== payload.term ||
25
- !context || context.permission_id !== payload.id || context.terminal_ref !== payload.term ||
26
- !resolution || resolution.decision !== payload.decision || !item.resolved_by) {
27
- return denied('authority_mismatch')
28
- }
29
- if ((item.status === 'resolved' && !['approved', 'answered'].includes(item.resolution_code)) ||
30
- (item.status === 'denied' && item.resolution_code !== 'rejected')) {
31
- return denied('resolution_mismatch')
32
- }
33
- const matching = Array.isArray(attempts) ? attempts.filter((event) =>
34
- event?.actor_id === item.resolved_by && Number(event.item_version) === Number(item.version) &&
35
- event.correlation_key === payload.attemptKey && event.payload?.data?.transport === 'code-perm') : []
36
- if (matching.length !== 1) return denied('delivery_attempt_unverified')
37
- const durableResponder = resolution.responder
38
- const responder = durableResponder && durableResponder.id === item.resolved_by
39
- ? {
40
- id: item.resolved_by,
41
- name: typeof durableResponder.name === 'string' && durableResponder.name.trim()
42
- ? durableResponder.name.trim().slice(0, 120)
43
- : null,
44
- }
45
- : { id: item.resolved_by, name: null }
46
- return Object.freeze({
47
- ok: true,
48
- code: 'durable_delivery_authorized',
49
- decision: resolution.decision,
50
- // App Server input is recovered from the durable resolution only. The public
51
- // broadcast intentionally remains a wake-up hint and cannot supply answers.
52
- answers: resolution.answers && typeof resolution.answers === 'object' ? resolution.answers : {},
53
- responder: Object.freeze(responder),
54
- itemId: item.id,
55
- version: Number(item.version),
56
- attemptKey: payload.attemptKey,
57
- })
58
- }
59
-
60
- export async function fetchPairControlDeliveryAuthority ({
61
- fetchImpl = globalThis.fetch, supabaseUrl, anonKey, token, roomCode, bridgeId, payload,
62
- } = {}) {
63
- if (typeof fetchImpl !== 'function' || !supabaseUrl || !anonKey || !token || !roomCode || !bridgeId ||
64
- !UUID.test(String(payload?.controlItemId || '')) || !SAFE_KEY.test(String(payload?.attemptKey || ''))) {
65
- return denied('authority_unavailable')
66
- }
67
- const headers = { apikey: anonKey, Authorization: `Bearer ${token}` }
68
- const itemUrl = new URL('/rest/v1/code_pair_control_items', supabaseUrl)
69
- itemUrl.searchParams.set('id', `eq.${payload.controlItemId}`)
70
- itemUrl.searchParams.set('session_code', `eq.${String(roomCode).toUpperCase()}`)
71
- itemUrl.searchParams.set('select', 'id,session_code,item_kind,action_kind,resolution_policy,required_actor_id,status,version,resolution_code,resolution_payload,resolved_by,bridge_authority_id,local_authority_id,request_context')
72
- const eventUrl = new URL('/rest/v1/code_pair_control_events', supabaseUrl)
73
- eventUrl.searchParams.set('item_id', `eq.${payload.controlItemId}`)
74
- eventUrl.searchParams.set('event_type', 'eq.delivery_attempted')
75
- eventUrl.searchParams.set('correlation_key', `eq.${payload.attemptKey}`)
76
- eventUrl.searchParams.set('select', 'actor_id,item_version,correlation_key,payload')
77
- eventUrl.searchParams.set('limit', '2')
78
- try {
79
- const [itemResponse, eventResponse] = await Promise.all([
80
- fetchImpl(itemUrl, { headers }), fetchImpl(eventUrl, { headers }),
81
- ])
82
- if (!itemResponse.ok || !eventResponse.ok) return denied('authority_read_failed')
83
- const [items, attempts] = await Promise.all([itemResponse.json(), eventResponse.json()])
84
- if (!Array.isArray(items) || items.length !== 1 || !Array.isArray(attempts)) return denied('authority_read_invalid')
85
- return verifyPairControlDeliveryAuthority({ item: items[0], attempts, payload, roomCode, bridgeId })
86
- } catch {
87
- return denied('authority_read_failed')
88
- }
89
- }
1
+ const e=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,t=/^[A-Za-z0-9][A-Za-z0-9._:@-]{7,159}$/,r=new Set(["resolved","denied"]),o=e=>Object.freeze({ok:!1,code:e});export function verifyPairControlDeliveryAuthority({item:i,attempts:a,payload:s,roomCode:n,bridgeId:d}={}){if(!i||!s||!e.test(String(s.controlItemId||""))||!t.test(String(s.attemptKey||""))||!Number.isSafeInteger(s.controlVersion)||s.controlVersion<1)return o("invalid_delivery_hint");const l=i.request_context,c=i.resolution_payload;if(!(i.id===s.controlItemId&&i.session_code===String(n||"").toUpperCase()&&"approval"===i.item_kind&&["approval","dispatch"].includes(i.action_kind)&&"either_member"===i.resolution_policy&&null==i.required_actor_id&&r.has(i.status)&&Number(i.version)===s.controlVersion&&i.bridge_authority_id===d&&i.local_authority_id===s.term&&l&&l.permission_id===s.id&&l.terminal_ref===s.term&&c&&c.decision===s.decision&&i.resolved_by))return o("authority_mismatch");if("resolved"===i.status&&!["approved","answered"].includes(i.resolution_code)||"denied"===i.status&&"rejected"!==i.resolution_code)return o("resolution_mismatch");if(1!==(Array.isArray(a)?a.filter(e=>e?.actor_id===i.resolved_by&&Number(e.item_version)===Number(i.version)&&e.correlation_key===s.attemptKey&&"code-perm"===e.payload?.data?.transport):[]).length)return o("delivery_attempt_unverified");const _=c.responder,m=_&&_.id===i.resolved_by?{id:i.resolved_by,name:"string"==typeof _.name&&_.name.trim()?_.name.trim().slice(0,120):null}:{id:i.resolved_by,name:null};return Object.freeze({ok:!0,code:"durable_delivery_authorized",decision:c.decision,answers:c.answers&&"object"==typeof c.answers?c.answers:{},responder:Object.freeze(m),itemId:i.id,version:Number(i.version),attemptKey:s.attemptKey})}export async function fetchPairControlDeliveryAuthority({fetchImpl:r=globalThis.fetch,supabaseUrl:i,anonKey:a,token:s,roomCode:n,bridgeId:d,payload:l}={}){if(!("function"==typeof r&&i&&a&&s&&n&&d&&e.test(String(l?.controlItemId||""))&&t.test(String(l?.attemptKey||""))))return o("authority_unavailable");const c={apikey:a,Authorization:`Bearer ${s}`},_=new URL("/rest/v1/code_pair_control_items",i);_.searchParams.set("id",`eq.${l.controlItemId}`),_.searchParams.set("session_code",`eq.${String(n).toUpperCase()}`),_.searchParams.set("select","id,session_code,item_kind,action_kind,resolution_policy,required_actor_id,status,version,resolution_code,resolution_payload,resolved_by,bridge_authority_id,local_authority_id,request_context");const m=new URL("/rest/v1/code_pair_control_events",i);m.searchParams.set("item_id",`eq.${l.controlItemId}`),m.searchParams.set("event_type","eq.delivery_attempted"),m.searchParams.set("correlation_key",`eq.${l.attemptKey}`),m.searchParams.set("select","actor_id,item_version,correlation_key,payload"),m.searchParams.set("limit","2");try{const[e,t]=await Promise.all([r(_,{headers:c}),r(m,{headers:c})]);if(!e.ok||!t.ok)return o("authority_read_failed");const[i,a]=await Promise.all([e.json(),t.json()]);return Array.isArray(i)&&1===i.length&&Array.isArray(a)?verifyPairControlDeliveryAuthority({item:i[0],attempts:a,payload:l,roomCode:n,bridgeId:d}):o("authority_read_invalid")}catch{return o("authority_read_failed")}}
@@ -1,105 +1 @@
1
- const MAX_QUERY = 160
2
- const MAX_RESULTS = 8
3
- const MAX_RECORDS = 48
4
- const MAX_EVENTS = 800
5
- const MAX_EXCERPT = 260
6
- const SECRET_VALUE = /(?:sk[_-](?:proj[_-])?[a-z0-9_-]{8,}|gsk_[a-z0-9_-]{8,}|AIza[a-z0-9_-]{8,}|gh[pousr]_[a-z0-9_-]{8,}|github_pat_[a-z0-9_]{20,}|glpat-[a-z0-9_-]{8,}|x(?:ox[baprsce]|app)-[a-z0-9_-]{8,}|(?:AKIA|ASIA)[0-9A-Z]{16}|npm_[a-z0-9]{24,}|bearer\s+[a-z0-9._-]{8,}|(?:api[_-]?key|access[_-]?token|auth(?:orization)?|token|secret)\s*[=:]\s*\S+)/i
7
- const PRIVATE_WORDS = /(?:system\s+prompt|hidden\s+reasoning|chain[ -]of[ -]thought|\bpassword\b|private[_ -]?key|\.env\b)/i
8
- const HOST_PATH = /(?:^|[\s=("'`])(?:~\/|\/(?:Users|home|var|tmp|private|workspace)\/|[A-Za-z]:\\|\\\\)[^\s)"'`]*/i
9
-
10
- function textContent(content) {
11
- if (typeof content === 'string') return content
12
- if (!Array.isArray(content)) return ''
13
- return content.map((item) => typeof item === 'string' ? item : item?.text || '').join(' ')
14
- }
15
-
16
- function safeExcerpt(value) {
17
- if (typeof value !== 'string') return null
18
- const line = value.replace(/\s+/g, ' ').trim()
19
- if (!line || SECRET_VALUE.test(line) || PRIVATE_WORDS.test(line) || HOST_PATH.test(line)) return null
20
- return line.slice(0, MAX_EXCERPT)
21
- }
22
-
23
- function eventCandidates(event) {
24
- if (!event || typeof event !== 'object') return []
25
- if (event.kind === 'flow-review-receipt') {
26
- const scope = event.scope
27
- return [
28
- `Flow ${event.outcome || 'review'}: ${event.taskKey || ''}`,
29
- ...(event.checks || []).map((line) => `Check: ${line}`),
30
- ...(event.reasons || []).map((line) => `Finding: ${line}`),
31
- ...((scope?.changed || []).map((line) => `Edited: ${line}`)),
32
- ...((scope?.unexpected || []).map((line) => `Unexpected: ${line}`)),
33
- ]
34
- }
35
- if (event.kind === 'assistant') {
36
- const out = []
37
- for (const block of event.blocks || []) {
38
- if (block?.type === 'text') out.push(block.text)
39
- if (block?.type === 'tool_use') {
40
- const name = String(block.name || '')
41
- if (/^(?:bash|exec_command|command_execution)$/i.test(name)) {
42
- const command = block.input?.command ?? block.input?.cmd
43
- if (command) out.push(`Command: ${command}`)
44
- }
45
- }
46
- }
47
- return out
48
- }
49
- if (event.kind === 'you') return [event.text]
50
- if (event.kind === 'tool_result') return [`Result: ${textContent(event.content)}`]
51
- if (event.kind === 'result') return [event.resultText]
52
- return []
53
- }
54
-
55
- function queryParts(query) {
56
- const text = String(query || '').replace(/\s+/g, ' ').trim()
57
- if (text.length < 2 || text.length > MAX_QUERY) throw new TypeError(`past-work query must contain 2-${MAX_QUERY} characters`)
58
- if (SECRET_VALUE.test(text) || PRIVATE_WORDS.test(text) || HOST_PATH.test(text)) return null
59
- return { text, lower: text.toLowerCase(), tokens: text.toLowerCase().split(/[^a-z0-9._/-]+/).filter((token) => token.length > 1) }
60
- }
61
-
62
- export function searchPastWork(records, query, { room, limit = MAX_RESULTS } = {}) {
63
- const parsed = queryParts(query)
64
- if (!parsed) return []
65
- const boundedLimit = Math.max(1, Math.min(MAX_RESULTS, Number(limit) || MAX_RESULTS))
66
- const results = []
67
- for (const record of (Array.isArray(records) ? records : []).slice(0, MAX_RECORDS)) {
68
- const terminal = safeExcerpt(record?.name) || 'Unnamed terminal'
69
- const events = Array.isArray(record?.log) ? record.log.slice(-MAX_EVENTS) : []
70
- for (const event of events) {
71
- for (const raw of eventCandidates(event)) {
72
- const excerpt = safeExcerpt(raw)
73
- if (!excerpt) continue
74
- const lower = excerpt.toLowerCase()
75
- const tokenHits = parsed.tokens.filter((token) => lower.includes(token)).length
76
- if (!lower.includes(parsed.lower) && (!parsed.tokens.length || tokenHits !== parsed.tokens.length)) continue
77
- results.push({
78
- room: String(room || '').trim() || 'current',
79
- terminal,
80
- terminalId: String(record?.id || '').slice(0, 80),
81
- ts: Number(event?.ts) || Number(record?.savedAt) || 0,
82
- seq: Number.isFinite(Number(event?.seq)) ? Number(event.seq) : null,
83
- excerpt,
84
- score: (lower.includes(parsed.lower) ? 10 : 0) + tokenHits + (excerpt.startsWith('Command:') ? 0 : 1),
85
- })
86
- }
87
- }
88
- }
89
- return results
90
- .sort((a, b) => b.score - a.score || b.ts - a.ts)
91
- .slice(0, boundedLimit)
92
- .map(({ score, ...result }) => result)
93
- }
94
-
95
- export function formatPastWorkSearch(results, query) {
96
- const safeQuery = safeExcerpt(String(query || '').trim())
97
- if (!results.length) return safeQuery
98
- ? `No cited past work matched “${safeQuery}” in this room.`
99
- : 'No cited past work matched that privacy-filtered query in this room.'
100
- return results.map((result, index) => {
101
- const date = result.ts ? new Date(result.ts).toISOString().slice(0, 10) : 'date unavailable'
102
- const event = result.seq == null ? 'saved transcript' : `event ${result.seq}`
103
- return `${index + 1}. ${result.excerpt}\n [Room ${result.room} · ${result.terminal} · ${date} · ${event}]`
104
- }).join('\n')
105
- }
1
+ const t=/(?:sk[_-](?:proj[_-])?[a-z0-9_-]{8,}|gsk_[a-z0-9_-]{8,}|AIza[a-z0-9_-]{8,}|gh[pousr]_[a-z0-9_-]{8,}|github_pat_[a-z0-9_]{20,}|glpat-[a-z0-9_-]{8,}|x(?:ox[baprsce]|app)-[a-z0-9_-]{8,}|(?:AKIA|ASIA)[0-9A-Z]{16}|npm_[a-z0-9]{24,}|bearer\s+[a-z0-9._-]{8,}|(?:api[_-]?key|access[_-]?token|auth(?:orization)?|token|secret)\s*[=:]\s*\S+)/i,e=/(?:system\s+prompt|hidden\s+reasoning|chain[ -]of[ -]thought|\bpassword\b|private[_ -]?key|\.env\b)/i,r=/(?:^|[\s=("'`])(?:~\/|\/(?:Users|home|var|tmp|private|workspace)\/|[A-Za-z]:\\|\\\\)[^\s)"'`]*/i;function n(n){if("string"!=typeof n)return null;const s=n.replace(/\s+/g," ").trim();return!s||t.test(s)||e.test(s)||r.test(s)?null:s.slice(0,260)}function s(t){if(!t||"object"!=typeof t)return[];if("flow-review-receipt"===t.kind){const e=t.scope;return[`Flow ${t.outcome||"review"}: ${t.taskKey||""}`,...(t.checks||[]).map(t=>`Check: ${t}`),...(t.reasons||[]).map(t=>`Finding: ${t}`),...(e?.changed||[]).map(t=>`Edited: ${t}`),...(e?.unexpected||[]).map(t=>`Unexpected: ${t}`)]}if("assistant"===t.kind){const e=[];for(const r of t.blocks||[])if("text"===r?.type&&e.push(r.text),"tool_use"===r?.type){const t=String(r.name||"");if(/^(?:bash|exec_command|command_execution)$/i.test(t)){const t=r.input?.command??r.input?.cmd;t&&e.push(`Command: ${t}`)}}return e}return"you"===t.kind?[t.text]:"tool_result"===t.kind?[`Result: ${e=t.content,"string"==typeof e?e:Array.isArray(e)?e.map(t=>"string"==typeof t?t:t?.text||"").join(" "):""}`]:"result"===t.kind?[t.resultText]:[];var e}export function searchPastWork(o,i,{room:a,limit:c=8}={}){const l=function(n){const s=String(n||"").replace(/\s+/g," ").trim();if(s.length<2||s.length>160)throw new TypeError("past-work query must contain 2-160 characters");return t.test(s)||e.test(s)||r.test(s)?null:{text:s,lower:s.toLowerCase(),tokens:s.toLowerCase().split(/[^a-z0-9._/-]+/).filter(t=>t.length>1)}}(i);if(!l)return[];const m=Math.max(1,Math.min(8,Number(c)||8)),u=[];for(const t of(Array.isArray(o)?o:[]).slice(0,48)){const e=n(t?.name)||"Unnamed terminal",r=Array.isArray(t?.log)?t.log.slice(-800):[];for(const o of r)for(const r of s(o)){const s=n(r);if(!s)continue;const i=s.toLowerCase(),c=l.tokens.filter(t=>i.includes(t)).length;(i.includes(l.lower)||l.tokens.length&&c===l.tokens.length)&&u.push({room:String(a||"").trim()||"current",terminal:e,terminalId:String(t?.id||"").slice(0,80),ts:Number(o?.ts)||Number(t?.savedAt)||0,seq:Number.isFinite(Number(o?.seq))?Number(o.seq):null,excerpt:s,score:(i.includes(l.lower)?10:0)+c+(s.startsWith("Command:")?0:1)})}}return u.sort((t,e)=>e.score-t.score||e.ts-t.ts).slice(0,m).map(({score:t,...e})=>e)}export function formatPastWorkSearch(t,e){const r=n(String(e||"").trim());return t.length?t.map((t,e)=>{const r=t.ts?new Date(t.ts).toISOString().slice(0,10):"date unavailable",n=null==t.seq?"saved transcript":`event ${t.seq}`;return`${e+1}. ${t.excerpt}\n [Room ${t.room} · ${t.terminal} · ${r} · ${n}]`}).join("\n"):r?`No cited past work matched “${r}” in this room.`:"No cited past work matched that privacy-filtered query in this room."}
package/plan-meters.mjs CHANGED
@@ -1,144 +1 @@
1
- /* Plan / rate-limit meters for the /usage report.
2
- *
3
- * WHERE THE NUMBERS COME FROM. `GET https://api.anthropic.com/api/oauth/usage`
4
- * with the Claude Code OAuth access token — the SAME endpoint the CLI's own /usage
5
- * screen reads. So the percentages and, critically, the RESET TIMES are the
6
- * server's, not ours. We never infer a 5-hour block boundary from transcript
7
- * timestamps (the ccusage approach): an inferred reset is a number that looks
8
- * authoritative and is wrong, which is exactly the bug this module exists to avoid.
9
- *
10
- * READ-ONLY, AND DELIBERATELY SO. We read the access token and we never refresh it.
11
- * Refreshing rotates the refresh token; two processes rotating against one credential
12
- * store is how the bridge's login died before (memory: thinkpool_bridge_login_headless_recovery).
13
- * An expired token therefore degrades to "plan meters unavailable" rather than
14
- * quietly re-authenticating behind the user's back.
15
- *
16
- * DEGRADE HONESTLY. Every failure — no token, non-Claude provider, network error,
17
- * 401, shape change, a reset timestamp in the past — returns null, and the caller
18
- * prints "plan meters unavailable". A stale or invented meter is worse than no meter.
19
- */
20
-
21
- import { execFileSync } from 'node:child_process'
22
- import { readFileSync } from 'node:fs'
23
- import { homedir } from 'node:os'
24
- import { join } from 'node:path'
25
-
26
- const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage'
27
- const CACHE_MS = 45_000
28
- const FETCH_TIMEOUT_MS = 4_000
29
-
30
- // ── token ────────────────────────────────────────────────────────────────────
31
- // macOS keeps Claude Code's credentials in the login keychain; Linux writes
32
- // ~/.claude/.credentials.json. Same JSON shape either way: { claudeAiOauth: { accessToken } }.
33
- // Any failure here is a normal, expected state (BYOK lane, API-key auth, logged out).
34
- export function readOAuthToken (deps = {}) {
35
- const {
36
- platform = process.platform,
37
- env = process.env,
38
- exec = execFileSync,
39
- read = readFileSync,
40
- home = homedir()
41
- } = deps
42
- if (env.THINKPOOL_OAUTH_TOKEN) return env.THINKPOOL_OAUTH_TOKEN // test/override seam
43
- let raw = null
44
- try {
45
- if (platform === 'darwin') {
46
- raw = exec('security', ['find-generic-password', '-s', 'Claude Code-credentials', '-w'],
47
- { encoding: 'utf8', timeout: 3000, stdio: ['ignore', 'pipe', 'ignore'] })
48
- } else {
49
- raw = read(join(home, '.claude', '.credentials.json'), 'utf8')
50
- }
51
- const tok = JSON.parse(raw)?.claudeAiOauth?.accessToken
52
- return (typeof tok === 'string' && tok.startsWith('sk-ant-oat')) ? tok : null
53
- } catch { return null }
54
- }
55
-
56
- // ── fetch ────────────────────────────────────────────────────────────────────
57
- export async function fetchPlanUsage (token, fetchImpl = globalThis.fetch) {
58
- if (!token) return null
59
- const ac = new AbortController()
60
- const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS)
61
- try {
62
- const res = await fetchImpl(USAGE_URL, {
63
- method: 'GET',
64
- headers: { authorization: `Bearer ${token}`, 'anthropic-beta': 'oauth-2025-04-20' },
65
- signal: ac.signal
66
- })
67
- if (!res.ok) return null // 401 (expired) / 403 / 5xx → unavailable, never a guess
68
- return await res.json()
69
- } catch { return null } finally { clearTimeout(timer) }
70
- }
71
-
72
- // ── format (pure) ────────────────────────────────────────────────────────────
73
- const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
74
-
75
- // A reset renders in LOCAL time. Beyond ~20h out (the weekly meter) a bare HH:MM is
76
- // ambiguous, so it carries a weekday. Returns null for an unparseable timestamp OR one
77
- // already in the past — a past reset means the payload is stale, and printing "resets
78
- // 10:10" when 10:10 has been and gone is precisely the lie we refuse to tell.
79
- export function fmtReset (iso, now) {
80
- const t = new Date(iso).getTime()
81
- if (!Number.isFinite(t) || t <= now.getTime()) return null
82
- // ROUND to the nearest minute, don't floor. The endpoint stamps `resets_at` with
83
- // sub-second jitter around the true boundary — two calls seconds apart returned
84
- // 10:10:00.355Z and 10:09:59.873Z for the same reset. Flooring renders those as
85
- // 12:10 and 12:09, so the meter visibly flickers a minute back and forth on a
86
- // surface whose whole job is to be the trustworthy number. Observed 2026-07-10.
87
- const d = new Date(Math.round(t / 60000) * 60000)
88
- const hhmm = `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
89
- return (t - now.getTime()) > 20 * 3600 * 1000 ? `${DAYS[d.getDay()]} ${hhmm}` : hhmm
90
- }
91
-
92
- const fmtIn = (ms) => {
93
- const m = Math.max(0, Math.round(ms / 60000))
94
- return m >= 60 ? `${Math.floor(m / 60)}h${String(m % 60).padStart(2, '0')}m` : `${m}m`
95
- }
96
-
97
- // Render one block ("5h" / "week") or null if it can't be stated truthfully.
98
- function block (label, node, now, withRelative) {
99
- const pct = node && typeof node.utilization === 'number' ? Math.round(node.utilization) : null
100
- if (pct == null) return null
101
- const at = fmtReset(node.resets_at, now)
102
- if (!at) return `${label}: ${pct}%` // percent is still true even if the reset isn't usable
103
- const rel = withRelative ? ` (${fmtIn(new Date(node.resets_at).getTime() - now.getTime())})` : ''
104
- return `${label}: ${pct}% · resets ${at}${rel}`
105
- }
106
-
107
- // json → "plan · 5h: 10% · resets 12:10 (1h04m) · week: 26% · resets Tue 11:00"
108
- // Returns null when nothing truthful can be said, so the caller prints the honest
109
- // "plan meters unavailable" instead of a half-line.
110
- export function formatPlanMeters (json, now = new Date()) {
111
- if (!json || typeof json !== 'object') return null
112
- const parts = [block('5h', json.five_hour, now, true), block('week', json.seven_day, now, false)]
113
- // A scoped weekly cap (e.g. a per-model ceiling) only matters while it's the binding
114
- // one — surface it, named, so a lane that's about to hit a model-specific wall sees it.
115
- const scoped = Array.isArray(json.limits)
116
- ? json.limits.find((l) => l && l.is_active && l.kind === 'weekly_scoped' && typeof l.percent === 'number')
117
- : null
118
- if (scoped) {
119
- const name = scoped.scope?.model?.display_name
120
- parts.push(`${name ? `week ${name}` : 'week scoped'}: ${Math.round(scoped.percent)}%`)
121
- }
122
- const live = parts.filter(Boolean)
123
- return live.length ? `plan · ${live.join(' · ')}` : null
124
- }
125
-
126
- // ── cached entry point ───────────────────────────────────────────────────────
127
- let cache = { at: 0, line: null }
128
-
129
- // The ONE line bridge.mjs appends under the token/cost line. Cached ~45s so a user
130
- // spamming /usage doesn't hammer the endpoint. Never throws.
131
- export async function planMeterLine (deps = {}) {
132
- const { now = () => Date.now(), clock = () => new Date(), token = readOAuthToken, fetchUsage = fetchPlanUsage } = deps
133
- const t = now()
134
- if (cache.line !== null && (t - cache.at) < CACHE_MS) return cache.line
135
- let line = 'plan meters unavailable'
136
- try {
137
- const json = await fetchUsage(token())
138
- line = formatPlanMeters(json, clock()) || 'plan meters unavailable'
139
- } catch { /* stays unavailable */ }
140
- cache = { at: t, line }
141
- return line
142
- }
143
-
144
- export function _resetCache () { cache = { at: 0, line: null } } // test seam
1
+ import{execFileSync as e}from"node:child_process";import{readFileSync as t}from"node:fs";import{homedir as n}from"node:os";import{join as r}from"node:path";export function readOAuthToken(a={}){const{platform:o=process.platform,env:i=process.env,exec:l=e,read:s=t,home:u=n()}=a;if(i.THINKPOOL_OAUTH_TOKEN)return i.THINKPOOL_OAUTH_TOKEN;let c=null;try{c="darwin"===o?l("security",["find-generic-password","-s","Claude Code-credentials","-w"],{encoding:"utf8",timeout:3e3,stdio:["ignore","pipe","ignore"]}):s(r(u,".claude",".credentials.json"),"utf8");const e=JSON.parse(c)?.claudeAiOauth?.accessToken;return"string"==typeof e&&e.startsWith("sk-ant-oat")?e:null}catch{return null}}export async function fetchPlanUsage(e,t=globalThis.fetch){if(!e)return null;const n=new AbortController,r=setTimeout(()=>n.abort(),4e3);try{const r=await t("https://api.anthropic.com/api/oauth/usage",{method:"GET",headers:{authorization:`Bearer ${e}`,"anthropic-beta":"oauth-2025-04-20"},signal:n.signal});return r.ok?await r.json():null}catch{return null}finally{clearTimeout(r)}}const a=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];export function fmtReset(e,t){const n=new Date(e).getTime();if(!Number.isFinite(n)||n<=t.getTime())return null;const r=new Date(6e4*Math.round(n/6e4)),o=`${String(r.getHours()).padStart(2,"0")}:${String(r.getMinutes()).padStart(2,"0")}`;return n-t.getTime()>72e6?`${a[r.getDay()]} ${o}`:o}function o(e,t,n,r){const a=t&&"number"==typeof t.utilization?Math.round(t.utilization):null;if(null==a)return null;const o=fmtReset(t.resets_at,n);return o?`${e}: ${a}% · resets ${o}${r?` (${(e=>{const t=Math.max(0,Math.round(e/6e4));return t>=60?`${Math.floor(t/60)}h${String(t%60).padStart(2,"0")}m`:`${t}m`})(new Date(t.resets_at).getTime()-n.getTime())})`:""}`:`${e}: ${a}%`}export function formatPlanMeters(e,t=new Date){if(!e||"object"!=typeof e)return null;const n=[o("5h",e.five_hour,t,!0),o("week",e.seven_day,t,!1)],r=Array.isArray(e.limits)?e.limits.find(e=>e&&e.is_active&&"weekly_scoped"===e.kind&&"number"==typeof e.percent):null;if(r){const e=r.scope?.model?.display_name;n.push(`${e?`week ${e}`:"week scoped"}: ${Math.round(r.percent)}%`)}const a=n.filter(Boolean);return a.length?`plan · ${a.join(" · ")}`:null}let i={at:0,line:null};export async function planMeterLine(e={}){const{now:t=()=>Date.now(),clock:n=()=>new Date,token:r=readOAuthToken,fetchUsage:a=fetchPlanUsage}=e,o=t();if(null!==i.line&&o-i.at<45e3)return i.line;let l="plan meters unavailable";try{l=formatPlanMeters(await a(r()),n())||"plan meters unavailable"}catch{}return i={at:o,line:l},l}export function _resetCache(){i={at:0,line:null}}