@meistrari/agent-core 0.0.0 → 0.1.1
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 +36 -3
- package/bin/supervisor.ts +116 -0
- package/package.json +42 -3
- package/scripts/build-supervisor-executable.ts +32 -0
- package/src/agents/agent-error-serializer.ts +23 -0
- package/src/agents/agent-event-stream.ts +81 -0
- package/src/agents/agent-id.ts +17 -0
- package/src/agents/agent-operation.ts +11 -0
- package/src/agents/agent-provider.ts +37 -0
- package/src/agents/agent-run.ts +32 -0
- package/src/agents/agent-runtime-error.ts +161 -0
- package/src/agents/agent-session-events.ts +32 -0
- package/src/agents/agent-tool-runner.ts +83 -0
- package/src/agents/agent-tool.ts +111 -0
- package/src/agents/author-context.ts +36 -0
- package/src/agents/claude/claude-command-mapper.ts +234 -0
- package/src/agents/claude/claude-event-mapper.ts +748 -0
- package/src/agents/claude/claude-provider.ts +191 -0
- package/src/agents/claude/claude-run.ts +468 -0
- package/src/agents/claude/claude-tool-mapper.ts +186 -0
- package/src/agents/claude/index.ts +1 -0
- package/src/agents/codex/codex-auth.ts +34 -0
- package/src/agents/codex/codex-command-mapper.ts +78 -0
- package/src/agents/codex/codex-event-mapper.ts +721 -0
- package/src/agents/codex/codex-json-rpc-client.ts +326 -0
- package/src/agents/codex/codex-protocol.ts +36 -0
- package/src/agents/codex/codex-provider.ts +1050 -0
- package/src/agents/codex/codex-run.ts +404 -0
- package/src/agents/codex/codex-skill-catalog.ts +158 -0
- package/src/agents/codex/codex-skill-roots.ts +19 -0
- package/src/agents/codex/codex-tool-mapper.ts +55 -0
- package/src/agents/codex/codex.errors.ts +68 -0
- package/src/agents/codex/generated/meta.gen.ts +606 -0
- package/src/agents/codex/generated/namespaces.gen.ts +311 -0
- package/src/agents/codex/generated/schema.gen.ts +34883 -0
- package/src/agents/codex/index.ts +2 -0
- package/src/agents/index.ts +14 -0
- package/src/agents/input-attachment-preparation.ts +86 -0
- package/src/agents/input-attachment.errors.ts +16 -0
- package/src/agents/instructions.ts +8 -0
- package/src/agents/materialized-input-attachment.ts +26 -0
- package/src/agents/message-id.ts +23 -0
- package/src/agents/normalize.ts +8 -0
- package/src/agents/sandbox-environment.ts +1 -0
- package/src/agents/tools/ping.tool.ts +13 -0
- package/src/agents/user-input-request.ts +470 -0
- package/src/protocol/agent-event.ts +2 -1
- package/src/protocol/agent-usage.ts +14 -0
- package/src/provenance.gen.ts +3 -3
- package/src/supervisor/agent-provider-factory.ts +189 -0
- package/src/supervisor/bootstrap-binder.ts +133 -0
- package/src/supervisor/config.ts +49 -0
- package/src/supervisor/control-authority-verifier.ts +135 -0
- package/src/supervisor/create-supervisor-runtime.ts +25 -0
- package/src/supervisor/errors.ts +24 -0
- package/src/supervisor/index.ts +34 -0
- package/src/supervisor/persistence/json.ts +21 -0
- package/src/supervisor/persistence/state-discovery.ts +56 -0
- package/src/supervisor/persistence/supervisor-store.ts +364 -0
- package/src/supervisor/ports/index.ts +109 -0
- package/src/supervisor/provider-factory.ts +37 -0
- package/src/supervisor/resident.ts +143 -0
- package/src/supervisor/rpc-client.ts +120 -0
- package/src/supervisor/runtime-handler.ts +309 -0
- package/src/supervisor/websocket-server.ts +435 -0
- package/src/supervisor-protocol/bootstrap.ts +1 -1
- package/src/template-onboarding.ts +47 -0
- package/src/testing/es256-test-keys.ts +73 -0
- package/src/testing/in-memory-runtime-control-plane.ts +206 -0
- package/src/testing/index.ts +6 -0
- package/src/testing/loopback-supervisor-connection.ts +71 -0
- package/src/testing/scripted-provider.ts +64 -0
- package/src/worker-runtime-client/command-pump.ts +127 -0
- package/src/worker-runtime-client/connection-attempt.ts +349 -0
- package/src/worker-runtime-client/control-authority-signer.ts +100 -0
- package/src/worker-runtime-client/e2b-supervisor-connection.ts +102 -0
- package/src/worker-runtime-client/frame-processor.ts +178 -0
- package/src/worker-runtime-client/index.ts +27 -0
- package/src/worker-runtime-client/lease-reconciler.ts +14 -0
- package/src/worker-runtime-client/ports.ts +139 -0
- package/src/worker-runtime-client/postgres-notification-listener.ts +91 -0
- package/src/worker-runtime-client/rpc-dispatcher.ts +27 -0
- package/src/worker-runtime-client/rpc-request-manager.ts +141 -0
- package/src/worker-runtime-client/sandbox-connection-runtime.ts +301 -0
- package/src/worker-runtime-client/token-crypto.ts +46 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import type { Logger } from '../logger'
|
|
2
|
+
import type {
|
|
3
|
+
BootstrapProvider,
|
|
4
|
+
CommandSource,
|
|
5
|
+
ConnectionAuthorityStore,
|
|
6
|
+
ControlAuthoritySigner,
|
|
7
|
+
DurableEventSink,
|
|
8
|
+
EphemeralEventSink,
|
|
9
|
+
RpcDispatcher,
|
|
10
|
+
RuntimeNotifications,
|
|
11
|
+
SandboxConnectionRuntimeTimings,
|
|
12
|
+
SessionFailureSink,
|
|
13
|
+
SupervisorConnectionOpener,
|
|
14
|
+
} from './ports'
|
|
15
|
+
import {
|
|
16
|
+
ConnectionAuthorityLostError,
|
|
17
|
+
ConnectionClosedError,
|
|
18
|
+
DeterministicEstablishmentError,
|
|
19
|
+
SessionSandboxConnectionAttempt,
|
|
20
|
+
} from './connection-attempt'
|
|
21
|
+
import { TokenDecryptionError } from './token-crypto'
|
|
22
|
+
|
|
23
|
+
const defaultTimings: SandboxConnectionRuntimeTimings = {
|
|
24
|
+
leaseDurationMs: 30_000,
|
|
25
|
+
leaseRenewIntervalMs: 10_000,
|
|
26
|
+
establishmentBudgetMs: 30_000,
|
|
27
|
+
dialTimeoutMs: 10_000,
|
|
28
|
+
retryInitialDelayMs: 250,
|
|
29
|
+
retryMaxDelayMs: 5_000,
|
|
30
|
+
candidateLimit: 100,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createSandboxConnectionRuntime(input: {
|
|
34
|
+
replicaId: string
|
|
35
|
+
trafficTokenEncryptionKey: string
|
|
36
|
+
signControlAuthority: ControlAuthoritySigner
|
|
37
|
+
openSupervisorConnection: SupervisorConnectionOpener
|
|
38
|
+
authorityStore: ConnectionAuthorityStore
|
|
39
|
+
commands: CommandSource
|
|
40
|
+
durableEvents: DurableEventSink
|
|
41
|
+
ephemeralEvents: EphemeralEventSink
|
|
42
|
+
rpc: RpcDispatcher
|
|
43
|
+
bootstrap: BootstrapProvider
|
|
44
|
+
failures: SessionFailureSink
|
|
45
|
+
notifications: RuntimeNotifications
|
|
46
|
+
timings?: Partial<SandboxConnectionRuntimeTimings>
|
|
47
|
+
logger: Logger
|
|
48
|
+
}): {
|
|
49
|
+
start: () => Promise<void>
|
|
50
|
+
stop: () => Promise<void>
|
|
51
|
+
wake: (sessionSandboxId: string) => void
|
|
52
|
+
wakeCommands: (sessionId: string) => void
|
|
53
|
+
} {
|
|
54
|
+
const timings = validatedTimings({ ...defaultTimings, ...input.timings })
|
|
55
|
+
const owned = new Map<string, OwnedConnection>()
|
|
56
|
+
const sessionIndex = new Map<string, string>()
|
|
57
|
+
let started = false
|
|
58
|
+
let stopping = false
|
|
59
|
+
|
|
60
|
+
const wake = (sessionSandboxId: string) => {
|
|
61
|
+
if (!started || stopping || owned.has(sessionSandboxId))
|
|
62
|
+
return
|
|
63
|
+
const connection = new OwnedConnection({ ...input, timings, sessionSandboxId })
|
|
64
|
+
owned.set(sessionSandboxId, connection)
|
|
65
|
+
void connection.run().finally(() => {
|
|
66
|
+
if (owned.get(sessionSandboxId) === connection)
|
|
67
|
+
owned.delete(sessionSandboxId)
|
|
68
|
+
const sessionId = connection.sessionId()
|
|
69
|
+
if (sessionId && sessionIndex.get(sessionId) === sessionSandboxId)
|
|
70
|
+
sessionIndex.delete(sessionId)
|
|
71
|
+
})
|
|
72
|
+
connection.onClaimed((sessionId) => {
|
|
73
|
+
sessionIndex.set(sessionId, sessionSandboxId)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
async start() {
|
|
79
|
+
if (started)
|
|
80
|
+
return
|
|
81
|
+
started = true
|
|
82
|
+
try {
|
|
83
|
+
await input.notifications.start({
|
|
84
|
+
onConnectionDemand: wake,
|
|
85
|
+
onCommandsReady: sessionId => owned.get(sessionIndex.get(sessionId) ?? '')?.wakeCommands(),
|
|
86
|
+
onOwnerChanged: change => owned.get(change.sessionSandboxId)?.ownerChanged(change.generation),
|
|
87
|
+
})
|
|
88
|
+
const candidates = await input.authorityStore.listConnectionCandidates({ limit: timings.candidateLimit })
|
|
89
|
+
for (const candidate of candidates)
|
|
90
|
+
wake(candidate.sessionSandboxId)
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
started = false
|
|
94
|
+
await input.notifications.stop().catch(() => undefined)
|
|
95
|
+
throw error
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
async stop() {
|
|
99
|
+
if (stopping)
|
|
100
|
+
return
|
|
101
|
+
stopping = true
|
|
102
|
+
await input.notifications.stop()
|
|
103
|
+
const connections = [...owned.values()]
|
|
104
|
+
for (const connection of connections)
|
|
105
|
+
connection.stop()
|
|
106
|
+
await Promise.allSettled(connections.map(connection => connection.finished()))
|
|
107
|
+
owned.clear()
|
|
108
|
+
sessionIndex.clear()
|
|
109
|
+
},
|
|
110
|
+
wake,
|
|
111
|
+
wakeCommands(sessionId) {
|
|
112
|
+
owned.get(sessionIndex.get(sessionId) ?? '')?.wakeCommands()
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class OwnedConnection {
|
|
118
|
+
private readonly controller = new AbortController()
|
|
119
|
+
private readonly completion = Promise.withResolvers<void>()
|
|
120
|
+
private attempt: SessionSandboxConnectionAttempt | undefined
|
|
121
|
+
private claimSessionId: string | undefined
|
|
122
|
+
private claimGeneration: number | undefined
|
|
123
|
+
private claimed: ((sessionId: string) => void) | undefined
|
|
124
|
+
|
|
125
|
+
constructor(private readonly dependencies: Parameters<typeof createSandboxConnectionRuntime>[0] & {
|
|
126
|
+
sessionSandboxId: string
|
|
127
|
+
timings: SandboxConnectionRuntimeTimings
|
|
128
|
+
}) {}
|
|
129
|
+
|
|
130
|
+
onClaimed(callback: (sessionId: string) => void): void {
|
|
131
|
+
this.claimed = callback
|
|
132
|
+
if (this.claimSessionId)
|
|
133
|
+
callback(this.claimSessionId)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
sessionId(): string | undefined {
|
|
137
|
+
return this.claimSessionId
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async run(): Promise<void> {
|
|
141
|
+
try {
|
|
142
|
+
const claim = await this.dependencies.authorityStore.claimConnection({
|
|
143
|
+
sessionSandboxId: this.dependencies.sessionSandboxId,
|
|
144
|
+
replicaId: this.dependencies.replicaId,
|
|
145
|
+
leaseDurationMs: this.dependencies.timings.leaseDurationMs,
|
|
146
|
+
})
|
|
147
|
+
if (!claim)
|
|
148
|
+
return
|
|
149
|
+
this.claimSessionId = claim.sessionId
|
|
150
|
+
this.claimGeneration = claim.runtimeConnectionGeneration
|
|
151
|
+
this.claimed?.(claim.sessionId)
|
|
152
|
+
let establishmentDeadline = performance.now() + this.dependencies.timings.establishmentBudgetMs
|
|
153
|
+
let backoff = this.dependencies.timings.retryInitialDelayMs
|
|
154
|
+
while (!this.controller.signal.aborted) {
|
|
155
|
+
const attempt = new SessionSandboxConnectionAttempt({
|
|
156
|
+
claim,
|
|
157
|
+
establishmentDeadline,
|
|
158
|
+
replicaId: this.dependencies.replicaId,
|
|
159
|
+
trafficTokenEncryptionKey: this.dependencies.trafficTokenEncryptionKey,
|
|
160
|
+
signControlAuthority: this.dependencies.signControlAuthority,
|
|
161
|
+
openSupervisorConnection: this.dependencies.openSupervisorConnection,
|
|
162
|
+
authorityStore: this.dependencies.authorityStore,
|
|
163
|
+
commands: this.dependencies.commands,
|
|
164
|
+
durableEvents: this.dependencies.durableEvents,
|
|
165
|
+
ephemeralEvents: this.dependencies.ephemeralEvents,
|
|
166
|
+
rpc: this.dependencies.rpc,
|
|
167
|
+
bootstrap: this.dependencies.bootstrap,
|
|
168
|
+
failures: this.dependencies.failures,
|
|
169
|
+
timings: this.dependencies.timings,
|
|
170
|
+
logger: this.dependencies.logger,
|
|
171
|
+
parentSignal: this.controller.signal,
|
|
172
|
+
})
|
|
173
|
+
this.attempt = attempt
|
|
174
|
+
try {
|
|
175
|
+
await attempt.run()
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
attempt.stop(error)
|
|
179
|
+
await attempt.drain()
|
|
180
|
+
if (this.controller.signal.aborted)
|
|
181
|
+
return
|
|
182
|
+
if (error instanceof DeterministicEstablishmentError) {
|
|
183
|
+
if (!error.reported) {
|
|
184
|
+
await this.dependencies.failures.failDeterministicEstablishment({
|
|
185
|
+
sessionId: claim.sessionId,
|
|
186
|
+
sessionSandboxId: claim.sessionSandboxId,
|
|
187
|
+
providerSandboxId: claim.providerSandboxId,
|
|
188
|
+
generation: claim.runtimeConnectionGeneration,
|
|
189
|
+
code: error.code,
|
|
190
|
+
detail: error.detail,
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
if (error instanceof ConnectionAuthorityLostError)
|
|
196
|
+
return
|
|
197
|
+
if (error instanceof TokenDecryptionError) {
|
|
198
|
+
await this.dependencies.failures.markSessionFailed({
|
|
199
|
+
sessionId: claim.sessionId,
|
|
200
|
+
sessionSandboxId: claim.sessionSandboxId,
|
|
201
|
+
providerSandboxId: claim.providerSandboxId,
|
|
202
|
+
code: 'agent-core.traffic-token-decryption-failed',
|
|
203
|
+
})
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
if (error instanceof ConnectionClosedError && error.ready) {
|
|
207
|
+
establishmentDeadline = performance.now() + this.dependencies.timings.establishmentBudgetMs
|
|
208
|
+
backoff = this.dependencies.timings.retryInitialDelayMs
|
|
209
|
+
}
|
|
210
|
+
await this.dependencies.authorityStore.markConnectionReconnecting({
|
|
211
|
+
sessionSandboxId: claim.sessionSandboxId,
|
|
212
|
+
generation: claim.runtimeConnectionGeneration,
|
|
213
|
+
})
|
|
214
|
+
if (performance.now() + backoff >= establishmentDeadline)
|
|
215
|
+
break
|
|
216
|
+
await abortableDelay(backoff, this.controller.signal)
|
|
217
|
+
backoff = Math.min(backoff * 2, this.dependencies.timings.retryMaxDelayMs)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (!this.controller.signal.aborted) {
|
|
221
|
+
const exhausted = await this.dependencies.authorityStore.markConnectionRetryExhausted({
|
|
222
|
+
sessionSandboxId: claim.sessionSandboxId,
|
|
223
|
+
generation: claim.runtimeConnectionGeneration,
|
|
224
|
+
replicaId: this.dependencies.replicaId,
|
|
225
|
+
})
|
|
226
|
+
if (exhausted) {
|
|
227
|
+
await this.dependencies.failures.markEstablishmentExhausted({
|
|
228
|
+
sessionId: claim.sessionId,
|
|
229
|
+
sessionSandboxId: claim.sessionSandboxId,
|
|
230
|
+
providerSandboxId: claim.providerSandboxId,
|
|
231
|
+
generation: claim.runtimeConnectionGeneration,
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
this.dependencies.logger.error({ err: error, sessionSandboxId: this.dependencies.sessionSandboxId }, 'sandbox connection owner failed')
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
this.attempt?.stop()
|
|
241
|
+
if (this.claimGeneration !== undefined) {
|
|
242
|
+
await this.dependencies.authorityStore.releaseConnection({
|
|
243
|
+
sessionSandboxId: this.dependencies.sessionSandboxId,
|
|
244
|
+
generation: this.claimGeneration,
|
|
245
|
+
reason: this.controller.signal.aborted ? 'runtime_stopped' : 'connection_owner_finished',
|
|
246
|
+
}).catch(error => this.dependencies.logger.warn({ err: error }, 'sandbox connection release failed'))
|
|
247
|
+
}
|
|
248
|
+
this.completion.resolve()
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
wakeCommands(): void {
|
|
253
|
+
this.attempt?.wakeCommands()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
ownerChanged(generation: number): void {
|
|
257
|
+
if (!this.attempt || generation !== this.claimGeneration)
|
|
258
|
+
return
|
|
259
|
+
this.stop(new ConnectionAuthorityLostError())
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
stop(reason?: unknown): void {
|
|
263
|
+
if (!this.controller.signal.aborted)
|
|
264
|
+
this.controller.abort(reason ?? new DOMException('Sandbox connection runtime stopped.', 'AbortError'))
|
|
265
|
+
this.attempt?.stop(reason)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async finished(): Promise<void> {
|
|
269
|
+
await this.completion.promise
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function abortableDelay(milliseconds: number, signal: AbortSignal): Promise<void> {
|
|
274
|
+
if (signal.aborted)
|
|
275
|
+
throw signal.reason
|
|
276
|
+
await new Promise<void>((resolve, reject) => {
|
|
277
|
+
let timeout: ReturnType<typeof setTimeout>
|
|
278
|
+
const abort = () => {
|
|
279
|
+
clearTimeout(timeout)
|
|
280
|
+
signal.removeEventListener('abort', abort)
|
|
281
|
+
reject(signal.reason)
|
|
282
|
+
}
|
|
283
|
+
timeout = setTimeout(() => {
|
|
284
|
+
signal.removeEventListener('abort', abort)
|
|
285
|
+
resolve()
|
|
286
|
+
}, milliseconds)
|
|
287
|
+
signal.addEventListener('abort', abort, { once: true })
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function validatedTimings(timings: SandboxConnectionRuntimeTimings): SandboxConnectionRuntimeTimings {
|
|
292
|
+
for (const [name, value] of Object.entries(timings)) {
|
|
293
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
294
|
+
throw new RangeError(`${name} must be a positive safe integer.`)
|
|
295
|
+
}
|
|
296
|
+
if (timings.leaseRenewIntervalMs >= timings.leaseDurationMs)
|
|
297
|
+
throw new RangeError('leaseRenewIntervalMs must be lower than leaseDurationMs.')
|
|
298
|
+
if (timings.retryInitialDelayMs > timings.retryMaxDelayMs)
|
|
299
|
+
throw new RangeError('retryInitialDelayMs must not exceed retryMaxDelayMs.')
|
|
300
|
+
return timings
|
|
301
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
2
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto'
|
|
3
|
+
|
|
4
|
+
const algorithm = 'aes-256-gcm'
|
|
5
|
+
const ivBytes = 12
|
|
6
|
+
|
|
7
|
+
function encryptionKey(secret: string): Buffer {
|
|
8
|
+
if (!secret)
|
|
9
|
+
throw new TypeError('Token encryption key must not be empty.')
|
|
10
|
+
return createHash('sha256').update(secret, 'utf8').digest()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function encryptToken(input: { token: string, secret: string }): string {
|
|
14
|
+
const iv = randomBytes(ivBytes)
|
|
15
|
+
const cipher = createCipheriv(algorithm, encryptionKey(input.secret), iv)
|
|
16
|
+
const ciphertext = Buffer.concat([cipher.update(input.token, 'utf8'), cipher.final()])
|
|
17
|
+
return [iv, cipher.getAuthTag(), ciphertext].map(value => value.toString('base64url')).join('.')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function decryptToken(input: { encryptedToken: string, secret: string }): string {
|
|
21
|
+
try {
|
|
22
|
+
const parts = input.encryptedToken.split('.')
|
|
23
|
+
if (parts.length !== 3)
|
|
24
|
+
throw new Error('Encrypted token has an invalid shape.')
|
|
25
|
+
const [encodedIv, encodedTag, encodedCiphertext] = parts
|
|
26
|
+
if (!encodedIv || !encodedTag || !encodedCiphertext)
|
|
27
|
+
throw new Error('Encrypted token contains an empty segment.')
|
|
28
|
+
const decipher = createDecipheriv(algorithm, encryptionKey(input.secret), Buffer.from(encodedIv, 'base64url'))
|
|
29
|
+
decipher.setAuthTag(Buffer.from(encodedTag, 'base64url'))
|
|
30
|
+
return Buffer.concat([
|
|
31
|
+
decipher.update(Buffer.from(encodedCiphertext, 'base64url')),
|
|
32
|
+
decipher.final(),
|
|
33
|
+
]).toString('utf8')
|
|
34
|
+
}
|
|
35
|
+
catch (cause) {
|
|
36
|
+
throw new TokenDecryptionError({ cause })
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class TokenDecryptionError extends Error {
|
|
41
|
+
override readonly name = 'TokenDecryptionError'
|
|
42
|
+
|
|
43
|
+
constructor(options: { cause: unknown }) {
|
|
44
|
+
super('Stored sandbox traffic credential could not be decrypted.', options)
|
|
45
|
+
}
|
|
46
|
+
}
|