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/providers.mjs CHANGED
@@ -1,491 +1 @@
1
- /* ─────────────────────────────────────────────────────────────
2
- providers.mjs — the bridge's LLM-provider REGISTRY (multi-BYOK).
3
-
4
- Slice 1 of the multi-provider BYOK feature (spec:
5
- docs/specs/2026-07-05-llm-provider-registry.md). Lets a user register
6
- extra Anthropic-compatible providers (name + base url + model + API
7
- key) on their OWN bridge; a Code lane can then spawn against any
8
- registered provider by id. Keys live ONLY on the bridge host — they are
9
- never broadcast, never written to the DB, never in an announce.
10
-
11
- This module owns:
12
- • providers.json — the registry ({id,name,baseUrl,model,key,addedAt}[]), 0600
13
- • bridge-key.json — the bridge's RSA keypair, 0600 (host-only)
14
- • seal/unseal — the hybrid envelope the dashboard uses to hand the
15
- bridge a key WITHOUT it ever crossing the wire in the
16
- clear (RSA-OAEP wraps an AES-256-GCM session key).
17
- • env resolution — {ANTHROPIC_BASE_URL,ANTHROPIC_AUTH_TOKEN,ANTHROPIC_MODEL}
18
- for a chosen provider id, matching provider.mjs conventions.
19
-
20
- The built-in `anthropic` provider (the host's regular Claude login) is
21
- IMPLICIT: never stored, never removable, always present. A lane with no
22
- provider id runs the default Claude path unchanged.
23
-
24
- ── Why HYBRID sealing, not raw RSA ──────────────────────────────────
25
- RSA-OAEP-2048/SHA-256 caps plaintext at 190 bytes. A real payload —
26
- {name,baseUrl,model,key} with e.g. an OpenAI `sk-proj-…` key (~164 chars)
27
- plus JSON overhead — blows past that and RSA encrypt throws. So `sealed`
28
- is a hybrid envelope: RSA-OAEP wraps a fresh 32-byte AES key, AES-256-GCM
29
- encrypts the payload. Both ends are WebCrypto-native (browser dashboard
30
- seals, Node bridge unseals). Envelope shape (base64 of UTF8 JSON):
31
- { v:1, ek:base64(RSA-OAEP(aesKeyRaw)), iv:base64(12B), ct:base64(gcmCiphertext||16B-tag) }
32
- WebCrypto's subtle.encrypt returns ciphertext||tag concatenated; Node's
33
- createDecipheriv wants the tag separately, so unseal splits the last 16.
34
-
35
- ── Auth (see account.mjs handlers) ─────────────────────────────────
36
- The account channel `tpacct:<uid>` is a PUBLIC realtime channel (no
37
- config.private:true), so it is NOT RLS-gated — the sender is verified at
38
- the application layer (getUser(jwt).id === owner uid) before any registry
39
- mutation. This module stays transport-agnostic; the caller does auth.
40
- ───────────────────────────────────────────────────────────── */
41
- import os from 'node:os'
42
- import fs from 'node:fs'
43
- import path from 'node:path'
44
- import crypto from 'node:crypto'
45
- import { claudeish } from './event-id.mjs'
46
- import {
47
- DEFAULT_RESILIENCE_POLICY,
48
- resilienceProjection,
49
- resolveAttemptTarget,
50
- validateResiliencePolicy,
51
- } from './provider-resilience.mjs'
52
-
53
- const DIR = path.join(os.homedir(), '.thinkpool-pair')
54
- const REG_FILE = path.join(DIR, 'providers.json')
55
- const KEY_FILE = path.join(DIR, 'bridge-key.json')
56
-
57
- // The built-in provider id — always present, never stored, never removable.
58
- export const BUILTIN_ID = 'anthropic'
59
-
60
- function ensureDir() { try { fs.mkdirSync(DIR, { recursive: true, mode: 0o700 }) } catch { /* noop */ } }
61
-
62
- // ── registry load / save ───────────────────────────────────────────────
63
- /** @returns {Array<{id:string,name:string,baseUrl:string,model?:string,key:string,addedAt:number,resilience?:object}>} */
64
- export function loadProviders() {
65
- try {
66
- const arr = JSON.parse(fs.readFileSync(REG_FILE, 'utf8'))
67
- return Array.isArray(arr) ? arr.filter((p) => p && p.id && p.id !== BUILTIN_ID) : []
68
- } catch { return [] }
69
- }
70
-
71
- function saveProviders(arr) {
72
- ensureDir()
73
- fs.writeFileSync(REG_FILE, JSON.stringify(arr, null, 2), { mode: 0o600 })
74
- // Writing with { mode } only applies on CREATE; harden an existing file too.
75
- try { fs.chmodSync(REG_FILE, 0o600) } catch { /* noop */ }
76
- }
77
-
78
- // ── keypair ─────────────────────────────────────────────────────────────
79
- // Generate an RSA-OAEP-2048 keypair on first run, persist 0600, return the
80
- // public key as SPKI base64 (what the dashboard imports) + the private key
81
- // object (for unseal). Idempotent: a persisted key is reused.
82
- let _keyCache = null
83
- export function ensureKeypair() {
84
- if (_keyCache) return _keyCache
85
- ensureDir()
86
- try {
87
- const saved = JSON.parse(fs.readFileSync(KEY_FILE, 'utf8'))
88
- if (saved?.publicKeyB64 && saved?.privateKeyPem) {
89
- _keyCache = {
90
- publicKeyB64: saved.publicKeyB64,
91
- privateKey: crypto.createPrivateKey({ key: saved.privateKeyPem, format: 'pem' }),
92
- }
93
- return _keyCache
94
- }
95
- } catch { /* generate below */ }
96
-
97
- const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 })
98
- const publicKeyB64 = publicKey.export({ type: 'spki', format: 'der' }).toString('base64')
99
- const privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' })
100
- fs.writeFileSync(KEY_FILE, JSON.stringify({ publicKeyB64, privateKeyPem, createdAt: Date.now() }, null, 2), { mode: 0o600 })
101
- try { fs.chmodSync(KEY_FILE, 0o600) } catch { /* noop */ }
102
- _keyCache = { publicKeyB64, privateKey }
103
- return _keyCache
104
- }
105
-
106
- /** SPKI base64 public key for the announce (`providerPubKey`). */
107
- export function publicKeyB64() { return ensureKeypair().publicKeyB64 }
108
-
109
- // Stable, public machine identity for terminal ownership. `host` is a display
110
- // label and can be "localhost" on multiple computers; this fingerprint is tied
111
- // to the bridge's existing keypair without exposing private material. Accepting
112
- // an explicit public key keeps the identity primitive directly testable.
113
- export function bridgeHostId(pubKey = publicKeyB64()) {
114
- return crypto.createHash('sha256').update(pubKey).digest('hex').slice(0, 32)
115
- }
116
-
117
- // ── seal / unseal (hybrid RSA-OAEP + AES-256-GCM) ───────────────────────
118
- // seal() is here so the UNIT can drive both ends node-side; the real seal
119
- // happens in the browser dashboard via WebCrypto against the same envelope.
120
- /** @param {object} payload @param {string=} pubKeyB64 → base64(envelope JSON) */
121
- export function seal(payload, pubKeyB64) {
122
- const spkiB64 = pubKeyB64 || publicKeyB64()
123
- const publicKey = crypto.createPublicKey({ key: Buffer.from(spkiB64, 'base64'), format: 'der', type: 'spki' })
124
- const aesKey = crypto.randomBytes(32)
125
- const iv = crypto.randomBytes(12)
126
- const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv)
127
- const enc = Buffer.concat([cipher.update(JSON.stringify(payload), 'utf8'), cipher.final()])
128
- const tag = cipher.getAuthTag()
129
- const ek = crypto.publicEncrypt(
130
- { key: publicKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' },
131
- aesKey,
132
- )
133
- const envelope = {
134
- v: 1,
135
- ek: ek.toString('base64'),
136
- iv: iv.toString('base64'),
137
- ct: Buffer.concat([enc, tag]).toString('base64'), // ct||tag, matches WebCrypto
138
- }
139
- return Buffer.from(JSON.stringify(envelope), 'utf8').toString('base64')
140
- }
141
-
142
- /**
143
- * Decrypt a sealed envelope with the bridge private key. Returns the parsed
144
- * payload object, or throws. Callers MUST catch and answer ok:false WITHOUT
145
- * logging the ciphertext or plaintext (security invariant d).
146
- * @param {string} sealedB64 base64(envelope JSON)
147
- */
148
- export function unseal(sealedB64) {
149
- const { privateKey } = ensureKeypair()
150
- const env = JSON.parse(Buffer.from(String(sealedB64 || ''), 'base64').toString('utf8'))
151
- if (!env || env.v !== 1 || !env.ek || !env.iv || !env.ct) throw new Error('bad envelope')
152
- const aesKey = crypto.privateDecrypt(
153
- { key: privateKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' },
154
- Buffer.from(env.ek, 'base64'),
155
- )
156
- const iv = Buffer.from(env.iv, 'base64')
157
- const ctTag = Buffer.from(env.ct, 'base64')
158
- if (ctTag.length < 16) throw new Error('bad ciphertext')
159
- const tag = ctTag.subarray(ctTag.length - 16)
160
- const ct = ctTag.subarray(0, ctTag.length - 16)
161
- const decipher = crypto.createDecipheriv('aes-256-gcm', aesKey, iv)
162
- decipher.setAuthTag(tag)
163
- const dec = Buffer.concat([decipher.update(ct), decipher.final()])
164
- return JSON.parse(dec.toString('utf8'))
165
- }
166
-
167
- // ── CRUD ────────────────────────────────────────────────────────────────
168
- /* Local-LLM exception (2026-07-10): a provider on the SAME machine (LM Studio,
169
- llama.cpp — both speak the Anthropic Messages API natively) is allowed over
170
- plain http and without a key. Both relaxations are LOOPBACK-ONLY:
171
- • https-only stays for every remote host — a key on plaintext http across a
172
- real wire is the leak the rule exists to stop; on 127.0.0.1 there is no wire.
173
- • key-required stays for every remote host — local servers simply don't take
174
- one (resolveProviderEnv synthesizes a placeholder token so the SDK doesn't
175
- fall back to the host's Claude login against a localhost URL). */
176
- export function isLoopbackHost(hostname) {
177
- const h = String(hostname || '').toLowerCase().replace(/^\[|\]$/g, '')
178
- // 127/8 must be a LITERAL IPv4 — '127.evil.io' is a DNS name that can resolve
179
- // anywhere, so a bare startsWith('127.') would be a spoofable hole.
180
- return h === 'localhost' || h === '::1' || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)
181
- }
182
-
183
- function validate({ name, baseUrl, key }) {
184
- if (!name || typeof name !== 'string' || !name.trim()) return 'name is required'
185
- let u
186
- try { u = new URL(String(baseUrl)) } catch { return 'baseUrl must be a valid https url' }
187
- const local = isLoopbackHost(u.hostname)
188
- if (u.protocol !== 'https:' && !(u.protocol === 'http:' && local)) {
189
- return 'baseUrl must be https (plain http is allowed only for localhost)'
190
- }
191
- if (!local && (!key || typeof key !== 'string' || !key.trim())) return 'key is required'
192
- return null
193
- }
194
-
195
- /**
196
- * Append a provider. `fields` = {name, baseUrl, model?, key} (already unsealed).
197
- * @returns {{ok:true,id:string} | {ok:false,error:string}}
198
- */
199
- export function addProvider(fields) {
200
- const err = validate(fields || {})
201
- if (err) return { ok: false, error: err }
202
- const arr = loadProviders()
203
- const id = crypto.randomUUID()
204
- arr.push({
205
- id,
206
- name: String(fields.name).trim(),
207
- baseUrl: String(fields.baseUrl).trim(),
208
- model: fields.model ? String(fields.model).trim() : undefined,
209
- key: fields.key ? String(fields.key).trim() : '', // '' = keyless local provider
210
- addedAt: Date.now(),
211
- })
212
- saveProviders(arr)
213
- return { ok: true, id }
214
- }
215
-
216
- /**
217
- * Add ANOTHER model to an already-registered provider — without the user ever
218
- * retyping the key (Max, 2026-07-09). The registry's unit is a {provider,model}
219
- * pair, not a provider, and `buildSwitchMenu` already groups rows by brand
220
- * ("Z.ai GLM 4.5" / "Z.ai GLM Air" collapse under one "z.ai" header) — so a new
221
- * model is simply a CLONE of the source row with a fresh id and a new model.
222
- * No schema change, no new menu shape.
223
- *
224
- * The clone copies `key` + `baseUrl` HOST-SIDE from the row that already holds
225
- * them, so no key material crosses the wire for this operation — the caller's
226
- * RPC carries no `sealed` envelope at all (see account.mjs `provider-add-model`).
227
- *
228
- * @param {{id:string, model:string, name?:string}} fields id = the SOURCE provider
229
- * @returns {{ok:true,id:string} | {ok:false,error:string}}
230
- */
231
- export function addProviderModel({ id, model, name } = {}) {
232
- if (!id || id === BUILTIN_ID) return { ok: false, error: 'the built-in Anthropic provider has no key to reuse' }
233
- if (!model || typeof model !== 'string' || !model.trim()) return { ok: false, error: 'model is required' }
234
- const arr = loadProviders()
235
- const src = arr.find((p) => p.id === id)
236
- if (!src) return { ok: false, error: 'no such provider' }
237
-
238
- const nextModel = String(model).trim()
239
- // An endpoint+model pair is the identity of a row — a second row for the same
240
- // pair would render as a duplicate menu entry that switches to nothing new.
241
- if (arr.some((p) => p.baseUrl === src.baseUrl && (p.model || '') === nextModel)) {
242
- return { ok: false, error: `${nextModel} is already registered on this provider` }
243
- }
244
-
245
- const nextName = (name && String(name).trim()) || `${brandOf(src.name)} ${nextModel}`.trim()
246
- const newId = crypto.randomUUID()
247
- arr.push({
248
- id: newId,
249
- name: nextName,
250
- baseUrl: src.baseUrl, // copied host-side
251
- model: nextModel,
252
- key: src.key, // copied host-side — never re-sent by the client
253
- // A cloned credential is not consent to retry it. Each row needs its own
254
- // explicit Phase-1 opt-in, even when both rows share the same endpoint/key.
255
- resilience: { ...DEFAULT_RESILIENCE_POLICY, retryOn: [...DEFAULT_RESILIENCE_POLICY.retryOn], circuit: { ...DEFAULT_RESILIENCE_POLICY.circuit }, fallback: [] },
256
- addedAt: Date.now(),
257
- })
258
- saveProviders(arr)
259
- return { ok: true, id: newId }
260
- }
261
-
262
- /**
263
- * First name token — the brand, matching code-constants.js `brandKey`. "Z.ai GLM
264
- * 4.5" → "Z.ai". Used to derive a default name for a cloned row so the switch
265
- * menu groups it under the SAME header as its source.
266
- */
267
- function brandOf(name) {
268
- const first = String(name || '').trim().split(/\s+/)[0]
269
- return first || 'Provider'
270
- }
271
-
272
- /**
273
- * Remove a provider by id. The built-in `anthropic` refuses.
274
- * @returns {{ok:true} | {ok:false,error:string}}
275
- */
276
- export function removeProvider(id) {
277
- if (!id || id === BUILTIN_ID) return { ok: false, error: 'the built-in Anthropic provider cannot be removed' }
278
- const arr = loadProviders()
279
- const next = arr.filter((p) => p.id !== id)
280
- if (next.length === arr.length) return { ok: false, error: 'no such provider' }
281
- saveProviders(next)
282
- return { ok: true }
283
- }
284
-
285
- /* ── the provider GROUP token (Max, 2026-07-09) ──────────────────────────
286
- Two registry rows are the SAME PROVIDER when they point at the same endpoint
287
- with the same key — they differ only in which model they ask it for. That is
288
- the identity that matters: it decides whether the switch menu unites them, and
289
- whether switching between them needs a fresh agent (it doesn't — same env).
290
-
291
- Before this, the clients grouped on `brandKey(name)`: the first whitespace token
292
- of the user's DISPLAY NAME, lowercased. "GLM" → `glm`, "GLM-5.2" → `glm5.2`, so
293
- Max's two models on one z.ai key rendered as two unrelated providers, each
294
- threatening to restart his agent. Identity was being derived from a label.
295
-
296
- `group` is a one-way digest of baseUrl + key, NOT either value. It is safe on
297
- the announce (which deliberately carries neither): a 12-hex prefix reveals only
298
- *sameness*, and preimaging it means preimaging the key. The built-in Anthropic
299
- has no stored key, so it gets the literal 'anthropic' — it groups with nothing.
300
-
301
- NUL-separated so ("https://a.io/x", "key") and ("https://a.io/xkey", "") can't
302
- collide into one group. */
303
- export function providerGroup(p) {
304
- if (!p || p.id === BUILTIN_ID) return BUILTIN_ID
305
- return crypto.createHash('sha256')
306
- .update(`${p.baseUrl || ''}\0${p.key || ''}`)
307
- .digest('hex')
308
- .slice(0, 12)
309
- }
310
-
311
- /**
312
- * Do these two registered providers share an endpoint AND a key? Then a switch
313
- * between them changes only ANTHROPIC_MODEL — the child's env is otherwise
314
- * identical, so the live session can just setModel() and keep its context
315
- * instead of being torn down and recapped. Built-in ↔ custom is never same-env.
316
- */
317
- export function sameProviderEnv(idA, idB) {
318
- const a = effectiveId(idA), b = effectiveId(idB)
319
- if (a === BUILTIN_ID || b === BUILTIN_ID) return a === b // both built-in, or one of them
320
- const arr = loadProviders()
321
- const pa = arr.find((x) => x.id === a), pb = arr.find((x) => x.id === b)
322
- if (!pa || !pb) return false // unknown id → never same
323
- return providerGroup(pa) === providerGroup(pb)
324
- }
325
-
326
- const effectiveId = (p) => (!p || p === BUILTIN_ID) ? BUILTIN_ID : String(p)
327
-
328
- /** The model a registered provider is configured to ask for (null for built-in/unknown). */
329
- export function providerModel(id) {
330
- if (!id || id === BUILTIN_ID) return null
331
- return loadProviders().find((x) => x.id === id)?.model || null
332
- }
333
-
334
- /**
335
- * Persist one custom provider row's Phase-1 same-target policy. This is a
336
- * host-local primitive: account RPC/UI wiring deliberately lives elsewhere.
337
- *
338
- * `policy` may be a complete v1 policy, or a boolean for the conservative
339
- * Phase-1 preset. Fallback declarations are rejected here: alternate targets
340
- * require the later explicit-consent flow, so this API can never turn one on.
341
- */
342
- export function setProviderResiliencePolicy(id, policy) {
343
- if (!id || id === BUILTIN_ID) return { ok: false, error: 'the built-in Anthropic provider cannot use resilience policy' }
344
- const arr = loadProviders()
345
- const provider = arr.find((p) => p.id === id)
346
- if (!provider) return { ok: false, error: 'no such provider' }
347
-
348
- const input = typeof policy === 'boolean'
349
- ? { ...DEFAULT_RESILIENCE_POLICY, enabled: policy, retryOn: [...DEFAULT_RESILIENCE_POLICY.retryOn], circuit: { ...DEFAULT_RESILIENCE_POLICY.circuit }, fallback: [] }
350
- : policy
351
- const checked = validateResiliencePolicy(input, { providers: arr, primaryProviderId: id })
352
- if (!checked.ok) return { ok: false, error: checked.error }
353
- if (checked.value.fallback.length) return { ok: false, error: 'fallback is not available in Phase 1' }
354
- // An enabled controller must have a configured exact model for its safe
355
- // target record. A disabled policy remains valid for legacy/no-model rows.
356
- if (checked.value.enabled && !resolveAttemptTarget({ providers: arr, providerId: id, model: provider.model })) {
357
- return { ok: false, error: 'an enabled resilience policy requires an exact configured model' }
358
- }
359
-
360
- const next = arr.map((p) => p.id === id ? { ...p, resilience: checked.value } : p)
361
- saveProviders(next)
362
- return { ok: true, resilience: resilienceProjection(checked.value) }
363
- }
364
-
365
- /**
366
- * Resolve a custom provider's persisted policy for bridge wiring without ever
367
- * returning host credentials or endpoint data. Missing policy is safely
368
- * disabled; malformed stored policy, built-in, unknown, and model-less targets
369
- * fail closed with null.
370
- */
371
- export function resolveProviderResiliencePolicy(id) {
372
- if (!id || id === BUILTIN_ID) return null
373
- const providers = loadProviders()
374
- const provider = providers.find((p) => p.id === id)
375
- if (!provider) return null
376
- const checked = validateResiliencePolicy(provider.resilience, { providers, primaryProviderId: id })
377
- if (!checked.ok) return null
378
- const target = resolveAttemptTarget({ providers, providerId: id, model: provider.model })
379
- if (!target) return null
380
- return { policy: resilienceProjection(checked.value), target }
381
- }
382
-
383
- // ── read-only projections (never expose the raw key) ────────────────────
384
- /** Masked list for the dashboard: keyHint = last 4 chars only. Built-in first. */
385
- export function listProviders() {
386
- const custom = loadProviders().map((p) => ({
387
- id: p.id,
388
- name: p.name,
389
- model: p.model || null,
390
- group: providerGroup(p),
391
- keyHint: keyHint(p.key),
392
- resilience: resilienceProjection(p.resilience),
393
- }))
394
- return [{ id: BUILTIN_ID, name: 'Anthropic (Claude)', model: null, group: BUILTIN_ID, keyHint: null, resilience: null }, ...custom]
395
- }
396
-
397
- /** Name-only projection for the announce/presence payload — NO key, NO baseUrl. */
398
- export function announceProviders() {
399
- const custom = loadProviders().map((p) => ({ id: p.id, name: p.name, model: p.model || null, group: providerGroup(p) }))
400
- return [{ id: BUILTIN_ID, name: 'Anthropic (Claude)', model: null, group: BUILTIN_ID }, ...custom]
401
- }
402
-
403
- /**
404
- * Resolve an agent-facing provider reference without ever falling through to the
405
- * built-in Claude login. Accept an exact id, a unique case-insensitive display
406
- * name, or a unique configured model. Unknown/ambiguous references return null
407
- * so callers can fail closed and show the current safe choices.
408
- */
409
- export function resolveProviderRef(ref) {
410
- if (!ref || ref === BUILTIN_ID) return BUILTIN_ID
411
- const raw = String(ref).trim()
412
- if (!raw) return BUILTIN_ID
413
- const providers = loadProviders()
414
- const exact = providers.find((provider) => provider.id === raw)
415
- if (exact) return exact.id
416
- const needle = raw.toLocaleLowerCase()
417
- const matches = providers.filter((provider) => (
418
- String(provider.name || '').trim().toLocaleLowerCase() === needle
419
- || String(provider.model || '').trim().toLocaleLowerCase() === needle
420
- ))
421
- return matches.length === 1 ? matches[0].id : null
422
- }
423
-
424
- /**
425
- * id → name map for the ROOM announce's per-lane provider projection (name-only,
426
- * NEVER key or baseUrl). Built once per announce so a cross-device viewer resolves
427
- * a lane's provider badge without the owner's account-channel registry. Built-in
428
- * `anthropic` is omitted (the default path shows no badge). A stale/removed id has
429
- * no entry; the caller falls back to the bare id (a random UUID — not key material).
430
- * @returns {Record<string,string>}
431
- */
432
- export function providerNameMap() {
433
- const m = {}
434
- for (const p of loadProviders()) if (p?.id && p.id !== BUILTIN_ID) m[p.id] = p.name
435
- return m
436
- }
437
-
438
- function keyHint(key) {
439
- const k = String(key || '')
440
- return k.length <= 4 ? k : k.slice(-4)
441
- }
442
-
443
- /**
444
- * Resolve the spawn env for a provider id, matching provider.mjs conventions
445
- * exactly (ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN / ANTHROPIC_MODEL).
446
- * Built-in / unknown id → null (the caller leaves the default Claude env intact).
447
- * @returns {{ANTHROPIC_BASE_URL:string,ANTHROPIC_AUTH_TOKEN:string,ANTHROPIC_MODEL?:string} | null}
448
- */
449
- export function resolveProviderEnv(id) {
450
- if (!id || id === BUILTIN_ID) return null
451
- const p = loadProviders().find((x) => x.id === id)
452
- if (!p) return null
453
- // Keyless (loopback) provider: the SDK still needs a non-empty token, or it
454
- // falls back to the host's Claude login against the localhost URL. Local
455
- // servers (LM Studio / llama.cpp) accept any bearer value.
456
- const env = { ANTHROPIC_BASE_URL: p.baseUrl, ANTHROPIC_AUTH_TOKEN: p.key || 'thinkpool-local' }
457
- if (p.model) env.ANTHROPIC_MODEL = p.model
458
- return env
459
- }
460
-
461
- /**
462
- * The ONE model id a lane actually runs on — used for BOTH the display label and the
463
- * SDK's `model` option, which is the whole point: they drifted, and the SDK option
464
- * silently OVERRIDES the ANTHROPIC_MODEL env resolveProviderEnv() supplies.
465
- *
466
- * The bug this closes: a new lane inherits the focused lane's model (client-side), so
467
- * opening a GLM lane while on Fable sent `model: 'claude-fable-5'` to
468
- * https://api.z.ai/api/anthropic → `API Error: 400 [1211][Unknown Model]`, while the
469
- * chip truthfully read `glm-4.6`. Label and wire disagreed; the wire lost.
470
- *
471
- * Rules (custom = any registered, non-built-in provider):
472
- * • built-in anthropic / no provider → passthrough, unchanged. Default path is sacred.
473
- * • custom + model equals the configured model → honour it.
474
- * • custom + model is a Claude id (claudeish) → DROP it. It leaked across a provider
475
- * boundary and the endpoint cannot serve it. Fall back to the configured model, or
476
- * to undefined (no `model` option → the env / endpoint default decides).
477
- * • custom + explicit non-Claude model (glm-4.5, deepseek-…) → honour it. This is a
478
- * deliberate pick (/model, spawn_terminal({provider, model})), not an inheritance leak.
479
- *
480
- * NEVER returns an inherited Claude id on a custom provider — that is the invariant.
481
- * Pure: `configuredModel` is passed in (not read from disk) so it is directly testable.
482
- *
483
- * @param {{provider?:string|null, model?:string|null, configuredModel?:string|null}} a
484
- * @returns {string|undefined} the model to send; undefined → omit the SDK `model` option
485
- */
486
- export function effectiveLaneModel({ provider, model, configuredModel } = {}) {
487
- if (!provider || provider === BUILTIN_ID) return model || undefined
488
- if (model && model === configuredModel) return model
489
- if (model && !claudeish(model)) return model
490
- return configuredModel || undefined
491
- }
1
+ import e from"node:os";import r from"node:fs";import o from"node:path";import t from"node:crypto";import{claudeish as i}from"./event-id.mjs";import{DEFAULT_RESILIENCE_POLICY as n,resilienceProjection as a,resolveAttemptTarget as d,validateResiliencePolicy as l}from"./provider-resilience.mjs";const s=o.join(e.homedir(),".thinkpool-pair"),c=o.join(s,"providers.json"),u=o.join(s,"bridge-key.json");export const BUILTIN_ID="anthropic";function p(){try{r.mkdirSync(s,{recursive:!0,mode:448})}catch{}}export function loadProviders(){try{const e=JSON.parse(r.readFileSync(c,"utf8"));return Array.isArray(e)?e.filter(e=>e&&e.id&&e.id!==BUILTIN_ID):[]}catch{return[]}}function f(e){p(),r.writeFileSync(c,JSON.stringify(e,null,2),{mode:384});try{r.chmodSync(c,384)}catch{}}let m=null;export function ensureKeypair(){if(m)return m;p();try{const e=JSON.parse(r.readFileSync(u,"utf8"));if(e?.publicKeyB64&&e?.privateKeyPem)return m={publicKeyB64:e.publicKeyB64,privateKey:t.createPrivateKey({key:e.privateKeyPem,format:"pem"})},m}catch{}const{publicKey:e,privateKey:o}=t.generateKeyPairSync("rsa",{modulusLength:2048}),i=e.export({type:"spki",format:"der"}).toString("base64"),n=o.export({type:"pkcs8",format:"pem"});r.writeFileSync(u,JSON.stringify({publicKeyB64:i,privateKeyPem:n,createdAt:Date.now()},null,2),{mode:384});try{r.chmodSync(u,384)}catch{}return m={publicKeyB64:i,privateKey:o},m}export function publicKeyB64(){return ensureKeypair().publicKeyB64}export function bridgeHostId(e=publicKeyB64()){return t.createHash("sha256").update(e).digest("hex").slice(0,32)}export function seal(e,r){const o=r||publicKeyB64(),i=t.createPublicKey({key:Buffer.from(o,"base64"),format:"der",type:"spki"}),n=t.randomBytes(32),a=t.randomBytes(12),d=t.createCipheriv("aes-256-gcm",n,a),l=Buffer.concat([d.update(JSON.stringify(e),"utf8"),d.final()]),s=d.getAuthTag(),c={v:1,ek:t.publicEncrypt({key:i,padding:t.constants.RSA_PKCS1_OAEP_PADDING,oaepHash:"sha256"},n).toString("base64"),iv:a.toString("base64"),ct:Buffer.concat([l,s]).toString("base64")};return Buffer.from(JSON.stringify(c),"utf8").toString("base64")}export function unseal(e){const{privateKey:r}=ensureKeypair(),o=JSON.parse(Buffer.from(String(e||""),"base64").toString("utf8"));if(!(o&&1===o.v&&o.ek&&o.iv&&o.ct))throw new Error("bad envelope");const i=t.privateDecrypt({key:r,padding:t.constants.RSA_PKCS1_OAEP_PADDING,oaepHash:"sha256"},Buffer.from(o.ek,"base64")),n=Buffer.from(o.iv,"base64"),a=Buffer.from(o.ct,"base64");if(a.length<16)throw new Error("bad ciphertext");const d=a.subarray(a.length-16),l=a.subarray(0,a.length-16),s=t.createDecipheriv("aes-256-gcm",i,n);s.setAuthTag(d);const c=Buffer.concat([s.update(l),s.final()]);return JSON.parse(c.toString("utf8"))}export function isLoopbackHost(e){const r=String(e||"").toLowerCase().replace(/^\[|\]$/g,"");return"localhost"===r||"::1"===r||/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(r)}export function addProvider(e){const r=function({name:e,baseUrl:r,key:o}){if(!e||"string"!=typeof e||!e.trim())return"name is required";let t;try{t=new URL(String(r))}catch{return"baseUrl must be a valid https url"}const i=isLoopbackHost(t.hostname);return"https:"===t.protocol||"http:"===t.protocol&&i?i||o&&"string"==typeof o&&o.trim()?null:"key is required":"baseUrl must be https (plain http is allowed only for localhost)"}(e||{});if(r)return{ok:!1,error:r};const o=loadProviders(),i=t.randomUUID();return o.push({id:i,name:String(e.name).trim(),baseUrl:String(e.baseUrl).trim(),model:e.model?String(e.model).trim():void 0,key:e.key?String(e.key).trim():"",addedAt:Date.now()}),f(o),{ok:!0,id:i}}export function addProviderModel({id:e,model:r,name:o}={}){if(!e||e===BUILTIN_ID)return{ok:!1,error:"the built-in Anthropic provider has no key to reuse"};if(!r||"string"!=typeof r||!r.trim())return{ok:!1,error:"model is required"};const i=loadProviders(),a=i.find(r=>r.id===e);if(!a)return{ok:!1,error:"no such provider"};const d=String(r).trim();if(i.some(e=>e.baseUrl===a.baseUrl&&(e.model||"")===d))return{ok:!1,error:`${d} is already registered on this provider`};const l=o&&String(o).trim()||`${function(e){return String(e||"").trim().split(/\s+/)[0]||"Provider"}(a.name)} ${d}`.trim(),s=t.randomUUID();return i.push({id:s,name:l,baseUrl:a.baseUrl,model:d,key:a.key,resilience:{...n,retryOn:[...n.retryOn],circuit:{...n.circuit},fallback:[]},addedAt:Date.now()}),f(i),{ok:!0,id:s}}export function removeProvider(e){if(!e||e===BUILTIN_ID)return{ok:!1,error:"the built-in Anthropic provider cannot be removed"};const r=loadProviders(),o=r.filter(r=>r.id!==e);return o.length===r.length?{ok:!1,error:"no such provider"}:(f(o),{ok:!0})}export function providerGroup(e){return e&&e.id!==BUILTIN_ID?t.createHash("sha256").update(`${e.baseUrl||""}\0${e.key||""}`).digest("hex").slice(0,12):BUILTIN_ID}export function sameProviderEnv(e,r){const o=y(e),t=y(r);if(o===BUILTIN_ID||t===BUILTIN_ID)return o===t;const i=loadProviders(),n=i.find(e=>e.id===o),a=i.find(e=>e.id===t);return!(!n||!a)&&providerGroup(n)===providerGroup(a)}const y=e=>e&&e!==BUILTIN_ID?String(e):BUILTIN_ID;export function providerModel(e){return e&&e!==BUILTIN_ID&&loadProviders().find(r=>r.id===e)?.model||null}export function setProviderResiliencePolicy(e,r){if(!e||e===BUILTIN_ID)return{ok:!1,error:"the built-in Anthropic provider cannot use resilience policy"};const o=loadProviders(),t=o.find(r=>r.id===e);if(!t)return{ok:!1,error:"no such provider"};const i="boolean"==typeof r?{...n,enabled:r,retryOn:[...n.retryOn],circuit:{...n.circuit},fallback:[]}:r,s=l(i,{providers:o,primaryProviderId:e});return s.ok?s.value.fallback.length?{ok:!1,error:"fallback is not available in Phase 1"}:s.value.enabled&&!d({providers:o,providerId:e,model:t.model})?{ok:!1,error:"an enabled resilience policy requires an exact configured model"}:(f(o.map(r=>r.id===e?{...r,resilience:s.value}:r)),{ok:!0,resilience:a(s.value)}):{ok:!1,error:s.error}}export function resolveProviderResiliencePolicy(e){if(!e||e===BUILTIN_ID)return null;const r=loadProviders(),o=r.find(r=>r.id===e);if(!o)return null;const t=l(o.resilience,{providers:r,primaryProviderId:e});if(!t.ok)return null;const i=d({providers:r,providerId:e,model:o.model});return i?{policy:a(t.value),target:i}:null}export function listProviders(){const e=loadProviders().map(e=>({id:e.id,name:e.name,model:e.model||null,group:providerGroup(e),keyHint:v(e.key),resilience:a(e.resilience)}));return[{id:BUILTIN_ID,name:"Anthropic (Claude)",model:null,group:BUILTIN_ID,keyHint:null,resilience:null},...e]}export function announceProviders(){const e=loadProviders().map(e=>({id:e.id,name:e.name,model:e.model||null,group:providerGroup(e)}));return[{id:BUILTIN_ID,name:"Anthropic (Claude)",model:null,group:BUILTIN_ID},...e]}export function resolveProviderRef(e){if(!e||e===BUILTIN_ID)return BUILTIN_ID;const r=String(e).trim();if(!r)return BUILTIN_ID;const o=loadProviders(),t=o.find(e=>e.id===r);if(t)return t.id;const i=r.toLocaleLowerCase(),n=o.filter(e=>String(e.name||"").trim().toLocaleLowerCase()===i||String(e.model||"").trim().toLocaleLowerCase()===i);return 1===n.length?n[0].id:null}export function providerNameMap(){const e={};for(const r of loadProviders())r?.id&&r.id!==BUILTIN_ID&&(e[r.id]=r.name);return e}function v(e){const r=String(e||"");return r.length<=4?r:r.slice(-4)}export function resolveProviderEnv(e){if(!e||e===BUILTIN_ID)return null;const r=loadProviders().find(r=>r.id===e);if(!r)return null;const o={ANTHROPIC_BASE_URL:r.baseUrl,ANTHROPIC_AUTH_TOKEN:r.key||"thinkpool-local"};return r.model&&(o.ANTHROPIC_MODEL=r.model),o}export function effectiveLaneModel({provider:e,model:r,configuredModel:o}={}){return e&&e!==BUILTIN_ID?r&&r===o||r&&!i(r)?r:o||void 0:r||void 0}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{createHash as e}from"node:crypto";import{existsSync as r,readFileSync as i}from"node:fs";import{dirname as o,join as t,resolve as n}from"node:path";import{fileURLToPath as s}from"node:url";const a=o(s(import.meta.url));function f(e,r){try{return JSON.parse(i(e,"utf8"))}catch{throw new Error(`${r} is missing or invalid`)}}function c(r){return e("sha256").update(i(r)).digest("hex")}export function verifyPublishArtifact(e=a){const i=f(t(e,"package.json"),"package.json");if("minified"!==i.thinkpoolArtifact?.kind||1!==i.thinkpoolArtifact?.format)throw new Error("raw bridge source cannot be packed or published; run node build-package.mjs first");const o=i.thinkpoolArtifact.manifest,n=f(t(e,o),o);if("minified"!==n.kind||1!==n.format||n.package!==i.name||n.version!==i.version)throw new Error("minified package manifest does not match package identity");if(!n.minifiedModules||n.artifactModuleBytes>=n.sourceModuleBytes)throw new Error("package manifest does not prove a smaller minified module set");for(const[i,o]of Object.entries(n.files||{})){const n=t(e,i);if(!r(n))throw new Error(`manifest file is missing: ${i}`);if(c(n)!==o)throw new Error(`manifest hash mismatch: ${i}`)}const s=new Set(["package.json",...(i.files||[]).filter(e=>e!==o)]),m=new Set(Object.keys(n.files||{}));for(const e of s)if(!m.has(e))throw new Error(`published file is not integrity-bound: ${e}`);for(const e of m)if(!s.has(e))throw new Error(`manifest contains an undeclared file: ${e}`);for(const e of i.files||[])if(e.endsWith(".map"))throw new Error(`source map cannot be published: ${e}`);return{pkg:i,manifest:n}}if(process.argv[1]&&n(process.argv[1])===s(import.meta.url))try{const{pkg:m,manifest:d}=verifyPublishArtifact();process.stderr.write(`PUBLISH GUARD: PASS — ${m.name}@${m.version}, ${d.minifiedModules} minified modules\n`)}catch(p){process.stderr.write(`PUBLISH GUARD: BLOCKED — ${p.message||p}\n`),process.exitCode=1}
@@ -0,0 +1,129 @@
1
+ {
2
+ "format": 1,
3
+ "package": "thinkpool-pair",
4
+ "version": "0.7.366",
5
+ "kind": "minified",
6
+ "minifiedModules": 111,
7
+ "sourceModuleBytes": 1620861,
8
+ "artifactModuleBytes": 719348,
9
+ "files": {
10
+ "package.json": "0229a6f617212531ad287ab4bfc00aecca97f8296a812dafcc666fd560dad051",
11
+ "LICENSE": "95f1ba638272b3c5f26c36b45bbd8a951b545a091e2c9710fb47f2d68a83a001",
12
+ "bridge.mjs": "9cddacbf7a91fddd8a7bc57ea2f6f9ce1ec442a21bafd948202fd09b188b00b1",
13
+ "bridge-service.sh": "b64f27c473ca8f3a49190fd08c23947ab14ca9958d70b6fa53c7bddc2be91403",
14
+ "command-guidance.mjs": "b9b967b59ff4e77c9613abb0f28858bf3ac5b106a509e471039777b53bec3e29",
15
+ "abort-turn-barrier.mjs": "3734a5b7e2e940ec6d7fa8b5b04935e1429a23d1bd9e92b2ac2a2ecd9f77c75e",
16
+ "host-memory.mjs": "0fab9118b1a53e6939dd047140a5e181d3fc98f5f8faa2b49b1ee04fd148f650",
17
+ "keep-awake.mjs": "af39f17c0279ddacffbbe52443231184d30fc4a5a579692898689b80e8189d67",
18
+ "sdk-smoke.mjs": "dfe509863ede8a12780914939740f7f9fee36d3980f8fcb57803a33593ac4056",
19
+ "sdk-admission.mjs": "eb33725cc33347b900c75289eae7917f6ce3526203711515aecd48d43b3c895d",
20
+ "launcher.mjs": "1723ab61347bd160479da520a5a18f99c8af1dc5a18fbd3f6ca03b97b16ce8e4",
21
+ "privacy-report.mjs": "2299c607b9872d8d3f563ad14ba3a7b75ea1fb5292e7b6ef756d62f26f2362ec",
22
+ "agent-visibility.mjs": "624448728a596fea3a2f01fc686db78716cff20429b8ddbfaa4fb7eb05027870",
23
+ "byok-detect.mjs": "61befb5e53427b558cb911d35ad69fab2458de93dc34f650b7c56ca75d387bc9",
24
+ "context-contract.mjs": "ffebb435dc5d75c289329bf16ab909df9d634a2b508154c250c6b25906a02a91",
25
+ "context-windows.mjs": "80af664f84d7e95ef2b0a2a3131090ac4ccd522709448ac9a0d96c378bbec0f7",
26
+ "claude-session.mjs": "d0b25010078ed642ee7f1149b3f171ba9ff165eaa6ad864f060410682d7e94f8",
27
+ "terminal-name.mjs": "3e114c771abdf42d4ba43d280d01a0d212267f122e3d6d793e3f4e52d052e66a",
28
+ "claude-command-catalog.mjs": "36bd4342b2ae94b43a0072533ef2d1648c7473003964bbad433fdb1a8b2bfec1",
29
+ "codex-session.mjs": "dc4029720e1787e334fc8c81672514c9f830201bef489564eb39f4720e6c3f73",
30
+ "question-response.mjs": "35a7ba53d3eb117717c18004d2a8441f8f37665b231a3c44f4bd9321ff59dd57",
31
+ "thinkpool-room-prompt.mjs": "d9abf116d457043833a8afdb7755a2d03426362f1858053f8615ebf46ed10e34",
32
+ "thinkpool-prompt-contracts.mjs": "8e47f2b7928dc99b85596f85b8ca1c26e9ee99b985405989e64cb9752ce67e9c",
33
+ "thinkpool-capabilities.json": "d0c138387d3cc48f5567480457d10659fd2b20a9445693183ae0f6958a4956bd",
34
+ "codex-app-server.mjs": "f9c2bad1eaf79420d87e12c256bc645ab48083c0b89d252e4968fd176f4bdfc3",
35
+ "codex-images.mjs": "31605d6d8b3dd6b73395da63d1b0c513445c95ffa2f4306ee466f201d633ffc0",
36
+ "codex-mcp-http.mjs": "7472543648a9bb575ce20dc3e3270c2da84174c6ea6e9faf87dfaafb95b6212a",
37
+ "lane-worktree.mjs": "37583757ab82843412ba565dfba3d7cab517e94782eb33115decd5438d092d8f",
38
+ "codex-event-mapper.mjs": "88d6b085170de748ccde943e5b596f0240ffe4e5c4bd64705e7e8dce8775b549",
39
+ "edit-diff.mjs": "9a1bef6bb2d853d630ad38aba374cb2a120dd75b9aa08ab42ffc83cb0e1c57b6",
40
+ "cumulative-event-relay.mjs": "cf6e6533e33b3c5bb0b462d15ccc86106f2b56f8b1e66fc1f4867dd8f44d7ae6",
41
+ "codex-commands.mjs": "cae3f617ac42e1316d8980ef97e4255227fe5df5e8e9b556630a3bfb83bb7c2b",
42
+ "acp-client.mjs": "9e7919ff79825883820019ff7a0ca578f120853ed61c7caab21a8ffc3a67ffc9",
43
+ "hermes-session.mjs": "18cb3fc87338724abb75de2a7ebd8e53fa2902f995dca1f93d2f26b08f3cd331",
44
+ "hermes-model-cache.mjs": "aee23efdebe1aa5e10053f291a9153981fe9b45065c293c9519ca73477251e72",
45
+ "hermes-policy.mjs": "c0e59a65555c7ac25fdaacf6d72c9e8153803f9115eaaeb5ff802560f3a64cbc",
46
+ "hermes-acp-bootstrap.py": "7c3c8114b1da3ea28320eaec9f9851b42d2359d76488e8dbf484871187c68850",
47
+ "hermes-event-mapper.mjs": "7c714e15d06d970c4d602e755461bacf8f4d4136a8afc03406e8b3207ff84cdc",
48
+ "hermes-probe.mjs": "935a80f2633afc4fb65d2610f662d650addde6b4086aaeac8bcbcf7932c26819",
49
+ "hermes-setup.mjs": "3198a2a21282f4694c9ef3bc46e97fa4f467b721c525db0af584d50f34dd570c",
50
+ "hermes-isolation.mjs": "ea9594480a8f470dc66bdcd1907b00c016f52398d9c39c8547cab95e5fb44f86",
51
+ "hermes-delegation-guard.mjs": "4e6dd8d0cddefaddb30f83769acda72242c9b7242671f23ca2cb0c242dace178",
52
+ "runtime-registry.mjs": "4606e104310a755be6a8c587e1050e8b0aa5efe56e7b71f9f13983f2685fc31b",
53
+ "runtime-contract.mjs": "eb24b177002ba33353ec0335f4f830fb57fe313552b1a8d7aef8163cc743f6c1",
54
+ "command-catalog.mjs": "8bdeba1dda874c68f4fa273cb03e6c634dfa22ffae88dec582398220ed2be8bf",
55
+ "repo-search.mjs": "10cbce1d86655a7add0d7350ea8862a57165ccb02c2afa406348f4669050c304",
56
+ "git-diff-report.mjs": "a428b3129c90e207a125a86c4485ec16c8ee7e81d432060a070e37172d85ce7a",
57
+ "runtime-session.mjs": "1a149012375047f0c031e210cba973e64ce2e6de427a951dfc937fa7c3c402ee",
58
+ "turn-stall.mjs": "dee2717afb5f3f474c8a61c1c140ad6fc436e96a0fde6cfa12aff49a10eedff9",
59
+ "update-gate.mjs": "f223a82e72a2a33b48b8321b508e0965f9c705b5722a80a3c199ebd5cf2fcdcf",
60
+ "agent-notify.mjs": "5950ba8928edb6e8d6091a0fdc8fb62cb21fcb79d864504e3b56e012a6f8339a",
61
+ "agent-detect.mjs": "f38ede090e9caa8f7cde0febc41d9dc2c4301e1cd6017a07517b96364ebafb8c",
62
+ "code-event-contract.mjs": "0a092608d85442064ed6b56cd28ea7b28d2cd49f37e8ea6bc5dc3adeb43be833",
63
+ "evidence-compact.mjs": "f8175315f17b46a42f7b396f081374ff3d39ead94c18d64fccfa5fd02b6679f0",
64
+ "evidence-citations.mjs": "865032925532c0f7a4b3f0ed484c85fa53237a7f46d79640714cd483b493b98d",
65
+ "error-recovery.mjs": "0ace80add28fa72b49a3f3a716f247e3ef9816671c09749b3863867700beae32",
66
+ "pair-control-authority.mjs": "7a95f101153dcc6dd0633b5a01595bf2979cd0c4bf50cfc6cd44bb7612ef40a5",
67
+ "scheduled-runs.mjs": "6e6c3fe3e0d976ad3038df948017c9955fc211b39ab2a58daecca6487baf2c59",
68
+ "scheduled-run-admission.mjs": "3a2098d904c3aff571bf314e0182d5636d25d242cef52207e70f9dc1618c94f5",
69
+ "event-id.mjs": "9ebd6dc404cc8c799c660b3d1fb397bd39a6181128d3a91f7bab5f42c55d4e9d",
70
+ "event-bounds.mjs": "aa8eb8517c4aa81b3378f71401ed39c550cdc80e28e3a57ab46d0a5ef5c7c9c1",
71
+ "replay-transport.mjs": "f4ed49dc023e3bb2d9534814bc2684121caafe4d24151b87688407eda47c6c7a",
72
+ "event-delivery-queue.mjs": "c596e298384e0e4890bccdc07f622e1807d1784e19b65f0ef5b7b1bfea0c41d4",
73
+ "mcp-flight-recorder.mjs": "46e3bf7b04212fe69819e38e377c4239caf8b1552ac7bf4905abddd66385065c",
74
+ "plan-meters.mjs": "f7f5aeb3d6a3462b754d3724367bbb331643ba58108e29e91b2aca2f613ab499",
75
+ "recap.mjs": "b2eee701ab8646007670514b08103e7b56b90eaa7d119e765b563a8ede87f85f",
76
+ "transcript-sanitize.mjs": "c7f4718f25f97c05bc7b4ad8e09f18cbda152c53ad6593af1ab99f0c132dbf01",
77
+ "session-store.mjs": "adeb5d7fe8b18b10e5c795877f797696bbb4f2f222916710173ada32bd433c1e",
78
+ "side-lane.mjs": "e70871b0c3d0ef0037ffc85dcdda888984dc907e88a2506620cad0c3f76921bf",
79
+ "worker-completion.mjs": "c456b78eed9847a31003937baa23f259e28b11209674402a462e20c59712a66c",
80
+ "cross-terminal.mjs": "e1404c47bcd437cd32d5ed7766e1cab2a2e8a9d19e39f677bbc41afcc514edd1",
81
+ "pair-bus.mjs": "cbc28cee7f4f06b2213091e0b7b07d61c1d01f0ff207c5c6d808cd616ece221e",
82
+ "direct-pair-room.mjs": "92557b54307da2ca31f0a0d090e44b019a23b1ab32a7dfd6cbcb9d55fd761736",
83
+ "lane-lifecycle.mjs": "9b5ae3d105c30e3ad37f1f1f792200da2dd4d2bb5c4979a2a69618373e58f1c8",
84
+ "lane-continuation.mjs": "b12ee9bb2b8625635c9fc20327acefd4c3ed41e716fdf212d0f7f53f514f8e93",
85
+ "interrupted-resume.mjs": "df46a5fa07fad734dc3a56643aa44e24121e4afdd8206cc5e123102dad87f901",
86
+ "dispatch-lease.mjs": "6e951cad5fb02999ac27e1cf03d9f932b079b2184853b8c6f9b5a8d579fcbbdd",
87
+ "dispatch-permission-cleanup.mjs": "e9793c6f1aab616ad6cf0bf78cd68630428b511f1fdc5c66bb12c68a9932fe26",
88
+ "flow-conductor.mjs": "a2b034a690e57869b3606cf61ae9029da422ed892032be7614bb76488a47da25",
89
+ "flow-worktree.mjs": "d9579a9b2eb8eff46c0708b2e3f546f5a6a3cded1ff726ab334cac9a48544e90",
90
+ "flow-task-graph.mjs": "52f555219c6c940c5d33c6c429d132ab2ded46f9ac6d5207bd5ad972067493bf",
91
+ "flow-models.mjs": "5796e23b8287d6b54bfe9dddfc30c9ef73d715dc322f54c73f2a30a523210021",
92
+ "flow-host-revert.mjs": "a6da239fc54723c74ed51fc03ae3203458b92d2a2e33c26576a9e62cd1031fea",
93
+ "flow-preview.mjs": "b4f1fb6aeed49623c0264ef47d02ec0b244c055ba4386f5ba4c1ffdcd02824c5",
94
+ "mockup-delivery.mjs": "fa112408a186d856b343157381188dc18502aba6cf7ef365459f6c5a074e9cd6",
95
+ "viewport.mjs": "80cb9b6e9275499397239a8b4288c0b49791886696a46917ac798acb75267ee5",
96
+ "design-edit.mjs": "eeafab61056512338a18ed3f2e0339c9315f2fbf0d58456ffe9be08d77dd42b3",
97
+ "design-source-contract.mjs": "18bcd024b317293e445c433fc9c59f9eb85e8fb3fa4de824b82604bbae4687b8",
98
+ "flow-review.mjs": "3d53a8a385dd1360d23c994847a7a3186e93df4a455c8dded1cffeeedbbac378",
99
+ "flow-receipt.mjs": "9fc2804b0a6940d605449818d69f46f19fa2d83dbe6ba29caeaaf0b6694e86aa",
100
+ "flow-scope-evidence.mjs": "37983eeee599380d47ef7dacc5d22349ce45dea712442e822c896f9cec9450fc",
101
+ "past-work-search.mjs": "766f4e669364bd9c3c3667135c502719dbbaf550bf074bda4394ab106f33f415",
102
+ "review-check.mjs": "e16c45fcbbe731b67c5aa1796daffc711e4a5734dbedda282a5e01db0602722e",
103
+ "flow-review-gate.mjs": "0ef351065269bf2cc6112707064024cf2b916890a80fb94404ebca3a7905754b",
104
+ "flow-review-reflect.mjs": "61b39e32ad21501b76c1d9267dd5cdf35017afe7e1333b305e72392bc0304628",
105
+ "flow-assembly.mjs": "3535203e1ad75fb326db5212c2f013ab6c0049e31bc0ba59813c7d0ef1956d03",
106
+ "flow-budget.mjs": "d406ecbdd5d4acc08c60c641d7ad12553675cf4bbf35a9600bc96300c1af3404",
107
+ "flow-context-store.mjs": "949c20aeac1984d265f0445547a7d248565e61270cf9fed509047a1dd2c44f02",
108
+ "flow-redispatch.mjs": "4fa3bf3dd9461fa1c447d12b5db5a0f1befb3427abaed1ee1cec86134ad3eb95",
109
+ "flow-skill-registry.mjs": "33de95aab75cb70bc145da065a460ec6351c46c7d5bbdfe73650258943cc570f",
110
+ "serve-dir.mjs": "1ab4cbdff02ddfd726cde4a021503ec2f24b47091e9950237d42d5827e5674ce",
111
+ "serve-consent.mjs": "f6c5e9f42f452525a83ed5005bc9e65156ddec6fb2cd83deccc81b86f20c6825",
112
+ "reap-terminal.mjs": "76d6ed14316d26d0d8b246d54cfdbde1f246badee91524b4caca8151f1440ccd",
113
+ "terminal-row-reconcile.mjs": "b6f4022f03d908445f404ca1f8479c780ed645bf855428a2726ac2db682ab4ed",
114
+ "switch-provider.mjs": "9805d59df49e1272ffd5ef7e3d2260f6e93c76d32265b78e461287ef500f5b71",
115
+ "service.mjs": "4d649a995d8fee138d9714dcfd41fdcfee5b5148e6b2b0328dd31a797cc134fc",
116
+ "supervisor-ready.mjs": "7d5e4ebe3c7a93e0888dd335fb4556a670e55a80a89b861c8a44feac159cd840",
117
+ "presence.mjs": "2516ebb4474a8f45dd881530335c2174af9cde44be8eed6896ff8c62497b6e2b",
118
+ "account.mjs": "a8bc640fe66b179777597477899ec4eb82460f602d637ca477ab76eb682eb233",
119
+ "auth-store.mjs": "c4d41ee7cdc34c2002c6dd50d32e53ace0a3f03a116ffa930ed94a437e247625",
120
+ "key-shape.mjs": "a4113293d52eeba39b05e70a3e0345abc1ddc181b4f236b413bde0ae11a3f646",
121
+ "supabase-key.mjs": "041bde6cf50b15f6426daaf738374abc9647861d8c1dd3d85cedc45684271888",
122
+ "provider.mjs": "5a96b83d8aa65fb344c8e8ec5ffcb9cc360497925120cdf7b4ad26416d284790",
123
+ "providers.mjs": "c0757433d30bf2f22e80f85e779cdeeeb10b412d568965f1115d4de2c8aa0314",
124
+ "provider-resilience.mjs": "a5a3921040d1860bb82f1d3b53c6ec25a5e230345c0131f2213c535e07fc497e",
125
+ "model-prices.mjs": "91f0bd8688f59ce6bff7743710d3b1139f4f0823d6b95fa68cd9b6fdcdba6ab4",
126
+ "README.md": "b801ce3677409cd8f99dacef2f5a9d69487db6d3146d41381c70b82f5d04eed4",
127
+ "publish-guard.mjs": "569dd788790e68ea822129f588fd16c1bb435324666552fe9dfe7c435ab3f617"
128
+ }
129
+ }