@cortexkit/opencode-anthropic-auth 1.21.0 → 1.23.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 (68) hide show
  1. package/README.md +57 -12
  2. package/dist/billing-lineage.d.ts +51 -0
  3. package/dist/claustrum-enrollment-registry.d.ts +4 -0
  4. package/dist/cli.d.ts +2 -1
  5. package/dist/cli.js +89 -6
  6. package/dist/custody-dimensions.d.ts +36 -0
  7. package/dist/custody-enrollment.d.ts +14 -0
  8. package/dist/custody-live.d.ts +111 -0
  9. package/dist/custody-manifest-watcher.d.ts +16 -0
  10. package/dist/custody-mode.d.ts +186 -0
  11. package/dist/effort-history.d.ts +62 -0
  12. package/dist/fable-fallback.d.ts +6 -7
  13. package/dist/{index-w9d41yy9.js → index-0jfsxyn9.js} +1 -1
  14. package/dist/index-1fq7h6yd.js +430 -0
  15. package/dist/{index-y0khnbt6.js → index-1gr8v4fn.js} +1 -1
  16. package/dist/index-797pmamn.js +2 -0
  17. package/dist/{index-1db17bc2.js → index-8y2t4mwz.js} +2 -2
  18. package/dist/index-a5d6v3ba.js +5 -0
  19. package/dist/index-n96q8pks.js +2 -0
  20. package/dist/index-xxhyhp98.js +2 -0
  21. package/dist/index-xzbhzz0a.js +2 -0
  22. package/dist/index-z7gfdyv3.js +1 -0
  23. package/dist/index.js +28 -21
  24. package/dist/local-login.d.ts +64 -0
  25. package/dist/{logger-ftk4bg3p.js → logger-kk9vyem9.js} +1 -1
  26. package/dist/prime-manager-registry.d.ts +5 -1
  27. package/dist/rpc/notifications.d.ts +3 -3
  28. package/dist/rpc/port-file.js +1 -1
  29. package/dist/rpc/protocol.d.ts +23 -1
  30. package/dist/rpc/protocol.js +1 -1
  31. package/dist/rpc/rpc-client.d.ts +1 -1
  32. package/dist/rpc/rpc-client.js +1 -1
  33. package/dist/rpc/rpc-dir.js +1 -1
  34. package/dist/rpc/server-registry.d.ts +10 -0
  35. package/dist/setup/activation.d.ts +7 -0
  36. package/dist/setup/claustrum.d.ts +26 -0
  37. package/dist/setup/command-runner.d.ts +2 -0
  38. package/dist/setup/command.d.ts +8 -0
  39. package/dist/setup/detect.d.ts +5 -0
  40. package/dist/setup/jsonc.d.ts +9 -0
  41. package/dist/setup/opencode-config.d.ts +18 -0
  42. package/dist/setup/paths.d.ts +15 -0
  43. package/dist/setup/pi.d.ts +8 -0
  44. package/dist/setup/process-fence.d.ts +14 -0
  45. package/dist/setup/types.d.ts +37 -0
  46. package/dist/sidebar-state.d.ts +5 -2
  47. package/dist/sidebar-state.js +1 -1
  48. package/dist/transform.d.ts +17 -1
  49. package/dist/tui/command-dialogs.d.ts +28 -9
  50. package/dist/tui-preferences.js +2 -2
  51. package/package.json +10 -5
  52. package/src/rpc/notifications.ts +28 -24
  53. package/src/rpc/protocol.ts +34 -1
  54. package/src/rpc/rpc-client.ts +1 -1
  55. package/src/rpc/rpc-server.ts +18 -5
  56. package/src/rpc/server-registry.ts +61 -0
  57. package/src/sidebar-state.ts +42 -3
  58. package/src/tui/command-dialogs.tsx +301 -97
  59. package/src/tui-compiled/rpc/protocol.ts +34 -1
  60. package/src/tui-compiled/rpc/rpc-client.ts +1 -1
  61. package/src/tui-compiled/sidebar-state.ts +42 -3
  62. package/src/tui-compiled/tui/command-dialogs.tsx +240 -70
  63. package/src/tui-compiled/tui.tsx +8 -3
  64. package/src/tui.tsx +18 -3
  65. package/dist/index-4nz423bt.js +0 -4
  66. package/dist/index-4x20896x.js +0 -2
  67. package/dist/index-wezdyxst.js +0 -2
  68. package/dist/index-yskwj5e6.js +0 -428
