@cortexkit/opencode-anthropic-auth 1.23.0 → 2.0.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.
- package/README.md +239 -195
- package/dist/cli-failure.d.ts +69 -0
- package/dist/cli.d.ts +14 -9
- package/dist/cli.js +98 -76
- package/dist/custody-dimensions.d.ts +7 -24
- package/dist/custody-mode.d.ts +29 -169
- package/dist/effort-history.d.ts +6 -1
- package/dist/index-1jkyhgyr.js +2 -0
- package/dist/{index-8y2t4mwz.js → index-2arfb1y3.js} +1 -1
- package/dist/{index-0jfsxyn9.js → index-4ya27n12.js} +1 -1
- package/dist/index-aaajxwzd.js +2 -0
- package/dist/index-ae3fq4n6.js +42 -0
- package/dist/{index-xzbhzz0a.js → index-fds0s2j4.js} +2 -2
- package/dist/index-nngmvczq.js +450 -0
- package/dist/index-pn597b6f.js +2 -0
- package/dist/{index-1gr8v4fn.js → index-rqync0pc.js} +1 -1
- package/dist/index.js +1 -38
- package/dist/native-command.d.ts +19 -0
- package/dist/rpc/notifications.d.ts +2 -1
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +8 -4
- package/dist/rpc/protocol.js +1 -1
- package/dist/rpc/rpc-client.d.ts +2 -0
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/rpc/rpc-server.d.ts +5 -1
- package/dist/setup/command.d.ts +12 -0
- package/dist/setup/command.test.d.ts +1 -0
- package/dist/setup/native-activation.d.ts +29 -0
- package/dist/setup/native-enrollment.d.ts +26 -0
- package/dist/setup/native-enrollment.test.d.ts +1 -0
- package/dist/setup/native-local.d.ts +20 -0
- package/dist/setup/native-login.d.ts +3 -0
- package/dist/setup/native-paths.d.ts +15 -0
- package/dist/setup/native-paths.test.d.ts +1 -0
- package/dist/setup/pi.d.ts +13 -3
- package/dist/setup/process-fence.d.ts +7 -1
- package/dist/setup/process-fence.test.d.ts +1 -0
- package/dist/sidebar-state.js +1 -1
- package/dist/tui/command-dialogs.d.ts +5 -0
- package/dist/tui-preferences.js +2 -2
- package/dist/v2/activation.d.ts +7 -0
- package/dist/v2/adapter.d.ts +276 -0
- package/dist/v2/install.d.ts +13 -0
- package/dist/v2/lifecycle.d.ts +8 -0
- package/dist/v2/menu-contract.d.ts +8 -0
- package/dist/v2/menu.d.ts +10 -0
- package/dist/v2/models.d.ts +6 -0
- package/dist/v2/ownership.d.ts +11 -0
- package/dist/v2/prime.d.ts +12 -0
- package/dist/v2/rpc.d.ts +50 -0
- package/dist/v2/server.d.ts +8 -0
- package/dist/v2/server.js +4 -0
- package/dist/v2/setup.d.ts +58 -0
- package/dist/v2/source.d.ts +41 -0
- package/dist/v2/tui.d.ts +13 -0
- package/dist/v2/tui.js +2 -0
- package/dist/v2/wire.d.ts +36 -0
- package/package.json +20 -5
- package/src/rpc/notifications.ts +9 -2
- package/src/rpc/protocol.ts +8 -7
- package/src/rpc/rpc-client.ts +17 -0
- package/src/rpc/rpc-server.ts +20 -3
- package/src/tui/command-dialogs.tsx +264 -0
- package/src/tui/entry.mjs +44 -18
- package/src/tui-compiled/rpc/protocol.ts +8 -7
- package/src/tui-compiled/rpc/rpc-client.ts +17 -0
- package/src/tui-compiled/tui/command-dialogs.tsx +315 -76
- package/src/tui-compiled/tui.tsx +24 -7
- package/src/tui.tsx +37 -9
- package/dist/claustrum-enrollment-registry.d.ts +0 -4
- package/dist/custody-enrollment.d.ts +0 -14
- package/dist/custody-live.d.ts +0 -111
- package/dist/custody-manifest-watcher.d.ts +0 -16
- package/dist/index-1fq7h6yd.js +0 -430
- package/dist/index-797pmamn.js +0 -2
- package/dist/index-a5d6v3ba.js +0 -5
- package/dist/index-n96q8pks.js +0 -2
- package/dist/index-xxhyhp98.js +0 -2
- package/dist/index-z7gfdyv3.js +0 -1
- package/dist/local-login.d.ts +0 -64
- package/dist/logger-kk9vyem9.js +0 -1
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
CommandApplyRequest,
|
|
5
|
+
CommandApplyResult,
|
|
6
|
+
CommandDialogPayload,
|
|
7
|
+
CommandMenuModel,
|
|
8
|
+
KnobValue,
|
|
9
|
+
MenuAction,
|
|
10
|
+
MenuItem,
|
|
11
|
+
MenuSection,
|
|
12
|
+
} from '@cortexkit/anthropic-auth-core'
|
|
2
13
|
import {
|
|
3
14
|
custodyStatusLabel,
|
|
4
15
|
type PrimeAccountStatus,
|
|
@@ -8,6 +19,259 @@ import type { AccountDialogAccount } from '../rpc/protocol'
|
|
|
8
19
|
import type { OpenDialogPayload } from '../rpc/protocol.js'
|
|
9
20
|
import { formatPrimeCost, formatPrimeTime } from '../sidebar-state.js'
|
|
10
21
|
|
|
22
|
+
/** Render menu sections, accounts and action inputs; send changes to the host instead of writing credentials here. */
|
|
23
|
+
export function openNativeMenuDialog(
|
|
24
|
+
api: {
|
|
25
|
+
readonly ui: Pick<
|
|
26
|
+
TuiPluginApi['ui'],
|
|
27
|
+
'dialog' | 'DialogSelect' | 'DialogPrompt' | 'DialogConfirm' | 'toast'
|
|
28
|
+
>
|
|
29
|
+
},
|
|
30
|
+
payload: CommandDialogPayload,
|
|
31
|
+
apply: (request: CommandApplyRequest) => Promise<CommandApplyResult>,
|
|
32
|
+
isCurrentSession: () => boolean,
|
|
33
|
+
): void {
|
|
34
|
+
let menu: CommandMenuModel = payload.menu
|
|
35
|
+
let inFlight = false
|
|
36
|
+
const DialogSelect = api.ui.DialogSelect<string>
|
|
37
|
+
const DialogPrompt = api.ui.DialogPrompt
|
|
38
|
+
const DialogConfirm = api.ui.DialogConfirm
|
|
39
|
+
const active = () => isCurrentSession() && !inFlight
|
|
40
|
+
|
|
41
|
+
const root = () => {
|
|
42
|
+
if (!active()) return
|
|
43
|
+
api.ui.dialog.setSize('xlarge')
|
|
44
|
+
api.ui.dialog.replace(() => (
|
|
45
|
+
<DialogSelect
|
|
46
|
+
title={menu.title}
|
|
47
|
+
options={[
|
|
48
|
+
...menu.sections.map((section) => ({
|
|
49
|
+
title: section.title,
|
|
50
|
+
value: section.id,
|
|
51
|
+
})),
|
|
52
|
+
{ title: 'Close', value: '__close' },
|
|
53
|
+
]}
|
|
54
|
+
onSelect={(option) => {
|
|
55
|
+
if (!active()) return
|
|
56
|
+
const section = menu.sections.find(
|
|
57
|
+
(entry) => entry.id === option.value,
|
|
58
|
+
)
|
|
59
|
+
if (section) showSection(section)
|
|
60
|
+
else if (option.value === '__close') api.ui.dialog.clear()
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const showSection = (section: MenuSection) => {
|
|
67
|
+
if (!active()) return
|
|
68
|
+
api.ui.dialog.replace(() => (
|
|
69
|
+
<box flexDirection='column' padding={1}>
|
|
70
|
+
<text>{section.lines.join('\n')}</text>
|
|
71
|
+
<DialogSelect
|
|
72
|
+
title={section.title}
|
|
73
|
+
options={[
|
|
74
|
+
...section.items.map((item) => ({
|
|
75
|
+
title: item.label,
|
|
76
|
+
value: `item:${item.id}`,
|
|
77
|
+
description: item.detail,
|
|
78
|
+
})),
|
|
79
|
+
...section.actions.map((action) => ({
|
|
80
|
+
title: action.label,
|
|
81
|
+
value: `action:${action.id}`,
|
|
82
|
+
description: action.description,
|
|
83
|
+
})),
|
|
84
|
+
{ title: 'Back', value: '__back' },
|
|
85
|
+
]}
|
|
86
|
+
onSelect={(option) => {
|
|
87
|
+
if (!active()) return
|
|
88
|
+
if (option.value === '__back') return root()
|
|
89
|
+
const action = section.actions.find(
|
|
90
|
+
(entry) => `action:${entry.id}` === option.value,
|
|
91
|
+
)
|
|
92
|
+
if (action) return collect(section, action)
|
|
93
|
+
const item = section.items.find(
|
|
94
|
+
(entry) => `item:${entry.id}` === option.value,
|
|
95
|
+
)
|
|
96
|
+
if (item) showItem(section, item)
|
|
97
|
+
}}
|
|
98
|
+
/>
|
|
99
|
+
</box>
|
|
100
|
+
))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const showItem = (section: MenuSection, item: MenuItem) => {
|
|
104
|
+
if (!active()) return
|
|
105
|
+
api.ui.dialog.replace(() => (
|
|
106
|
+
<box flexDirection='column' padding={1}>
|
|
107
|
+
<text>{item.detail ?? ''}</text>
|
|
108
|
+
<DialogSelect
|
|
109
|
+
title={item.label}
|
|
110
|
+
options={[
|
|
111
|
+
...item.actions.map((action) => ({
|
|
112
|
+
title: action.label,
|
|
113
|
+
value: action.id,
|
|
114
|
+
description: action.description,
|
|
115
|
+
})),
|
|
116
|
+
{ title: 'Back', value: '__back' },
|
|
117
|
+
]}
|
|
118
|
+
onSelect={(option) => {
|
|
119
|
+
if (!active()) return
|
|
120
|
+
const action = item.actions.find(
|
|
121
|
+
(entry) => entry.id === option.value,
|
|
122
|
+
)
|
|
123
|
+
if (action) collect(section, action, item)
|
|
124
|
+
else if (option.value === '__back') showSection(section)
|
|
125
|
+
}}
|
|
126
|
+
/>
|
|
127
|
+
</box>
|
|
128
|
+
))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const collect = (
|
|
132
|
+
section: MenuSection,
|
|
133
|
+
action: MenuAction,
|
|
134
|
+
item?: MenuItem,
|
|
135
|
+
) => {
|
|
136
|
+
const values: Record<string, KnobValue> = {}
|
|
137
|
+
const back = () => {
|
|
138
|
+
for (const key of Object.keys(values)) delete values[key]
|
|
139
|
+
if (item) showItem(section, item)
|
|
140
|
+
else showSection(section)
|
|
141
|
+
}
|
|
142
|
+
const submit = async () => {
|
|
143
|
+
if (!active()) return
|
|
144
|
+
inFlight = true
|
|
145
|
+
try {
|
|
146
|
+
const result = await apply({
|
|
147
|
+
command: menu.command,
|
|
148
|
+
sectionId: section.id,
|
|
149
|
+
actionId: action.id,
|
|
150
|
+
...(item ? { itemId: item.id } : {}),
|
|
151
|
+
values: { ...values },
|
|
152
|
+
...(action.confirm ? { confirmed: true } : {}),
|
|
153
|
+
})
|
|
154
|
+
if (!isCurrentSession()) return
|
|
155
|
+
menu = result.menu
|
|
156
|
+
api.ui.toast({ message: result.text })
|
|
157
|
+
} catch {
|
|
158
|
+
if (isCurrentSession())
|
|
159
|
+
api.ui.toast({ message: 'Menu apply unavailable; outcome unknown.' })
|
|
160
|
+
} finally {
|
|
161
|
+
for (const key of Object.keys(values)) delete values[key]
|
|
162
|
+
inFlight = false
|
|
163
|
+
if (isCurrentSession()) {
|
|
164
|
+
const refreshed = menu.sections.find(
|
|
165
|
+
(entry) => entry.id === section.id,
|
|
166
|
+
)
|
|
167
|
+
if (refreshed) showSection(refreshed)
|
|
168
|
+
else root()
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const next = (index: number) => {
|
|
173
|
+
if (!active()) return
|
|
174
|
+
const knob = action.knobs[index]
|
|
175
|
+
if (!knob) {
|
|
176
|
+
if (!action.confirm) return void submit()
|
|
177
|
+
api.ui.dialog.replace(() => (
|
|
178
|
+
<DialogConfirm
|
|
179
|
+
title={action.label}
|
|
180
|
+
message={action.confirm?.message ?? ''}
|
|
181
|
+
onConfirm={() => void submit()}
|
|
182
|
+
onCancel={back}
|
|
183
|
+
/>
|
|
184
|
+
))
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
if (knob.kind === 'choice' || knob.kind === 'toggle') {
|
|
188
|
+
const choices =
|
|
189
|
+
knob.kind === 'choice'
|
|
190
|
+
? knob.choices
|
|
191
|
+
: [
|
|
192
|
+
{ value: 'on', label: 'On' },
|
|
193
|
+
{ value: 'off', label: 'Off' },
|
|
194
|
+
]
|
|
195
|
+
api.ui.dialog.replace(() => (
|
|
196
|
+
<DialogSelect
|
|
197
|
+
title={knob.label}
|
|
198
|
+
options={[
|
|
199
|
+
...choices.map((choice) => ({
|
|
200
|
+
title: choice.label,
|
|
201
|
+
value: choice.value,
|
|
202
|
+
})),
|
|
203
|
+
{ title: 'Cancel', value: '__cancel' },
|
|
204
|
+
]}
|
|
205
|
+
onSelect={(option) => {
|
|
206
|
+
if (!active()) return
|
|
207
|
+
if (option.value === '__cancel') return back()
|
|
208
|
+
values[knob.id] =
|
|
209
|
+
knob.kind === 'toggle' ? option.value === 'on' : option.value
|
|
210
|
+
next(index + 1)
|
|
211
|
+
}}
|
|
212
|
+
/>
|
|
213
|
+
))
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
// DialogPrompt has no masking capability in the host API. Never seed a
|
|
217
|
+
// protected input or claim it is hidden; warn before collecting it.
|
|
218
|
+
api.ui.dialog.replace(() => (
|
|
219
|
+
<DialogPrompt
|
|
220
|
+
title={knob.label}
|
|
221
|
+
description={() => (
|
|
222
|
+
<text>
|
|
223
|
+
{knob.kind === 'text' && knob.masked
|
|
224
|
+
? 'This host prompt displays input. Avoid screen sharing; the value is not echoed in menu results.'
|
|
225
|
+
: knob.required
|
|
226
|
+
? 'Required input'
|
|
227
|
+
: 'Optional; leave empty for the default'}
|
|
228
|
+
</text>
|
|
229
|
+
)}
|
|
230
|
+
value={
|
|
231
|
+
knob.kind === 'text' && knob.masked
|
|
232
|
+
? ''
|
|
233
|
+
: knob.value === undefined
|
|
234
|
+
? ''
|
|
235
|
+
: String(knob.value)
|
|
236
|
+
}
|
|
237
|
+
placeholder={
|
|
238
|
+
knob.kind === 'text'
|
|
239
|
+
? knob.placeholder
|
|
240
|
+
: `${knob.min ?? ''}-${knob.max ?? ''}`
|
|
241
|
+
}
|
|
242
|
+
onCancel={back}
|
|
243
|
+
onConfirm={(typed) => {
|
|
244
|
+
if (!active()) return
|
|
245
|
+
const text = typed.trim()
|
|
246
|
+
if (!text && knob.required) {
|
|
247
|
+
api.ui.toast({ message: `${knob.label} is required.` })
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
const value =
|
|
251
|
+
text === '' ? null : knob.kind === 'number' ? Number(text) : text
|
|
252
|
+
if (
|
|
253
|
+
knob.kind === 'number' &&
|
|
254
|
+
typeof value === 'number' &&
|
|
255
|
+
(!Number.isFinite(value) ||
|
|
256
|
+
(knob.min !== undefined && value < knob.min) ||
|
|
257
|
+
(knob.max !== undefined && value > knob.max))
|
|
258
|
+
) {
|
|
259
|
+
api.ui.toast({
|
|
260
|
+
message: `${knob.label} needs a number in range.`,
|
|
261
|
+
})
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
values[knob.id] = value
|
|
265
|
+
next(index + 1)
|
|
266
|
+
}}
|
|
267
|
+
/>
|
|
268
|
+
))
|
|
269
|
+
}
|
|
270
|
+
next(0)
|
|
271
|
+
}
|
|
272
|
+
root()
|
|
273
|
+
}
|
|
274
|
+
|
|
11
275
|
type ApplyFn = (
|
|
12
276
|
command: OpenDialogPayload['command'],
|
|
13
277
|
args: string,
|
package/src/tui/entry.mjs
CHANGED
|
@@ -12,26 +12,52 @@ function isMissingRuntimeRegistry(error) {
|
|
|
12
12
|
)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
await import(runtimeProbe)
|
|
18
|
-
} catch (error) {
|
|
19
|
-
if (!isMissingRuntimeRegistry(error)) {
|
|
20
|
-
console.error('Anthropic Auth TUI runtime registry probe failed', error)
|
|
21
|
-
throw error
|
|
22
|
-
}
|
|
23
|
-
// Older hosts and bare Bun do not provide the virtual registry. Their source
|
|
24
|
-
// loader still applies the Solid transform, so retain the raw TSX fallback.
|
|
25
|
-
mod = await import('../tui.tsx')
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (!mod) {
|
|
15
|
+
async function loadLegacyTui() {
|
|
16
|
+
let mod
|
|
29
17
|
try {
|
|
30
|
-
|
|
18
|
+
await import(runtimeProbe)
|
|
31
19
|
} catch (error) {
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
if (!isMissingRuntimeRegistry(error)) {
|
|
21
|
+
console.error('Anthropic Auth TUI runtime registry probe failed', error)
|
|
22
|
+
throw error
|
|
23
|
+
}
|
|
24
|
+
// Older hosts and bare Bun do not provide the virtual registry. Their source
|
|
25
|
+
// loader still applies the Solid transform, so retain the raw TSX fallback.
|
|
26
|
+
mod = await import('../tui.tsx')
|
|
34
27
|
}
|
|
28
|
+
|
|
29
|
+
if (!mod) {
|
|
30
|
+
try {
|
|
31
|
+
mod = await import('../tui-compiled/tui.tsx')
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error('Anthropic Auth compiled TUI failed to load', error)
|
|
34
|
+
throw error
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return mod.default
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let legacyModule
|
|
41
|
+
|
|
42
|
+
// OpenCode 1 reads id and tui from the default definition; OpenCode 2 reads
|
|
43
|
+
// its setup method. Both use this package subpath, so these methods must be
|
|
44
|
+
// on the default export, not only exported as separate named bindings.
|
|
45
|
+
export const id = 'cortexkit.anthropic-auth'
|
|
46
|
+
|
|
47
|
+
/** @type {import('@opencode-ai/plugin/tui').TuiPlugin} */
|
|
48
|
+
export const tui = async (...args) => {
|
|
49
|
+
legacyModule ??= loadLegacyTui()
|
|
50
|
+
const plugin = await legacyModule
|
|
51
|
+
return plugin.tui(...args)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** @type {import('@opencode/plugin/tui').Plugin.Definition['setup']} */
|
|
55
|
+
export const setup = async (context) => {
|
|
56
|
+
// OpenCode 2 already routes RPC through the active project. Importing its
|
|
57
|
+
// TUI must not load the OpenCode 1 renderer or start that renderer's separate
|
|
58
|
+
// loopback notification poller.
|
|
59
|
+
const { setupAnthropicTui } = await import('../../dist/v2/tui.js')
|
|
60
|
+
return setupAnthropicTui(context)
|
|
35
61
|
}
|
|
36
62
|
|
|
37
|
-
export default
|
|
63
|
+
export default { id, tui, setup }
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ClaustrumEnrollmentStatus,
|
|
3
|
+
CommandDialogPayload,
|
|
4
|
+
} from '@cortexkit/anthropic-auth-core'
|
|
2
5
|
|
|
3
6
|
export const COMMAND_MODAL_NAMES = [
|
|
4
7
|
'claude-account',
|
|
@@ -53,12 +56,10 @@ export interface OpenDialogPayload {
|
|
|
53
56
|
knobs: Record<string, unknown>
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
export
|
|
57
|
-
|
|
58
|
-
type: 'open-
|
|
59
|
-
|
|
60
|
-
sessionId: string
|
|
61
|
-
}
|
|
59
|
+
export type RpcNotification = { id: number; sessionId: string } & (
|
|
60
|
+
| { type: 'open-dialog'; payload: OpenDialogPayload }
|
|
61
|
+
| { type: 'open-menu'; payload: CommandDialogPayload }
|
|
62
|
+
)
|
|
62
63
|
|
|
63
64
|
export interface ApplyRequest {
|
|
64
65
|
command: CommandModalName
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CommandApplyRequest,
|
|
3
|
+
CommandApplyResult,
|
|
4
|
+
} from '@cortexkit/anthropic-auth-core'
|
|
1
5
|
import { discoverPortFile } from './port-file'
|
|
2
6
|
import type { ApplyRequest, ApplyResult, RpcNotification } from './protocol'
|
|
3
7
|
|
|
@@ -7,6 +11,7 @@ export interface RpcClient {
|
|
|
7
11
|
sessionId: string,
|
|
8
12
|
) => Promise<RpcNotification[]>
|
|
9
13
|
apply: (request: ApplyRequest) => Promise<ApplyResult>
|
|
14
|
+
applyMenu: (request: CommandApplyRequest) => Promise<CommandApplyResult>
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
async function call<T>(
|
|
@@ -58,5 +63,17 @@ export function createRpcClient(dir: string, expectedPid?: number): RpcClient {
|
|
|
58
63
|
)
|
|
59
64
|
return out ?? { text: 'apply failed', knobs: {} }
|
|
60
65
|
},
|
|
66
|
+
async applyMenu(request) {
|
|
67
|
+
if (request.command !== 'claude' || !request.sessionId?.trim())
|
|
68
|
+
throw new TypeError('A claude menu sessionId is required')
|
|
69
|
+
const out = await call<CommandApplyResult>(
|
|
70
|
+
dir,
|
|
71
|
+
'apply-menu',
|
|
72
|
+
{ ...request },
|
|
73
|
+
expectedPid,
|
|
74
|
+
)
|
|
75
|
+
if (!out) throw new Error('Menu apply unavailable; outcome unknown')
|
|
76
|
+
return out
|
|
77
|
+
},
|
|
61
78
|
}
|
|
62
79
|
}
|