@cortexkit/opencode-openai-auth 0.8.0 → 0.9.0

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 (39) hide show
  1. package/dist/index-301y4qa1.js +2 -0
  2. package/dist/index-338z7171.js +211 -0
  3. package/dist/index-5y4vzdf8.js +2 -0
  4. package/dist/{index-507jh8bv.js → index-9ehhb051.js} +1 -1
  5. package/dist/{index-944gsj1s.js → index-jwn4r3sw.js} +1 -1
  6. package/dist/{index-02h7g5f9.js → index-sdt2d2ra.js} +1 -1
  7. package/dist/{index-3w4vmckx.js → index-w42s0g6m.js} +2 -2
  8. package/dist/index.d.ts +172 -341
  9. package/dist/index.js +61 -61
  10. package/dist/internal-Y7QNVDJT.d.ts +908 -0
  11. package/dist/quota-normalize-3qffywcm.js +1 -0
  12. package/dist/rpc/port-file.js +1 -1
  13. package/dist/rpc/protocol.d.ts +1 -1
  14. package/dist/rpc/rpc-client.d.ts +2 -1
  15. package/dist/rpc/rpc-client.js +1 -1
  16. package/dist/rpc/rpc-dir.js +1 -1
  17. package/dist/sidebar-state-B98sERYt.d.ts +219 -0
  18. package/dist/sidebar-state.d.ts +2 -172
  19. package/dist/sidebar-state.js +1 -1
  20. package/dist/{token-jdvpwk60.js → token-9823y0pc.js} +1 -1
  21. package/dist/token-util-ypgz7rge.js +1 -0
  22. package/dist/tui-preferences.js +1 -1
  23. package/dist/tui.d.ts +27 -5
  24. package/package.json +2 -1
  25. package/src/tui-compiled/raw/core-custody-state.ts +243 -0
  26. package/src/tui-compiled/raw/sidebar-state.ts +140 -11
  27. package/src/tui-compiled/raw/tui-command-dialogs.tsx +89 -39
  28. package/src/tui-compiled/raw/tui.tsx +138 -18
  29. package/src/tui-compiled/runtime/core-custody-state.ts +243 -0
  30. package/src/tui-compiled/runtime/sidebar-state.ts +140 -11
  31. package/src/tui-compiled/runtime/tui-command-dialogs.tsx +70 -33
  32. package/src/tui-compiled/runtime/tui.tsx +241 -143
  33. package/src/tui-compiled/shared/logger.ts +9 -2
  34. package/dist/index-2xppkvkx.js +0 -2
  35. package/dist/index-nshbgkz1.js +0 -198
  36. package/dist/index-xyg86jgt.js +0 -2
  37. package/dist/protocol-Cv9ZmU0p.d.ts +0 -24
  38. package/dist/quota-normalize-wap0rne8.js +0 -1
  39. package/dist/token-util-zzm7paz9.js +0 -1
@@ -33,6 +33,7 @@ import {
33
33
  resolveSessionSidebarRouting,
34
34
  resolveSessionStickyAccount,
35
35
  type SidebarState,
36
+ type SpendControlReading,
36
37
  } from './sidebar-state.ts'
37
38
  import { openCommandDialog } from './tui-command-dialogs.tsx'
