nebula-ai-core 0.1.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 +24 -0
- package/package.json +69 -0
- package/src/brain/compaction.ts +131 -0
- package/src/brain/frozen-prefix.ts +320 -0
- package/src/brain/history-persist.ts +154 -0
- package/src/brain/index.ts +43 -0
- package/src/brain/openai-brain.ts +533 -0
- package/src/brain/sanitize.ts +23 -0
- package/src/brain/stub.ts +20 -0
- package/src/brain/types.ts +129 -0
- package/src/chain.ts +75 -0
- package/src/claude-plugins/discovery.ts +152 -0
- package/src/claude-plugins/index.ts +6 -0
- package/src/claude-plugins/types.ts +38 -0
- package/src/commands/index.ts +16 -0
- package/src/commands/registry.ts +255 -0
- package/src/config.ts +213 -0
- package/src/economy/index.ts +6 -0
- package/src/events/index.ts +4 -0
- package/src/events/listeners.ts +37 -0
- package/src/events/queue.ts +63 -0
- package/src/events/router.ts +42 -0
- package/src/events/types.ts +28 -0
- package/src/format.ts +12 -0
- package/src/identity/agent-card.ts +110 -0
- package/src/identity/deployments.ts +20 -0
- package/src/identity/erc8004.ts +161 -0
- package/src/identity/index.ts +29 -0
- package/src/identity/keystore-blob.ts +60 -0
- package/src/identity/receipt.ts +27 -0
- package/src/identity/stub.ts +29 -0
- package/src/identity/types.ts +20 -0
- package/src/index.ts +372 -0
- package/src/locks.ts +233 -0
- package/src/mcp/discovery.ts +150 -0
- package/src/mcp/index.ts +10 -0
- package/src/mcp/manager.ts +110 -0
- package/src/mcp/stdio-client.ts +154 -0
- package/src/mcp/types.ts +44 -0
- package/src/memory/edit.ts +53 -0
- package/src/memory/encryption.ts +88 -0
- package/src/memory/fs-util.ts +15 -0
- package/src/memory/index-file.ts +74 -0
- package/src/memory/index-sync.ts +99 -0
- package/src/memory/index.ts +58 -0
- package/src/memory/list-tool.ts +105 -0
- package/src/memory/pack-blob.ts +120 -0
- package/src/memory/pack-gather.ts +112 -0
- package/src/memory/parser.ts +20 -0
- package/src/memory/read-tool.ts +198 -0
- package/src/memory/save-tool.ts +189 -0
- package/src/memory/scan.ts +63 -0
- package/src/memory/topic.ts +32 -0
- package/src/memory/types.ts +49 -0
- package/src/migration/index.ts +6 -0
- package/src/migration/option3-crypto.ts +127 -0
- package/src/operator/index.ts +9 -0
- package/src/operator/keychain.ts +53 -0
- package/src/operator/keystore-file.ts +33 -0
- package/src/operator/privkey-base.ts +60 -0
- package/src/operator/raw-privkey.ts +39 -0
- package/src/operator/signer.ts +46 -0
- package/src/operator/walletconnect.ts +454 -0
- package/src/pairing.ts +285 -0
- package/src/paths.ts +70 -0
- package/src/permission/dangerous.ts +108 -0
- package/src/permission/env-redact.ts +54 -0
- package/src/permission/index.ts +16 -0
- package/src/permission/path-guard.ts +114 -0
- package/src/permission/service.ts +191 -0
- package/src/plugins/context.ts +225 -0
- package/src/plugins/hooks.ts +81 -0
- package/src/plugins/index.ts +24 -0
- package/src/plugins/tool-search.ts +49 -0
- package/src/public/card.ts +67 -0
- package/src/runtime/activity.ts +29 -0
- package/src/runtime/index.ts +2 -0
- package/src/runtime/runtime.ts +113 -0
- package/src/sandbox/credentials.ts +25 -0
- package/src/sandbox/docker.ts +396 -0
- package/src/sandbox/factory.ts +99 -0
- package/src/sandbox/index.ts +15 -0
- package/src/sandbox/linux.ts +141 -0
- package/src/sandbox/local.ts +19 -0
- package/src/sandbox/macos.ts +71 -0
- package/src/sandbox/seatbelt-profile.ts +139 -0
- package/src/sandbox/types.ts +129 -0
- package/src/skills/index.ts +8 -0
- package/src/skills/scanner.ts +257 -0
- package/src/skills/triggers.ts +78 -0
- package/src/skills/types.ts +37 -0
- package/src/storage/encryption.ts +87 -0
- package/src/storage/factory.ts +31 -0
- package/src/storage/index.ts +11 -0
- package/src/storage/local-stub.ts +70 -0
- package/src/storage/sqlite.ts +95 -0
- package/src/storage/types.ts +21 -0
- package/src/tools/escalation.ts +200 -0
- package/src/tools/index.ts +11 -0
- package/src/tools/registry.ts +152 -0
- package/src/tools/types.ts +65 -0
- package/src/tools/zod-helpers.ts +36 -0
- package/src/tools/zod-schema.ts +99 -0
- package/src/wallet/drain.ts +79 -0
- package/src/wallet/eoa.ts +51 -0
- package/src/wallet/index.ts +47 -0
- package/src/wallet/keystore.ts +50 -0
- package/src/wallet/operator-keystore-crypto.ts +530 -0
- package/src/wallet/operator-session.ts +344 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator session: a per-agent on-disk cache of the operator-derived AES
|
|
3
|
+
* keys (one per scope) so the headless gateway daemon can boot without
|
|
4
|
+
* prompting Touch ID. Written once via `nebula gateway start` after an
|
|
5
|
+
* interactive Touch ID unlock; read by the daemon at boot.
|
|
6
|
+
*
|
|
7
|
+
* Security model:
|
|
8
|
+
* - File at `~/.nebula/agents/<id>/.operator-session` with permission 0600.
|
|
9
|
+
* - Same threat surface as hermes's `~/.hermes/.env` (which holds API keys
|
|
10
|
+
* in plaintext for daemon use). An attacker with read access to the user's
|
|
11
|
+
* home directory can extract these keys and decrypt the agent keystore +
|
|
12
|
+
* telegram secrets.
|
|
13
|
+
* - 24-hour default TTL. Caller can override via `expiresInMs`.
|
|
14
|
+
* - Atomic temp+rename writes.
|
|
15
|
+
* - The keys themselves are RFC-6979 deterministic — same operator privkey +
|
|
16
|
+
* same agent address always produce the same key.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { chmodSync, existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
20
|
+
import { join } from 'node:path'
|
|
21
|
+
import { type Address, type Hex, bytesToHex, hexToBytes } from 'viem'
|
|
22
|
+
import type { OperatorSigner } from '../operator/signer'
|
|
23
|
+
import { agentPaths } from '../paths'
|
|
24
|
+
import {
|
|
25
|
+
OPERATOR_BLOB_SCOPES,
|
|
26
|
+
type OperatorBlobScope,
|
|
27
|
+
deriveBlobKey,
|
|
28
|
+
deriveKeystoreKey,
|
|
29
|
+
deriveLegacyEmptyDomainKey,
|
|
30
|
+
} from './operator-keystore-crypto'
|
|
31
|
+
|
|
32
|
+
export const OPERATOR_SESSION_VERSION = 1 as const
|
|
33
|
+
export const DEFAULT_OPERATOR_SESSION_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours
|
|
34
|
+
|
|
35
|
+
/** Plain-object scope-keyed map; `keystore` is the canonical legacy slot. */
|
|
36
|
+
export type OperatorSessionKeys = Partial<Record<'keystore' | OperatorBlobScope, Hex>> & {
|
|
37
|
+
keystore: Hex
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface OperatorSession {
|
|
41
|
+
version: typeof OPERATOR_SESSION_VERSION
|
|
42
|
+
agent: Address
|
|
43
|
+
keys: OperatorSessionKeys
|
|
44
|
+
expiresAt: number
|
|
45
|
+
createdAt: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Path to the session file for a given agent id. */
|
|
49
|
+
export function operatorSessionPath(agentId: string): string {
|
|
50
|
+
return join(agentPaths.agent(agentId).dir, '.operator-session')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Atomically write the session file at perm 0600. Overwrites any existing
|
|
55
|
+
* session.
|
|
56
|
+
*/
|
|
57
|
+
export function writeOperatorSession(agentId: string, session: OperatorSession): void {
|
|
58
|
+
const path = operatorSessionPath(agentId)
|
|
59
|
+
const tmp = `${path}.tmp-${process.pid}-${Date.now().toString(36)}`
|
|
60
|
+
writeFileSync(tmp, JSON.stringify(session, null, 2), { mode: 0o600 })
|
|
61
|
+
renameSync(tmp, path)
|
|
62
|
+
// rename preserves source perms but be belt-and-suspenders explicit.
|
|
63
|
+
// Wrapped in try/catch for non-POSIX hosts (Windows) where chmod is advisory.
|
|
64
|
+
try {
|
|
65
|
+
chmodSync(path, 0o600)
|
|
66
|
+
} catch {
|
|
67
|
+
/* non-POSIX: permissions are advisory only */
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Read the session file. Returns null when the file is missing, malformed,
|
|
73
|
+
* or expired. Stale sessions are auto-deleted to keep the on-disk surface
|
|
74
|
+
* small.
|
|
75
|
+
*/
|
|
76
|
+
export function readOperatorSession(agentId: string): OperatorSession | null {
|
|
77
|
+
const path = operatorSessionPath(agentId)
|
|
78
|
+
let raw: string
|
|
79
|
+
try {
|
|
80
|
+
raw = readFileSync(path, 'utf8')
|
|
81
|
+
} catch {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(raw) as Partial<OperatorSession>
|
|
86
|
+
if (parsed.version !== OPERATOR_SESSION_VERSION) return null
|
|
87
|
+
if (typeof parsed.agent !== 'string' || !parsed.agent.startsWith('0x')) return null
|
|
88
|
+
if (typeof parsed.expiresAt !== 'number') return null
|
|
89
|
+
if (typeof parsed.createdAt !== 'number') return null
|
|
90
|
+
if (typeof parsed.keys !== 'object' || parsed.keys === null) return null
|
|
91
|
+
if (typeof parsed.keys.keystore !== 'string') return null
|
|
92
|
+
if (Date.now() > parsed.expiresAt) {
|
|
93
|
+
// Stale; clean up so we don't keep reading expired bytes.
|
|
94
|
+
try {
|
|
95
|
+
unlinkSync(path)
|
|
96
|
+
} catch {
|
|
97
|
+
/* race or perm issue; ignore */
|
|
98
|
+
}
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
return parsed as OperatorSession
|
|
102
|
+
} catch {
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Best-effort delete; race-tolerant. */
|
|
108
|
+
export function clearOperatorSession(agentId: string): void {
|
|
109
|
+
try {
|
|
110
|
+
unlinkSync(operatorSessionPath(agentId))
|
|
111
|
+
} catch {
|
|
112
|
+
/* ENOENT or perm; ignore */
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** True if a non-expired session exists on disk. */
|
|
117
|
+
export function isOperatorSessionFresh(agentId: string): boolean {
|
|
118
|
+
return readOperatorSession(agentId) !== null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Map encrypted blob filename → required scope. Extend when new blob types
|
|
123
|
+
* are added so `requiredScopesForAgent` picks them up automatically.
|
|
124
|
+
*/
|
|
125
|
+
const SCOPE_BLOB_FILES: ReadonlyArray<readonly [filename: string, scope: OperatorBlobScope]> = [
|
|
126
|
+
['telegram-secrets.encrypted', OPERATOR_BLOB_SCOPES.TELEGRAM],
|
|
127
|
+
] as const
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Map memory-file path → required scope. Used the same way as
|
|
131
|
+
* `SCOPE_BLOB_FILES` but matches on a path under `<agentDir>/memory/` instead
|
|
132
|
+
* of `<agentDir>/`. v0.23.0: the PROFILE slot is operator-keyed, so its scope
|
|
133
|
+
* is required whenever profile.md exists (which is always after init).
|
|
134
|
+
*/
|
|
135
|
+
const SCOPE_MEMORY_FILES: ReadonlyArray<readonly [path: string, scope: OperatorBlobScope]> = [
|
|
136
|
+
['memory/user/profile.md', OPERATOR_BLOB_SCOPES.PROFILE],
|
|
137
|
+
] as const
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Inspect the agent dir on disk and return the set of scopes the operator
|
|
141
|
+
* session must contain to fully boot the daemon. Always includes 'keystore'
|
|
142
|
+
* (the canonical legacy slot). Adds extra scopes when their corresponding
|
|
143
|
+
* encrypted blob is present.
|
|
144
|
+
*
|
|
145
|
+
* Used by `nebula gateway start` and TUI auto-spawn to decide whether the
|
|
146
|
+
* cached session is "complete enough" or whether re-derivation via Touch ID
|
|
147
|
+
* is needed.
|
|
148
|
+
*/
|
|
149
|
+
export function requiredScopesForAgent(agentId: string): Array<'keystore' | OperatorBlobScope> {
|
|
150
|
+
const dir = agentPaths.agent(agentId).dir
|
|
151
|
+
const required: Array<'keystore' | OperatorBlobScope> = ['keystore']
|
|
152
|
+
for (const [filename, scope] of SCOPE_BLOB_FILES) {
|
|
153
|
+
if (existsSync(join(dir, filename))) {
|
|
154
|
+
required.push(scope)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const [relPath, scope] of SCOPE_MEMORY_FILES) {
|
|
158
|
+
if (existsSync(join(dir, relPath))) {
|
|
159
|
+
required.push(scope)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return required
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Stricter sibling of `isOperatorSessionFresh`: true only when (a) a
|
|
167
|
+
* non-expired session exists AND (b) the session contains every scope key
|
|
168
|
+
* required by the agent's on-disk state. A session can be "fresh" by
|
|
169
|
+
* timestamp but missing a scope (e.g. written before telegram-secrets was
|
|
170
|
+
* configured), in which case this returns false so the caller knows to
|
|
171
|
+
* re-derive via Touch ID.
|
|
172
|
+
*
|
|
173
|
+
* The closing the gap on the v0.21.12 regression where `nebula gateway start`
|
|
174
|
+
* skipped re-derivation because the session was timestamp-fresh, but the
|
|
175
|
+
* gateway daemon then booted without the TELEGRAM scope key and silently
|
|
176
|
+
* dropped all inbound TG messages.
|
|
177
|
+
*/
|
|
178
|
+
export function isOperatorSessionComplete(
|
|
179
|
+
agentId: string,
|
|
180
|
+
required: Array<'keystore' | OperatorBlobScope>,
|
|
181
|
+
): boolean {
|
|
182
|
+
const sess = readOperatorSession(agentId)
|
|
183
|
+
if (!sess) return false
|
|
184
|
+
for (const scope of required) {
|
|
185
|
+
if (!sess.keys[scope]) return false
|
|
186
|
+
}
|
|
187
|
+
return true
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Pull a key from the session by scope. Returns null when no session exists
|
|
192
|
+
* or the scope is missing. Throws on disk corruption (length mismatch) so
|
|
193
|
+
* silent fallback to Touch ID prompts doesn't mask data integrity bugs.
|
|
194
|
+
*/
|
|
195
|
+
export function getSessionKey(
|
|
196
|
+
agentId: string,
|
|
197
|
+
which: 'keystore' | OperatorBlobScope,
|
|
198
|
+
): Buffer | null {
|
|
199
|
+
const sess = readOperatorSession(agentId)
|
|
200
|
+
if (!sess) return null
|
|
201
|
+
const hex = sess.keys[which]
|
|
202
|
+
if (!hex) return null
|
|
203
|
+
const buf = Buffer.from(hexToBytes(hex))
|
|
204
|
+
if (buf.length !== 32) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`operator-session: corrupt key for scope '${which}' (length ${buf.length}, expected 32)`,
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
return buf
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Optional verifier called by `precomputeAllScopes` after each scope's
|
|
214
|
+
* canonical key is derived. Returning false triggers the v0.24.10 legacy
|
|
215
|
+
* empty-EIP712Domain fallback for that scope (and propagates legacy
|
|
216
|
+
* derivation to any later scope keys, since the EIP712Domain trap is a
|
|
217
|
+
* signer-wide property, not a per-scope one).
|
|
218
|
+
*/
|
|
219
|
+
export type PrecomputeVerifyKey = (
|
|
220
|
+
scope: 'keystore' | OperatorBlobScope,
|
|
221
|
+
key: Buffer,
|
|
222
|
+
) => boolean | Promise<boolean>
|
|
223
|
+
|
|
224
|
+
export interface PrecomputeAllScopesOpts {
|
|
225
|
+
/**
|
|
226
|
+
* v0.24.10: Optional verifier. When unset, `precomputeAllScopes` behaves
|
|
227
|
+
* exactly as it did in v0.24.9 (parallel canonical-only derivation). When
|
|
228
|
+
* set, the verifier runs after each derive — failure swaps to the legacy
|
|
229
|
+
* variant via the signer's `signTypedDataLegacyEmptyDomain` escape hatch.
|
|
230
|
+
*
|
|
231
|
+
* The verifier is supplied by the caller because it owns the disk layout:
|
|
232
|
+
* gateway-start verifies against `keystore.json` + `telegram-secrets.encrypted`
|
|
233
|
+
* on disk; `init` doesn't pass a verifier because the keystore is being
|
|
234
|
+
* freshly encrypted under the just-derived canonical key.
|
|
235
|
+
*/
|
|
236
|
+
verifyKey?: PrecomputeVerifyKey
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Derive all requested scope keys from the operator signer in parallel.
|
|
241
|
+
* Each derive triggers `signer.signTypedData`. Many signer backends (keychain)
|
|
242
|
+
* serialize the underlying transport, so parallel is a free win for backends
|
|
243
|
+
* that don't (raw-privkey, in-memory) and a no-op for those that do.
|
|
244
|
+
*
|
|
245
|
+
* `extraScopes` — additional scopes to derive beyond the always-on
|
|
246
|
+
* `keystore`. Phase 12 telegram passes [OPERATOR_BLOB_SCOPES.TELEGRAM];
|
|
247
|
+
* v0.23.0 PROFILE adds [OPERATOR_BLOB_SCOPES.PROFILE] when the agent's
|
|
248
|
+
* user-partition memory exists.
|
|
249
|
+
*
|
|
250
|
+
* v0.24.10: when `opts.verifyKey` is supplied, the keystore canonical key is
|
|
251
|
+
* trial-decrypted against the on-disk blob; if the verifier rejects it, the
|
|
252
|
+
* signer's `signTypedDataLegacyEmptyDomain` escape hatch is invoked to derive
|
|
253
|
+
* the pre-v0.24.9 WC variant. The detection cascades to remaining scopes —
|
|
254
|
+
* once the signer is known to be in legacy mode, every scope key is derived
|
|
255
|
+
* via the legacy method so the daemon boots with keys that actually decrypt
|
|
256
|
+
* the on-disk artifacts (single MM popup per scope on the first launch
|
|
257
|
+
* post-v0.24.9 for legacy WC agents; canonical-success agents see zero
|
|
258
|
+
* behavior change).
|
|
259
|
+
*/
|
|
260
|
+
export async function precomputeAllScopes(
|
|
261
|
+
signer: OperatorSigner,
|
|
262
|
+
agent: Address,
|
|
263
|
+
extraScopes: OperatorBlobScope[] = [],
|
|
264
|
+
opts: PrecomputeAllScopesOpts = {},
|
|
265
|
+
): Promise<OperatorSessionKeys> {
|
|
266
|
+
if (!opts.verifyKey) {
|
|
267
|
+
const [keystore, ...extras] = await Promise.all([
|
|
268
|
+
deriveKeystoreKey(signer, agent),
|
|
269
|
+
...extraScopes.map(scope => deriveBlobKey(signer, agent, scope)),
|
|
270
|
+
])
|
|
271
|
+
const result: OperatorSessionKeys = { keystore: bytesToHex(keystore) }
|
|
272
|
+
extraScopes.forEach((scope, i) => {
|
|
273
|
+
const buf = extras[i]
|
|
274
|
+
if (buf) result[scope] = bytesToHex(buf)
|
|
275
|
+
})
|
|
276
|
+
return result
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Verify-and-swap path. Serialize keystore first so the legacy detection
|
|
280
|
+
// cascades to remaining scopes uniformly.
|
|
281
|
+
const verifyKey = opts.verifyKey
|
|
282
|
+
let keystoreKey = await deriveKeystoreKey(signer, agent)
|
|
283
|
+
let useLegacyForRest = false
|
|
284
|
+
if (!(await verifyKey('keystore', keystoreKey))) {
|
|
285
|
+
const legacyKey = await deriveLegacyEmptyDomainKey(signer, agent, 'keystore')
|
|
286
|
+
if (!legacyKey) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
'precomputeAllScopes: keystore decrypt verification failed with canonical key and signer does not expose a legacy variant. Verify the operator wallet matches the agent keystore.',
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
if (!(await verifyKey('keystore', legacyKey))) {
|
|
292
|
+
throw new Error(
|
|
293
|
+
'precomputeAllScopes: keystore decrypt verification failed with both canonical and legacy variants. The operator wallet may not match the agent keystore.',
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
keystoreKey = legacyKey
|
|
297
|
+
useLegacyForRest = true
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const extras = await Promise.all(
|
|
301
|
+
extraScopes.map(async (scope): Promise<{ scope: OperatorBlobScope; key: Buffer | null }> => {
|
|
302
|
+
let key: Buffer | null = useLegacyForRest
|
|
303
|
+
? await deriveLegacyEmptyDomainKey(signer, agent, scope)
|
|
304
|
+
: await deriveBlobKey(signer, agent, scope)
|
|
305
|
+
if (key && !(await verifyKey(scope, key))) {
|
|
306
|
+
const altKey: Buffer | null = useLegacyForRest
|
|
307
|
+
? await deriveBlobKey(signer, agent, scope)
|
|
308
|
+
: await deriveLegacyEmptyDomainKey(signer, agent, scope)
|
|
309
|
+
if (altKey && (await verifyKey(scope, altKey))) {
|
|
310
|
+
key = altKey
|
|
311
|
+
} else {
|
|
312
|
+
key = null
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return { scope, key }
|
|
316
|
+
}),
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
const result: OperatorSessionKeys = { keystore: bytesToHex(keystoreKey) }
|
|
320
|
+
for (const { scope, key } of extras) {
|
|
321
|
+
if (key) result[scope] = bytesToHex(key)
|
|
322
|
+
}
|
|
323
|
+
return result
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Build an OperatorSession from a keys object plus an optional TTL override.
|
|
328
|
+
* Convenience composer used by `nebula gateway start`.
|
|
329
|
+
*/
|
|
330
|
+
export function buildOperatorSession(opts: {
|
|
331
|
+
agent: Address
|
|
332
|
+
keys: OperatorSessionKeys
|
|
333
|
+
expiresInMs?: number
|
|
334
|
+
}): OperatorSession {
|
|
335
|
+
const now = Date.now()
|
|
336
|
+
const ttl = opts.expiresInMs ?? DEFAULT_OPERATOR_SESSION_TTL_MS
|
|
337
|
+
return {
|
|
338
|
+
version: OPERATOR_SESSION_VERSION,
|
|
339
|
+
agent: opts.agent,
|
|
340
|
+
keys: opts.keys,
|
|
341
|
+
expiresAt: now + ttl,
|
|
342
|
+
createdAt: now,
|
|
343
|
+
}
|
|
344
|
+
}
|