@@ -1,6 +1,10 @@
1
1
  /** @jsxImportSource @opentui/solid */
2
- import type { PrimeAccountStatus } from '@cortexkit/anthropic-auth-core'
2
+ import {
3
+ custodyStatusLabel,
4
+ type PrimeAccountStatus,
5
+ } from '@cortexkit/anthropic-auth-core'
3
6
  import type { TuiPluginApi } from '@opencode-ai/plugin/tui'
7
+ import type { AccountDialogAccount } from '../rpc/protocol'
4
8
  import type { OpenDialogPayload } from '../rpc/protocol.js'
5
9
  import { formatPrimeCost, formatPrimeTime } from '../sidebar-state.js'
6
10
 
@@ -15,6 +19,27 @@ type KillswitchDialogConfig = {
15
19
  accounts?: Record<string, Record<string, number>>
16
20
  }
17
21
 
22
+ type TuiAccountDialogAccount = Omit<
23
+ AccountDialogAccount,
24
+ 'vaultReauth' | 'custodyState'
25
+ > &
26
+ Partial<Pick<AccountDialogAccount, 'vaultReauth' | 'custodyState'>>
27
+
28
+ type TuiAccountDialogPayload = {
29
+ accounts: TuiAccountDialogAccount[]
30
+ claustrumDetection: string
31
+ custodyMode?: TuiCustodyMode
32
+ enrollmentStatus?: string
33
+ }
34
+
35
+ type TuiCustodyMode = 'local' | 'claustrum' | `mismatch: ${string}`
36
+
37
+ type AccountDialogOption = {
38
+ title: string
39
+ value: string
40
+ description?: string
41
+ }
42
+
18
43
  export const PRIME_DIALOG_OPTIONS = [
19
44
  { title: 'Enable', value: 'on' },
20
45
  { title: 'Disable', value: 'off' },
@@ -48,26 +73,189 @@ export function buildKillswitchThresholdSeed(
48
73
  return seedParts.join(' ')
49
74
  }
50
75
 
51
- export function buildAccountDialogOption(account: {
52
- id: string
53
- label: string
54
- role: string
55
- enabled: boolean
56
- quotaPercent: number | null
57
- tierLabel?: string
58
- }) {
76
+ function normalizeTuiCustodyMode(value: string): TuiCustodyMode {
77
+ return value === 'local' || value === 'claustrum'
78
+ ? value
79
+ : `mismatch: ${value}`
80
+ }
81
+
82
+ export function normalizeAccountDialogPayload(
83
+ value: unknown,
84
+ ): TuiAccountDialogPayload {
85
+ const payload =
86
+ value && typeof value === 'object' ? (value as Record<string, unknown>) : {}
87
+ const rawAccounts = Array.isArray(payload.accounts) ? payload.accounts : []
88
+ const accounts = rawAccounts.flatMap((value) => {
89
+ if (!value || typeof value !== 'object') return []
90
+ const account = value as Record<string, unknown>
91
+ const quotaPercent = account.quotaPercent
92
+ if (
93
+ typeof account.id !== 'string' ||
94
+ typeof account.label !== 'string' ||
95
+ (account.role !== 'main' && account.role !== 'fallback') ||
96
+ typeof account.enabled !== 'boolean' ||
97
+ (quotaPercent !== null &&
98
+ (typeof quotaPercent !== 'number' || !Number.isFinite(quotaPercent))) ||
99
+ (account.claustrumGate !== 'on' &&
100
+ account.claustrumGate !== 'off' &&
101
+ account.claustrumGate !== 'na') ||
102
+ typeof account.vaultServed !== 'boolean'
103
+ ) {
104
+ return []
105
+ }
106
+ const normalized = {
107
+ id: account.id,
108
+ label: account.label,
109
+ role: account.role,
110
+ enabled: account.enabled,
111
+ quotaPercent,
112
+ ...(typeof account.tierLabel === 'string' && {
113
+ tierLabel: account.tierLabel,
114
+ }),
115
+ claustrumGate: account.claustrumGate,
116
+ vaultServed: account.vaultServed,
117
+ } satisfies TuiAccountDialogAccount
118
+ const custodyState = account.custodyState
119
+ if (
120
+ (custodyState === 'na' ||
121
+ custodyState === 'off' ||
122
+ custodyState === 'on-vault-served' ||
123
+ custodyState === 'on-vault-reauth' ||
124
+ custodyState === 'on-cold') &&
125
+ typeof account.vaultReauth === 'boolean'
126
+ ) {
127
+ return [
128
+ {
129
+ ...normalized,
130
+ custodyState,
131
+ vaultReauth: account.vaultReauth,
132
+ },
133
+ ]
134
+ }
135
+ return [normalized]
136
+ })
137
+ const custodyMode: TuiCustodyMode | undefined =
138
+ payload.custodyModeKnown === true && typeof payload.custodyMode === 'string'
139
+ ? normalizeTuiCustodyMode(payload.custodyMode)
140
+ : undefined
141
+ return {
142
+ accounts,
143
+ claustrumDetection:
144
+ typeof payload.claustrumDetection === 'string'
145
+ ? payload.claustrumDetection
146
+ : 'unknown',
147
+ ...(custodyMode && { custodyMode }),
148
+ ...(typeof payload.enrollmentStatus === 'string' &&
149
+ payload.enrollmentStatus.length <= 2_000 && {
150
+ enrollmentStatus: payload.enrollmentStatus,
151
+ }),
152
+ }
153
+ }
154
+
155
+ export function buildAccountDialogOption(account: TuiAccountDialogAccount) {
59
156
  const pct =
60
157
  account.quotaPercent != null
61
158
  ? ` ${Math.round(account.quotaPercent)}%`
62
159
  : ' \u2013%'
63
160
  const status = !account.enabled ? ' (disabled)' : ''
161
+ const custody = account.custodyState
162
+ ? account.custodyState === 'na'
163
+ ? undefined
164
+ : custodyStatusLabel(account.custodyState).replace(
165
+ ' (OpenCode managed)',
166
+ '',
167
+ )
168
+ : undefined
64
169
  return {
65
- title: `${account.label} [${account.role}]${status}${pct}`,
170
+ title: `${account.label} [${account.role}]${status}${pct}${custody ? ` · custody ${custody}` : ''}`,
66
171
  value: account.id,
67
172
  ...(account.tierLabel && { description: account.tierLabel }),
68
173
  }
69
174
  }
70
175
 
176
+ export function buildManageAccountOptions(account: TuiAccountDialogAccount) {
177
+ const options: AccountDialogOption[] = []
178
+ const toggleLabel = account.enabled ? 'Disable' : 'Enable'
179
+ options.push({
180
+ title: toggleLabel,
181
+ value: account.enabled ? 'disable' : 'enable',
182
+ description: account.enabled
183
+ ? 'Stop using this fallback account'
184
+ : 'Allow this fallback account to be used',
185
+ })
186
+ options.push({
187
+ title: 'Move up',
188
+ value: 'move-up',
189
+ description: 'Higher priority in fallback order',
190
+ })
191
+ options.push({
192
+ title: 'Move down',
193
+ value: 'move-down',
194
+ description: 'Lower priority in fallback order',
195
+ })
196
+ options.push({
197
+ title: 'Remove\u2026',
198
+ value: 'remove',
199
+ description: 'Delete this account permanently',
200
+ })
201
+ options.push({ title: 'Back', value: 'back' })
202
+ return options
203
+ }
204
+
205
+ export function buildAccountDialogL1(value: unknown): {
206
+ header: string
207
+ options: AccountDialogOption[]
208
+ enrollmentStatus?: string
209
+ modeAction?: { command: 'claude-account'; arguments: 'local' | 'claustrum' }
210
+ } {
211
+ const payload = normalizeAccountDialogPayload(value)
212
+ const modeAction =
213
+ payload.custodyMode === 'local' || payload.custodyMode === 'claustrum'
214
+ ? {
215
+ command: 'claude-account' as const,
216
+ arguments:
217
+ payload.custodyMode === 'local'
218
+ ? ('claustrum' as const)
219
+ : ('local' as const),
220
+ }
221
+ : undefined
222
+ return {
223
+ header: payload.custodyMode
224
+ ? `Custody mode: ${payload.custodyMode}`
225
+ : 'Custody mode: unavailable from older server',
226
+ options: [
227
+ ...(modeAction
228
+ ? [
229
+ {
230
+ title:
231
+ payload.custodyMode === 'local'
232
+ ? 'Use Claustrum custody'
233
+ : 'Use local custody',
234
+ value: '__custody-mode__',
235
+ },
236
+ ]
237
+ : []),
238
+ {
239
+ title: 'Add account…',
240
+ value: '__add__',
241
+ description: 'Add an API key or OAuth fallback account',
242
+ },
243
+ ...payload.accounts.map(buildAccountDialogOption),
244
+ ],
245
+ ...(payload.enrollmentStatus && {
246
+ enrollmentStatus: payload.enrollmentStatus,
247
+ }),
248
+ ...(modeAction && { modeAction }),
249
+ }
250
+ }
251
+
252
+ export function retainAccountDialogProjection(
253
+ current: Record<string, unknown>,
254
+ incoming: Record<string, unknown>,
255
+ ): Record<string, unknown> {
256
+ return Array.isArray(incoming.accounts) ? incoming : current
257
+ }
258
+
71
259
  function showText(api: TuiPluginApi, text: string) {
72
260
  api.ui.dialog.setSize('xlarge')
73
261
  api.ui.dialog.replace(() => (
@@ -100,6 +288,8 @@ export function buildPrimeStatusRows(accounts: PrimeAccountStatus[]): string[] {
100
288
  const time = formatPrimeTime(account.lastPrimedAt)
101
289
  if (account.lastResult === 'error') {
102
290
  rows.push(`${account.label} \u00b7 primed ${time} err`)
291
+ } else if (account.lastResult === 'skipped') {
292
+ rows.push(`${account.label} \u00b7 primed ${time} skipped`)
103
293
  } else {
104
294
  rows.push(`${account.label} \u00b7 primed ${time} \u2713`)
105
295
  }
@@ -378,22 +568,17 @@ export function openCommandDialog(
378
568
  }
379
569
 
380
570
  if (payload.command === 'claude-account') {
381
- const accounts =
382
- (payload.knobs.accounts as Array<{
383
- id: string
384
- label: string
385
- role: string
386
- enabled: boolean
387
- quotaPercent: number | null
388
- tierLabel?: string
389
- }>) ?? []
571
+ let accountKnobs: Record<string, unknown> = payload.knobs
572
+ const accounts = normalizeAccountDialogPayload(accountKnobs).accounts
573
+ let statusMessage: { text: string; error?: boolean } | undefined
390
574
 
391
575
  const updateAccounts = (r: {
392
576
  text: string
393
577
  knobs: Record<string, unknown>
394
578
  }) => {
395
- const updated = r.knobs.accounts as typeof accounts
396
- if (updated && updated.length > 0) {
579
+ accountKnobs = retainAccountDialogProjection(accountKnobs, r.knobs)
580
+ const updated = normalizeAccountDialogPayload(accountKnobs).accounts
581
+ if (updated.length > 0) {
397
582
  accounts.length = 0
398
583
  accounts.push(...updated)
399
584
  }
@@ -401,44 +586,79 @@ export function openCommandDialog(
401
586
 
402
587
  const buildL1 = () => {
403
588
  const DialogSelect = api.ui.DialogSelect<string>
404
- const l1Options: Array<{
405
- title: string
406
- value: string
407
- description?: string
408
- }> = [
409
- {
410
- title: 'Add account\u2026',
411
- value: '__add__',
412
- description: 'Add an API key or OAuth fallback account',
413
- },
414
- ...accounts.map(buildAccountDialogOption),
415
- ]
589
+ const l1 = buildAccountDialogL1({ ...accountKnobs, accounts })
416
590
  api.ui.dialog.setSize('xlarge')
417
591
  api.ui.dialog.replace(() => (
418
- <DialogSelect
419
- title='Claude accounts'
420
- options={l1Options}
421
- onSelect={(option) => {
422
- if (option.value === '__add__') {
423
- openAddType()
424
- return
425
- }
426
- const account = accounts.find((a) => a.id === option.value)
427
- if (!account) return
428
- if (account.role === 'main') {
429
- const pct =
430
- account.quotaPercent != null
431
- ? ` ${Math.round(account.quotaPercent)}%`
432
- : ' \u2013%'
433
- showText(
434
- api,
435
- `${account.label}\nRole: main (read-only)\nQuota:${pct}`,
436
- )
437
- return
438
- }
439
- openManage(account, false)
440
- }}
441
- />
592
+ <box flexDirection='column' padding={1} width='100%'>
593
+ <text>{`Claustrum: ${normalizeAccountDialogPayload(accountKnobs).claustrumDetection}`}</text>
594
+ <text>{l1.header}</text>
595
+ {l1.enrollmentStatus && (
596
+ <box marginTop={1}>
597
+ <text>{l1.enrollmentStatus}</text>
598
+ </box>
599
+ )}
600
+ {statusMessage && (
601
+ <box marginTop={1}>
602
+ <text fg={statusMessage.error ? '#ef4444' : '#22c55e'}>
603
+ {statusMessage.text}
604
+ </text>
605
+ </box>
606
+ )}
607
+ <box marginTop={1}>
608
+ <DialogSelect
609
+ title='Claude accounts'
610
+ options={l1.options}
611
+ onSelect={(option) => {
612
+ if (option.value === '__add__') {
613
+ statusMessage = undefined
614
+ openAddType()
615
+ return
616
+ }
617
+ if (option.value === '__custody-mode__' && l1.modeAction) {
618
+ void apply(l1.modeAction.command, l1.modeAction.arguments)
619
+ .then((r) => {
620
+ const isRefusalOrError =
621
+ r.text.includes('refused') ||
622
+ r.text.includes('unavailable') ||
623
+ r.text.includes('failed') ||
624
+ r.text.includes('Error') ||
625
+ r.text.includes('error')
626
+ if (isRefusalOrError) {
627
+ statusMessage = { text: r.text, error: true }
628
+ } else {
629
+ statusMessage = { text: r.text, error: false }
630
+ api.ui.toast({ message: r.text })
631
+ }
632
+ updateAccounts(r)
633
+ buildL1()
634
+ })
635
+ .catch((err) => {
636
+ const msg =
637
+ err instanceof Error ? err.message : String(err)
638
+ statusMessage = { text: `Error: ${msg}`, error: true }
639
+ buildL1()
640
+ })
641
+ return
642
+ }
643
+ statusMessage = undefined
644
+ const account = accounts.find((a) => a.id === option.value)
645
+ if (!account) return
646
+ if (account.role === 'main') {
647
+ const pct =
648
+ account.quotaPercent != null
649
+ ? ` ${Math.round(account.quotaPercent)}%`
650
+ : ' \u2013%'
651
+ showText(
652
+ api,
653
+ `${account.label}\nRole: main (read-only)\nQuota:${pct}`,
654
+ )
655
+ return
656
+ }
657
+ openManage(account, false)
658
+ }}
659
+ />
660
+ </box>
661
+ </box>
442
662
  ))
443
663
  }
444
664
 
@@ -738,37 +958,9 @@ export function openCommandDialog(
738
958
  const DialogConfirm = api.ui.DialogConfirm
739
959
  api.ui.dialog.setSize('xlarge')
740
960
 
741
- const options: Array<{
742
- title: string
743
- value: string
744
- description?: string
745
- }> = []
746
- if (!isMain) {
747
- const toggleLabel = account.enabled ? 'Disable' : 'Enable'
748
- options.push({
749
- title: toggleLabel,
750
- value: account.enabled ? 'disable' : 'enable',
751
- description: account.enabled
752
- ? 'Stop using this fallback account'
753
- : 'Allow this fallback account to be used',
754
- })
755
- options.push({
756
- title: 'Move up',
757
- value: 'move-up',
758
- description: 'Higher priority in fallback order',
759
- })
760
- options.push({
761
- title: 'Move down',
762
- value: 'move-down',
763
- description: 'Lower priority in fallback order',
764
- })
765
- options.push({
766
- title: 'Remove\u2026',
767
- value: 'remove',
768
- description: 'Delete this account permanently',
769
- })
770
- }
771
- options.push({ title: 'Back', value: 'back' })
961
+ const options = isMain
962
+ ? [{ title: 'Back', value: 'back' }]
963
+ : buildManageAccountOptions(account)
772
964
 
773
965
  api.ui.dialog.replace(() => (
774
966
  <DialogSelect
@@ -786,13 +978,18 @@ export function openCommandDialog(
786
978
  title={`Remove ${account.label}?`}
787
979
  message={`Are you sure you want to remove the fallback account "${account.label}"?`}
788
980
  onConfirm={() => {
789
- void apply('claude-account', `remove ${account.id}`).then(
790
- (r) => {
981
+ void apply('claude-account', `remove ${account.id}`)
982
+ .then((r) => {
791
983
  api.ui.toast({ message: r.text })
792
984
  updateAccounts(r)
793
985
  buildL1()
794
- },
795
- )
986
+ })
987
+ .catch((err) => {
988
+ const msg =
989
+ err instanceof Error ? err.message : String(err)
990
+ statusMessage = { text: `Error: ${msg}`, error: true }
991
+ buildL1()
992
+ })
796
993
  }}
797
994
  onCancel={() => openManage(account, isMain)}
798
995
  />
@@ -800,18 +997,25 @@ export function openCommandDialog(
800
997
  return
801
998
  }
802
999
 
803
- void apply('claude-account', `${option.value} ${account.id}`).then(
804
- (r) => {
1000
+ const args = `${option.value} ${account.id}`
1001
+ void apply('claude-account', args)
1002
+ .then((r) => {
805
1003
  api.ui.toast({ message: r.text })
806
1004
  updateAccounts(r)
807
- const updatedList = r.knobs.accounts as typeof accounts
1005
+ const updatedList = normalizeAccountDialogPayload(
1006
+ r.knobs,
1007
+ ).accounts
808
1008
  const refreshed =
809
1009
  (updatedList && updatedList.length > 0
810
1010
  ? updatedList.find((a) => a.id === account.id)
811
1011
  : undefined) ?? account
812
1012
  openManage(refreshed, isMain)
813
- },
814
- )
1013
+ })
1014
+ .catch((err) => {
1015
+ const msg = err instanceof Error ? err.message : String(err)
1016
+ statusMessage = { text: `Error: ${msg}`, error: true }
1017
+ buildL1()
1018
+ })
815
1019
  }}
816
1020
  />
817
1021
  ))
@@ -1,3 +1,5 @@
1
+ import type { ClaustrumEnrollmentStatus } from '@cortexkit/anthropic-auth-core'
2
+
1
3
  export const COMMAND_MODAL_NAMES = [
2
4
  'claude-account',
3
5
  'claude-cache',
@@ -14,6 +16,37 @@ export const COMMAND_MODAL_NAMES = [
14
16
 
15
17
  export type CommandModalName = (typeof COMMAND_MODAL_NAMES)[number]
16
18
 
19
+ export interface AccountDialogAccount {
20
+ id: string
21
+ label: string
22
+ role: 'main' | 'fallback'
23
+ enabled: boolean
24
+ quotaPercent: number | null
25
+ tierLabel?: string
26
+ claustrumGate: 'on' | 'off' | 'na'
27
+ vaultServed: boolean
28
+ vaultReauth: boolean
29
+ custodyState:
30
+ | 'na'
31
+ | 'off'
32
+ | 'on-vault-served'
33
+ | 'on-vault-reauth'
34
+ | 'on-cold'
35
+ | 'unknown-identity'
36
+ | 'on-identity-mismatch'
37
+ | 'on-corrupt-binding'
38
+ }
39
+
40
+ export interface AccountDialogKnobs {
41
+ accounts: AccountDialogAccount[]
42
+ claustrumDetection: string
43
+ custodyMode?: 'local' | 'claustrum'
44
+ custodyModeKnown?: boolean
45
+ enrollmentStatus?: string
46
+ claustrumEnrollment?: ClaustrumEnrollmentStatus
47
+ [key: string]: unknown
48
+ }
49
+
17
50
  export interface OpenDialogPayload {
18
51
  command: CommandModalName
19
52
  text: string
@@ -24,7 +57,7 @@ export interface RpcNotification {
24
57
  id: number
25
58
  type: 'open-dialog'
26
59
  payload: OpenDialogPayload
27
- sessionId?: string
60
+ sessionId: string
28
61
  }
29
62
 
30
63
  export interface ApplyRequest {
@@ -4,7 +4,7 @@ import type { ApplyRequest, ApplyResult, RpcNotification } from './protocol'
4
4
  export interface RpcClient {
5
5
  pending: (
6
6
  lastReceivedId: number,
7
- sessionId?: string,
7
+ sessionId: string,
8
8
  ) => Promise<RpcNotification[]>
9
9
  apply: (request: ApplyRequest) => Promise<ApplyResult>
10
10
  }
@@ -34,6 +34,18 @@ export interface SidebarAccountState {
34
34
  // True when the account's refresh token is permanently dead (400
35
35
  // invalid_grant) and it needs a re-login — distinct from a transient backoff.
36
36
  needsReauth: boolean
37
+ // True when the vault copy needs re-importing while the sidecar remains usable.
38
+ vaultReauth?: boolean
39
+ // A gate alone does not prove the sidecar can serve a request.
40
+ vaultServed?: boolean
41
+ custodyState?:
42
+ | 'off'
43
+ | 'on-vault-served'
44
+ | 'on-vault-reauth'
45
+ | 'on-cold'
46
+ | 'unknown-identity'
47
+ | 'on-identity-mismatch'
48
+ | 'on-corrupt-binding'
37
49
  tierLabel?: string
38
50
  }
39
51
 
@@ -57,7 +69,7 @@ export interface PrimeSidebarAccountState {
57
69
  label: string
58
70
  nextDueAt?: number | null
59
71
  lastPrimedAt?: number | null
60
- lastResult?: 'ok' | 'error'
72
+ lastResult?: 'ok' | 'error' | 'skipped'
61
73
  usage?: PrimeUsageCounters
62
74
  estimatedCostUsd?: number
63
75
  }
@@ -178,7 +190,11 @@ function normalizePrimeAccount(
178
190
  } else if (isFiniteNumber(value.lastPrimedAt)) {
179
191
  account.lastPrimedAt = value.lastPrimedAt
180
192
  }
181
- if (value.lastResult === 'ok' || value.lastResult === 'error') {
193
+ if (
194
+ value.lastResult === 'ok' ||
195
+ value.lastResult === 'error' ||
196
+ value.lastResult === 'skipped'
197
+ ) {
182
198
  account.lastResult = value.lastResult
183
199
  }
184
200
  const usage = normalizePrimeUsage(value.usage)
@@ -335,6 +351,12 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
335
351
  enabled: typeof entry.enabled === 'boolean' ? entry.enabled : false,
336
352
  needsReauth:
337
353
  typeof entry.needsReauth === 'boolean' ? entry.needsReauth : false,
354
+ ...(entry.vaultReauth === true && { vaultReauth: true }),
355
+ ...(entry.vaultServed === true && { vaultServed: true }),
356
+ ...(typeof entry.custodyState === 'string' && {
357
+ custodyState:
358
+ entry.custodyState as SidebarAccountState['custodyState'],
359
+ }),
338
360
  tierLabel:
339
361
  typeof entry.tierLabel === 'string' && entry.tierLabel.trim()
340
362
  ? entry.tierLabel.trim()
@@ -715,6 +737,8 @@ export async function setSidebarState(
715
737
  lastUpdated: Math.max(current.lastUpdated, state.lastUpdated),
716
738
  }
717
739
  }
740
+ // The state file is a cross-process artifact; future account fields must fail closed.
741
+ stateToWrite = normalizeSidebarState(stateToWrite)
718
742
  result = await writeSidebarStateAtomic(
719
743
  stateFile,
720
744
  stateToWrite,
@@ -795,13 +819,25 @@ export function formatScopedQuotaLabel(title: string) {
795
819
  }
796
820
 
797
821
  /**
798
- * Pretty label for a recoverable-refusal source model (Fable 5 or Opus 5).
822
+ * Pretty label for a recoverable-refusal source model (Fable 5/5.1 or Opus 5).
799
823
  * Falls back to the raw id for unrecognized ids so the sidebar never goes
800
824
  * blank on a future model that has not yet been cataloged here.
801
825
  */
802
826
  export function formatFallbackModelLabel(modelId: string | undefined): string {
827
+ if (
828
+ modelId === 'claude-mythos-5-1' ||
829
+ modelId?.startsWith('claude-mythos-5-1-')
830
+ )
831
+ return 'Mythos 5.1'
832
+ if (
833
+ modelId === 'claude-fable-5-1' ||
834
+ modelId?.startsWith('claude-fable-5-1-')
835
+ )
836
+ return 'Fable 5.1'
803
837
  if (modelId === 'claude-fable-5' || modelId?.startsWith('claude-fable-5-'))
804
838
  return 'Fable 5'
839
+ if (modelId === 'claude-opus-5-5' || modelId?.startsWith('claude-opus-5-5-'))
840
+ return 'Opus 5.5'
805
841
  if (modelId === 'claude-opus-5' || modelId?.startsWith('claude-opus-5-'))
806
842
  return 'Opus 5'
807
843
  if (modelId === 'claude-opus-4-8' || modelId?.startsWith('claude-opus-4-8-'))
@@ -829,6 +865,9 @@ export function formatPrimeAccountValue(account: PrimeSidebarAccountState): {
829
865
  if (account.lastResult === 'error') {
830
866
  return { text: 'err', hasError: true }
831
867
  }
868
+ if (account.lastResult === 'skipped') {
869
+ return { text: 'skip', hasError: false }
870
+ }
832
871
  if (account.nextDueAt && account.nextDueAt > Date.now()) {
833
872
  return { text: formatPrimeTime(account.nextDueAt), hasError: false }
834
873
  }