38
39
  import {
@@ -241,28 +242,97 @@ function CollapsedRow(props: {
241
242
  }
242
243
 
243
244
  export interface QuotaDisplayRow {
244
- key: 'primary' | 'secondary'
245
+ key: 'primary' | 'secondary' | 'spendControl'
245
246
  label: string
247
+ labelWidth: number
246
248
  window: QuotaWindow
247
249
  pacing: QuotaPacing | null
248
250
  }
249
251
 
252
+ const SPEND_CONTROL_LABEL = 'credits'
253
+
254
+ // Labels a quota snapshot contributes to the sidebar, independent of pacing or
255
+ // the wall clock — the label column is sized from these alone.
256
+ function quotaRowLabels(quota: AccountQuota | null): string[] {
257
+ const labels = getPresentQuotaWindows(quota).map((row) => row.label)
258
+ if (quota?.spendControl) labels.push(SPEND_CONTROL_LABEL)
259
+ return labels
260
+ }
261
+
262
+ // The label column is a property of the whole sidebar, not of one account's row
263
+ // set: every account's bars must start at the same column, so the width is
264
+ // derived once from every account rendered (main plus fallbacks) and threaded
265
+ // into each account's rows. The +1 keeps a separator before the longest label's
266
+ // bar; 3 is the floor the short 5h/7d labels already relied on.
267
+ /**
268
+ * Every quota the sidebar will draw, in one list.
269
+ *
270
+ * The label column is shared by all of them, so the width has to be measured
271
+ * over this exact set — measuring a subset is what made stacked accounts
272
+ * disagree about where the bar starts. Named rather than inlined at each call
273
+ * site because the set is the part that can be got wrong, and a call site
274
+ * cannot be tested without the host's rendering runtime.
275
+ */
276
+ export function renderedQuotas(state: {
277
+ main?: { quota?: AccountQuota | null } | null
278
+ fallbacks?: ReadonlyArray<{ enabled?: boolean; quota?: AccountQuota | null }>
279
+ }): ReadonlyArray<AccountQuota | null | undefined> {
280
+ return [
281
+ state.main?.quota ?? null,
282
+ ...(state.fallbacks ?? [])
283
+ .filter((fallback) => fallback.enabled)
284
+ .map((fallback) => fallback.quota),
285
+ ]
286
+ }
287
+
288
+ export function computeQuotaLabelWidth(
289
+ quotas: ReadonlyArray<AccountQuota | null | undefined>,
290
+ ): number {
291
+ let width = 3
292
+ for (const quota of quotas) {
293
+ for (const label of quotaRowLabels(quota ?? null)) {
294
+ width = Math.max(width, label.length + 1)
295
+ }
296
+ }
297
+ return width
298
+ }
299
+
250
300
  // Maps present quota windows onto display rows and computes pacing against each
251
301
  // dynamic duration or the historical 5h/7d duration for legacy snapshots.
302
+ // `labelWidth` comes from computeQuotaLabelWidth for rendered sidebars; callers
303
+ // that only inspect pacing may omit it and get this row set's own width.
252
304
  export function buildQuotaRowsForDisplay(
253
305
  quota: AccountQuota | null,
254
306
  now: number,
255
307
  pacingEnabled: boolean,
308
+ labelWidth?: number,
256
309
  ): QuotaDisplayRow[] {
257
- return getPresentQuotaWindows(quota).map((row) => ({
258
- key: row.key,
259
- label: row.label,
260
- window: row.window,
261
- pacing:
262
- pacingEnabled && row.windowMs !== null
263
- ? computeQuotaPacing(row.window, row.windowMs, now)
264
- : null,
265
- }))
310
+ const rows: Array<Omit<QuotaDisplayRow, 'labelWidth'>> =
311
+ getPresentQuotaWindows(quota).map((row) => ({
312
+ key: row.key,
313
+ label: row.label,
314
+ window: row.window,
315
+ pacing:
316
+ pacingEnabled && row.windowMs !== null
317
+ ? computeQuotaPacing(row.window, row.windowMs, now)
318
+ : null,
319
+ }))
320
+ const spendControl = quota?.spendControl
321
+ if (spendControl) {
322
+ rows.push({
323
+ key: 'spendControl',
324
+ label: SPEND_CONTROL_LABEL,
325
+ window: {
326
+ usedPercent: spendControl.usedPercent,
327
+ remainingPercent: spendControl.remainingPercent,
328
+ resetsAt: spendControl.resetsAt,
329
+ },
330
+ pacing: null,
331
+ })
332
+ }
333
+ const width =
334
+ labelWidth ?? Math.max(3, ...rows.map((row) => row.label.length + 1))
335
+ return rows.map((row) => ({ ...row, labelWidth: width }))
266
336
  }
267
337
 
268
338
  export function isQuotaLoaded(quota: AccountQuota | null): boolean {
@@ -280,12 +350,39 @@ export function getQuotaMetadataRows(
280
350
  return rows
281
351
  }
282
352
 
353
+ function formatGroupedAmount(value: number): string {
354
+ return Math.round(value).toLocaleString('en-US')
355
+ }
356
+
357
+ // The unit names the budget the amounts are counted in, so it agrees with the
358
+ // total rather than staying singular against a plural quantity.
359
+ function pluralizeUnit(unit: string, count: number): string {
360
+ if (count === 1) return unit
361
+ return unit.endsWith('s') ? unit : `${unit}s`
362
+ }
363
+
364
+ export function formatSpendControlAmounts(
365
+ spendControl: SpendControlReading,
366
+ ): string {
367
+ const unit = pluralizeUnit(spendControl.unit ?? 'unit', spendControl.limit)
368
+ return `${formatGroupedAmount(spendControl.used)} / ${formatGroupedAmount(spendControl.limit)} ${unit}`
369
+ }
370
+
283
371
  export function getAccountMetadataRows(
284
372
  resetCredits: number | undefined,
373
+ spendControl?: SpendControlReading,
285
374
  ): Array<{ label: string; value: string }> {
286
- return resetCredits === undefined
287
- ? []
288
- : [{ label: 'resets', value: String(resetCredits) }]
375
+ const rows: Array<{ label: string; value: string }> = []
376
+ if (spendControl) {
377
+ rows.push({
378
+ label: 'credits',
379
+ value: formatSpendControlAmounts(spendControl),
380
+ })
381
+ }
382
+ if (resetCredits !== undefined) {
383
+ rows.push({ label: 'resets', value: String(resetCredits) })
384
+ }
385
+ return rows
289
386
  }
290
387
 
291
388
  // Quota window row: muted label left, tone-colored bar + percentage right,
@@ -296,6 +393,7 @@ function QuotaRow(props: {
296
393
  theme: ThemeCurrent
297
394
  appearance: AppearancePrefs
298
395
  label: string
396
+ labelWidth: number
299
397
  window: { usedPercent: number; resetsAt?: string } | undefined
300
398
  pacing: QuotaPacing | null
301
399
  }) {
@@ -315,7 +413,9 @@ function QuotaRow(props: {
315
413
  when={props.window}
316
414
  fallback={
317
415
  <box width='100%' flexDirection='row'>
318
- <text fg={props.theme.textMuted}>{props.label.padEnd(3)}</text>
416
+ <text fg={props.theme.textMuted}>
417
+ {props.label.padEnd(props.labelWidth)}
418
+ </text>
319
419
  <text fg={props.theme.textMuted}>{'\u2014'}</text>
320
420
  </box>
321
421
  }
@@ -325,7 +425,9 @@ function QuotaRow(props: {
325
425
  the right edge so reset times align in their own right column. */}
326
426
  <box width='100%' flexDirection='row' justifyContent='space-between'>
327
427
  <box flexDirection='row'>
328
- <text fg={props.theme.textMuted}>{props.label.padEnd(3)}</text>
428
+ <text fg={props.theme.textMuted}>
429
+ {props.label.padEnd(props.labelWidth)}
430
+ </text>
329
431
  <For each={quotaBarSegments(used(), props.appearance, props.pacing)}>
330
432
  {(segment) => (
331
433
  <text fg={toneColor(props.theme, segment.tone)}>
@@ -345,7 +447,7 @@ function QuotaRow(props: {
345
447
  </box>
346
448
  <Show when={paceLine()}>
347
449
  <box width='100%' flexDirection='row'>
348
- <text fg={props.theme.textMuted}>{' '}</text>
450
+ <text fg={props.theme.textMuted}>{''.padEnd(props.labelWidth)}</text>
349
451
  <text
350
452
  fg={toneColor(
351
453
  props.theme,
@@ -371,6 +473,7 @@ function AccountBlock(props: {
371
473
  killed: boolean
372
474
  active: boolean
373
475
  pacingEnabled: boolean
476
+ labelWidth: number
374
477
  resetCredits?: number
375
478
  marginTop?: number
376
479
  }) {
@@ -379,7 +482,12 @@ function AccountBlock(props: {
379
482
  const statusTone = (): Tone =>
380
483
  props.killed ? 'err' : props.active ? 'ok' : 'muted'
381
484
  const rows = () =>
382
- buildQuotaRowsForDisplay(props.quota, Date.now(), props.pacingEnabled)
485
+ buildQuotaRowsForDisplay(
486
+ props.quota,
487
+ Date.now(),
488
+ props.pacingEnabled,
489
+ props.labelWidth,
490
+ )
383
491
  return (
384
492
  <box width='100%' flexDirection='column' marginTop={props.marginTop ?? 0}>
385
493
  <box width='100%' flexDirection='row' justifyContent='space-between'>
@@ -404,6 +512,7 @@ function AccountBlock(props: {
404
512
  theme={props.theme}
405
513
  appearance={props.appearance}
406
514
  label={row.label}
515
+ labelWidth={row.labelWidth}
407
516
  window={row.window}
408
517
  pacing={row.pacing}
409
518
  />
@@ -411,7 +520,12 @@ function AccountBlock(props: {
411
520
  </For>
412
521
  </Show>
413
522
  </Show>
414
- <For each={getAccountMetadataRows(props.resetCredits)}>
523
+ <For
524
+ each={getAccountMetadataRows(
525
+ props.resetCredits,
526
+ props.quota?.spendControl,
527
+ )}
528
+ >
415
529
  {(row) => (
416
530
  <StatRow
417
531
  theme={props.theme}
@@ -453,6 +567,7 @@ function QuotaDialogContent(props: {
453
567
  const enabledFallbacks = () =>
454
568
  (state().fallbacks ?? []).filter((f) => f.enabled)
455
569
  const activeId = () => resolveQuotaDialogActiveId(state(), props.sessionId)
570
+ const quotaLabelWidth = () => computeQuotaLabelWidth(renderedQuotas(state()))
456
571
  return (
457
572
  <box flexDirection='column' padding={2} width='100%' alignItems='center'>
458
573
  <box flexDirection='column' width={58}>
@@ -469,6 +584,7 @@ function QuotaDialogContent(props: {
469
584
  killed={state().main?.killed ?? false}
470
585
  active={activeId() === 'main'}
471
586
  pacingEnabled={prefs().sections.pacing}
587
+ labelWidth={quotaLabelWidth()}
472
588
  resetCredits={state().main?.resetCredits}
473
589
  />
474
590
  <Show when={prefs().sections.fallbackAccounts}>
@@ -482,6 +598,7 @@ function QuotaDialogContent(props: {
482
598
  killed={fb.killed}
483
599
  active={activeId() === fb.id}
484
600
  pacingEnabled={prefs().sections.pacing}
601
+ labelWidth={quotaLabelWidth()}
485
602
  resetCredits={fb.resetCredits}
486
603
  marginTop={1}
487
604
  />
@@ -662,6 +779,7 @@ function QuotaSidebar(props: {
662
779
  route: sessionRouting().route,
663
780
  })
664
781
  const activeAccount = () => resolveActiveAccount(sessionState())
782
+ const quotaLabelWidth = () => computeQuotaLabelWidth(renderedQuotas(state()))
665
783
  const activeQuotaSummary = () =>
666
784
  getCollapsedQuotaSummary(activeAccount().quota)
667
785
  const activePacingDeficit = () => {
@@ -794,6 +912,7 @@ function QuotaSidebar(props: {
794
912
  killed={state().main?.killed ?? false}
795
913
  active={sessionRouting().activeId === 'main'}
796
914
  pacingEnabled={prefs().sections.pacing}
915
+ labelWidth={quotaLabelWidth()}
797
916
  resetCredits={state().main?.resetCredits}
798
917
  />
799
918
  <Show when={prefs().sections.fallbackAccounts}>
@@ -807,6 +926,7 @@ function QuotaSidebar(props: {
807
926
  killed={fb.killed}
808
927
  active={sessionRouting().activeId === fb.id}
809
928
  pacingEnabled={prefs().sections.pacing}
929
+ labelWidth={quotaLabelWidth()}
810
930
  resetCredits={fb.resetCredits}
811
931
  marginTop={1}
812
932
  />
@@ -0,0 +1,243 @@
1
+ export type CustodyMode = 'local' | 'claustrum'
2
+
3
+ export type ManifestCustodyState = 'absent' | 'present' | 'unreadable'
4
+
5
+ export type LocalCustodyState =
6
+ | 'real'
7
+ | 'tombstone'
8
+ | 'empty'
9
+ | 'gone'
10
+ | 'slot-absent'
11
+
12
+ export type VaultCustodyState =
13
+ | 'serves'
14
+ | 'cold'
15
+ | 'needs_reauth'
16
+ | 'identity_mismatch'
17
+ | 'no_handle'
18
+
19
+ export const CUSTODY_INERT_REASONS = [
20
+ 'needs-login',
21
+ 'mode-mismatch',
22
+ 'corrupt-under-binding',
23
+ 'manifest-unreadable',
24
+ 'unbound-under-claustrum',
25
+ 'orphan-tombstone',
26
+ 'vault-cold',
27
+ 'vault-reauth',
28
+ 'identity-mismatch',
29
+ 'no-handle',
30
+ 'takeover-incomplete',
31
+ 'new-local-family-under-claustrum',
32
+ 'takeover-incomplete/vault-unavailable',
33
+ 'takeover-incomplete/slot-absent',
34
+ ] as const
35
+
36
+ export type CustodyInertReason = (typeof CUSTODY_INERT_REASONS)[number]
37
+
38
+ export type CustodyVerdict =
39
+ | { kind: 'LOCAL' }
40
+ | {
41
+ kind: 'VAULT'
42
+ canonicalize?: boolean
43
+ installTombstone?: boolean
44
+ }
45
+ | {
46
+ kind: 'INERT'
47
+ reason: CustodyInertReason
48
+ installTombstone?: boolean
49
+ canonicalize?: boolean
50
+ }
51
+ | { kind: 'NEEDS_LOGIN'; reason?: 'corrupt' }
52
+
53
+ export interface EvaluateCustodyStartupInput {
54
+ mode: CustodyMode
55
+ manifest: ManifestCustodyState
56
+ local: LocalCustodyState
57
+ vault: () => VaultCustodyState
58
+ manifestFailureReason?: string
59
+ fingerprintMatch?: boolean
60
+ verifiedInProcessLogin: boolean
61
+ isMain?: boolean
62
+ }
63
+
64
+ function unreachable(value: never): never {
65
+ return value
66
+ }
67
+
68
+ function withCanonicalization<
69
+ T extends Extract<CustodyVerdict, { kind: 'VAULT' | 'INERT' }>,
70
+ >(local: LocalCustodyState, verdict: T): T | (T & { canonicalize: true }) {
71
+ return local === 'empty' ? { ...verdict, canonicalize: true } : verdict
72
+ }
73
+
74
+ function evaluateLocal(local: LocalCustodyState): CustodyVerdict {
75
+ switch (local) {
76
+ case 'real':
77
+ return { kind: 'LOCAL' }
78
+ case 'tombstone':
79
+ case 'empty':
80
+ case 'slot-absent':
81
+ return { kind: 'NEEDS_LOGIN' }
82
+ case 'gone':
83
+ return { kind: 'NEEDS_LOGIN', reason: 'corrupt' }
84
+ }
85
+ return unreachable(local)
86
+ }
87
+
88
+ function evaluateLocalWithBinding(
89
+ local: LocalCustodyState,
90
+ verifiedInProcessLogin: boolean,
91
+ ): CustodyVerdict {
92
+ switch (local) {
93
+ case 'real':
94
+ return verifiedInProcessLogin
95
+ ? { kind: 'LOCAL' }
96
+ : { kind: 'INERT', reason: 'needs-login' }
97
+ case 'tombstone':
98
+ case 'empty':
99
+ case 'slot-absent':
100
+ return { kind: 'INERT', reason: 'mode-mismatch' }
101
+ case 'gone':
102
+ return { kind: 'INERT', reason: 'corrupt-under-binding' }
103
+ }
104
+ return unreachable(local)
105
+ }
106
+
107
+ function evaluateUnboundClaustrum(local: LocalCustodyState): CustodyVerdict {
108
+ switch (local) {
109
+ case 'tombstone':
110
+ case 'empty':
111
+ return { kind: 'INERT', reason: 'orphan-tombstone' }
112
+ case 'real':
113
+ case 'gone':
114
+ case 'slot-absent':
115
+ return { kind: 'INERT', reason: 'unbound-under-claustrum' }
116
+ }
117
+ return unreachable(local)
118
+ }
119
+
120
+ function evaluateTombstone(
121
+ local: Extract<LocalCustodyState, 'tombstone' | 'empty'>,
122
+ vault: VaultCustodyState,
123
+ ): CustodyVerdict {
124
+ switch (vault) {
125
+ case 'serves':
126
+ return withCanonicalization(local, { kind: 'VAULT' })
127
+ case 'cold':
128
+ return withCanonicalization(local, {
129
+ kind: 'INERT',
130
+ reason: 'vault-cold',
131
+ })
132
+ case 'needs_reauth':
133
+ return withCanonicalization(local, {
134
+ kind: 'INERT',
135
+ reason: 'vault-reauth',
136
+ })
137
+ case 'identity_mismatch':
138
+ return withCanonicalization(local, {
139
+ kind: 'INERT',
140
+ reason: 'identity-mismatch',
141
+ })
142
+ case 'no_handle':
143
+ return withCanonicalization(local, { kind: 'INERT', reason: 'no-handle' })
144
+ }
145
+ return unreachable(vault)
146
+ }
147
+
148
+ function evaluateReal(
149
+ vault: VaultCustodyState,
150
+ fingerprintMatch: boolean | undefined,
151
+ ): CustodyVerdict {
152
+ switch (vault) {
153
+ case 'serves':
154
+ return fingerprintMatch === false
155
+ ? { kind: 'INERT', reason: 'new-local-family-under-claustrum' }
156
+ : { kind: 'INERT', reason: 'takeover-incomplete' }
157
+ case 'cold':
158
+ case 'needs_reauth':
159
+ return { kind: 'INERT', reason: 'takeover-incomplete/vault-unavailable' }
160
+ case 'identity_mismatch':
161
+ return { kind: 'INERT', reason: 'identity-mismatch' }
162
+ case 'no_handle':
163
+ return { kind: 'INERT', reason: 'no-handle' }
164
+ }
165
+ return unreachable(vault)
166
+ }
167
+
168
+ function evaluateGone(vault: VaultCustodyState): CustodyVerdict {
169
+ switch (vault) {
170
+ case 'serves':
171
+ return { kind: 'VAULT', installTombstone: true }
172
+ case 'cold':
173
+ return { kind: 'INERT', reason: 'vault-cold', installTombstone: true }
174
+ case 'needs_reauth':
175
+ return { kind: 'INERT', reason: 'vault-reauth', installTombstone: true }
176
+ case 'identity_mismatch':
177
+ return { kind: 'INERT', reason: 'identity-mismatch' }
178
+ case 'no_handle':
179
+ return { kind: 'INERT', reason: 'no-handle' }
180
+ }
181
+ return unreachable(vault)
182
+ }
183
+
184
+ function evaluateSlotAbsent(vault: VaultCustodyState): CustodyVerdict {
185
+ switch (vault) {
186
+ case 'serves':
187
+ case 'cold':
188
+ case 'needs_reauth':
189
+ return { kind: 'INERT', reason: 'takeover-incomplete/slot-absent' }
190
+ case 'identity_mismatch':
191
+ return { kind: 'INERT', reason: 'identity-mismatch' }
192
+ case 'no_handle':
193
+ return { kind: 'INERT', reason: 'no-handle' }
194
+ }
195
+ return unreachable(vault)
196
+ }
197
+
198
+ function evaluatePresentClaustrum(
199
+ local: LocalCustodyState,
200
+ vault: VaultCustodyState,
201
+ fingerprintMatch: boolean | undefined,
202
+ ): CustodyVerdict {
203
+ switch (local) {
204
+ case 'tombstone':
205
+ case 'empty':
206
+ return evaluateTombstone(local, vault)
207
+ case 'real':
208
+ return evaluateReal(vault, fingerprintMatch)
209
+ case 'gone':
210
+ return evaluateGone(vault)
211
+ case 'slot-absent':
212
+ return evaluateSlotAbsent(vault)
213
+ }
214
+ return unreachable(local)
215
+ }
216
+
217
+ export function evaluateCustodyStartup(
218
+ input: EvaluateCustodyStartupInput,
219
+ ): CustodyVerdict {
220
+ if (input.manifest === 'unreadable') {
221
+ return { kind: 'INERT', reason: 'manifest-unreadable' }
222
+ }
223
+
224
+ switch (input.mode) {
225
+ case 'local':
226
+ return input.manifest === 'present'
227
+ ? evaluateLocalWithBinding(input.local, input.verifiedInProcessLogin)
228
+ : evaluateLocal(input.local)
229
+ case 'claustrum':
230
+ if (input.manifest === 'absent') {
231
+ return evaluateUnboundClaustrum(input.local)
232
+ }
233
+ if (input.manifest === 'present') {
234
+ return evaluatePresentClaustrum(
235
+ input.local,
236
+ input.vault(),
237
+ input.fingerprintMatch,
238
+ )
239
+ }
240
+ return unreachable(input.manifest)
241
+ }
242
+ return unreachable(input.mode)
243
+ }