rovloom 0.6.0 → 0.6.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.
Files changed (62) hide show
  1. package/README.md +2 -2
  2. package/dist/src/admin/index.js +1 -1
  3. package/dist/src/{chunk-g27b2gmx.js → chunk-0e9gg4sg.js} +2 -2
  4. package/dist/src/chunk-1xer1ent.js +102 -0
  5. package/dist/src/{chunk-863tk45b.js → chunk-41fny9qm.js} +5 -5
  6. package/dist/src/{chunk-zjzrcc59.js → chunk-59h200cr.js} +139 -7
  7. package/dist/src/chunk-5w5srj3b.js +93 -0
  8. package/dist/src/{chunk-d4ejrtjq.js → chunk-6n6b1xpy.js} +1 -1
  9. package/dist/src/{chunk-ez7d5dac.js → chunk-6w4g9fw8.js} +4 -1
  10. package/dist/src/{chunk-0dqx2mmz.js → chunk-73aq2qkz.js} +1 -1
  11. package/dist/src/{chunk-n6wtjyfb.js → chunk-8c9yc0ge.js} +5 -5
  12. package/dist/src/{chunk-57ayxqnb.js → chunk-8fy8w7ds.js} +2 -23
  13. package/dist/src/{chunk-2tn0r64b.js → chunk-9h9x79bx.js} +5 -5
  14. package/dist/src/{chunk-qjh9x872.js → chunk-av31bx1e.js} +1 -1
  15. package/dist/src/{chunk-0xgpxdgc.js → chunk-bbdtsg4b.js} +7 -5
  16. package/dist/src/{chunk-q5pgbwb3.js → chunk-e7jgpxkf.js} +2 -2
  17. package/dist/src/{chunk-zj48jvfe.js → chunk-efty77sb.js} +3 -40
  18. package/dist/src/{chunk-gyvqqnjb.js → chunk-enj7s8cv.js} +49 -29
  19. package/dist/src/{chunk-jka7v3wb.js → chunk-ftpy8xx3.js} +311 -23
  20. package/dist/src/{chunk-zdxww7ms.js → chunk-h3xyq7zk.js} +1 -1
  21. package/dist/src/{chunk-ttsrctxg.js → chunk-hxmq28pg.js} +9 -7
  22. package/dist/src/{chunk-s3ydr65k.js → chunk-j59ac0xs.js} +2 -2
  23. package/dist/src/{chunk-5s2emvww.js → chunk-j6svdqr8.js} +10 -302
  24. package/dist/src/{chunk-9zkq0kc8.js → chunk-jkzfck98.js} +3 -3
  25. package/dist/src/{chunk-ssp57p43.js → chunk-m76cmkfy.js} +7 -5
  26. package/dist/src/{chunk-d2kz25f2.js → chunk-m96mbzcw.js} +674 -308
  27. package/dist/src/{chunk-yv9px0f5.js → chunk-n3z3jaxy.js} +20 -14
  28. package/dist/src/{chunk-jf4r0r44.js → chunk-ndj0ay2p.js} +19 -18
  29. package/dist/src/{chunk-5b8g62kw.js → chunk-pgs9jp6t.js} +61 -179
  30. package/dist/src/chunk-pksaqt9a.js +25 -0
  31. package/dist/src/{chunk-wxqhscsw.js → chunk-pnep7yhj.js} +7 -99
  32. package/dist/src/chunk-qn5xae7g.js +66 -0
  33. package/dist/src/{chunk-0pewfj89.js → chunk-qp8tgms2.js} +1 -1
  34. package/dist/src/{chunk-mg0fd6ph.js → chunk-rmgw8h27.js} +10 -8
  35. package/dist/src/{chunk-gbsbwssz.js → chunk-sf4ztxba.js} +3 -3
  36. package/dist/src/{chunk-x0td5wn4.js → chunk-shez7hkc.js} +1 -1
  37. package/dist/src/chunk-sqz614bh.js +30 -0
  38. package/dist/src/{chunk-09f9q26d.js → chunk-vdwzb63d.js} +2 -2
  39. package/dist/src/chunk-xtvgm6kf.js +48 -0
  40. package/dist/src/chunk-y8zm75jt.js +157 -0
  41. package/dist/src/chunk-yasqw2vj.js +756 -0
  42. package/dist/src/{chunk-srfbjwtk.js → chunk-z0qmfqvd.js} +3 -3
  43. package/dist/src/chunk-zm705snh.js +53 -0
  44. package/dist/src/cli/index.js +29 -28
  45. package/dist/src/cli/main.js +5 -5
  46. package/dist/src/connector/herdr/installation.js +4 -4
  47. package/dist/src/connector/herdr/provider.js +9 -9
  48. package/dist/src/connector/herdr/runtime.js +7 -7
  49. package/dist/src/connector/luvus/index.js +7 -6
  50. package/dist/src/mcp/index.js +16 -15
  51. package/dist/src/runtime/index.js +16 -15
  52. package/dist/src/service/index.js +2 -2
  53. package/dist/src/store.js +4 -4
  54. package/dist/src/transport/index.js +9 -9
  55. package/dist/src/webui/index.js +141 -939
  56. package/package.json +1 -1
  57. package/dist/src/chunk-0qgk7mhz.js +0 -52
  58. package/dist/src/chunk-3dvn33c4.js +0 -1299
  59. package/dist/src/chunk-5j0gj6w2.js +0 -7
  60. package/dist/src/chunk-vwcntax3.js +0 -228
  61. package/dist/src/chunk-ytpfmcp1.js +0 -25
  62. package/dist/src/chunk-zjkjjm81.js +0 -1114
@@ -1,1114 +0,0 @@
1
- // @bun
2
- import {
3
- parseNativeJson2
4
- } from "./chunk-7crhmqn2.js";
5
- import {
6
- object2,
7
- fields2,
8
- id2,
9
- sha2,
10
- boundedJson2,
11
- hash2
12
- } from "./chunk-qt9m2kg2.js";
13
- import {
14
- HERDR_WORKFLOW_METHODS
15
- } from "./chunk-9tt13dm0.js";
16
- import {
17
- LUVUS_OPERATION_METHODS2
18
- } from "./chunk-zj48jvfe.js";
19
- import {
20
- compileHerdrSchemas2
21
- } from "./chunk-5h82060k.js";
22
- import {
23
- createHerdrCodec2
24
- } from "./chunk-zb641qa3.js";
25
- import {
26
- parseHerdrProviderRefConfig2,
27
- createHerdrGatewayResources2
28
- } from "./chunk-2tn0r64b.js";
29
- import {
30
- checkedRemoteLuvusConfig,
31
- createPinnedRemoteLuvus
32
- } from "./chunk-5s2emvww.js";
33
- import {
34
- cliMessages2,
35
- formatMessage2
36
- } from "./chunk-5b8g62kw.js";
37
- import {
38
- writeAdminFiles2,
39
- managementSourceExists2
40
- } from "./chunk-yv9px0f5.js";
41
- import {
42
- setupFileStamp,
43
- writeSetupFile,
44
- readSetupJson,
45
- writeSetupJson,
46
- REGISTRY_MAX_BYTES,
47
- parseRegistry,
48
- isAdminPlaceholder
49
- } from "./chunk-wxqhscsw.js";
50
- import {
51
- readRuntimeConfig2
52
- } from "./chunk-bspzg8er.js";
53
- import {
54
- runtimeReference,
55
- readRuntimeFile,
56
- readRuntimeJson
57
- } from "./chunk-z7kkfrgz.js";
58
- import {
59
- luvusMode,
60
- CliError2
61
- } from "./chunk-hpzr94hm.js";
62
- import {
63
- canonicalLoopbackHost
64
- } from "./chunk-6n9s7nfe.js";
65
- import {
66
- PairingAdmin2,
67
- createPairingDelivery2
68
- } from "./chunk-vwcntax3.js";
69
-
70
- // src/cli/pairing-http.ts
71
- import { createHash, timingSafeEqual } from "crypto";
72
-
73
- // src/cli/setup-policy.ts
74
- function setupSessions(input) {
75
- if (input === undefined) {
76
- return Object.freeze({});
77
- }
78
- const row = input;
79
- if (row === null || typeof row !== "object" || Array.isArray(row) || Object.keys(row).some((key) => key !== "luvus") || row.luvus !== undefined && row.luvus !== "default" && row.luvus !== "rovloom") {
80
- throw new CliError2("configuration_required");
81
- }
82
- return Object.freeze(row.luvus === undefined ? {} : { luvus: row.luvus });
83
- }
84
- function setupTarget(machine, kind = "luvus", sessions = {}) {
85
- return {
86
- machine_id: machine,
87
- provider_id: kind,
88
- profile: "session",
89
- session: kind === "luvus" ? sessions.luvus ?? "rovloom" : "default"
90
- };
91
- }
92
- function setupProviderKinds(input) {
93
- if (!Array.isArray(input) || input.some((kind) => kind !== "luvus" && kind !== "herdr") || !["luvus", "herdr", "luvus,herdr"].includes(input.join(","))) {
94
- throw new CliError2("configuration_required");
95
- }
96
- return Object.freeze([...input]);
97
- }
98
- function selectedSetupPolicies(plan, machine, kinds, sessions = {}) {
99
- const grants = setupProviderKinds(kinds).map((kind) => ({
100
- target: setupTarget(machine, kind, sessions),
101
- methods: [...kind === "luvus" ? LUVUS_OPERATION_METHODS2 : HERDR_WORKFLOW_METHODS],
102
- actions: ["dispatch", "read", "cancel"]
103
- }));
104
- return {
105
- principal_id: plan.actor,
106
- policies: {
107
- caller: { revision: "setup_caller_" + (plan.lineage ?? plan.attempt_id), grants },
108
- local: { revision: "setup_local_" + (plan.lineage ?? plan.attempt_id), grants: structuredClone(grants) }
109
- }
110
- };
111
- }
112
- function setupReference(attempt, kind = "luvus") {
113
- return kind + "_" + attempt;
114
- }
115
- function setupSessionsFor(config) {
116
- return Object.freeze({ luvus: luvusMode(config) === "attach" ? "default" : "rovloom" });
117
- }
118
-
119
- // src/cli/pairing-http.ts
120
- function setupReply(value, status = 200) {
121
- return new Response(JSON.stringify(value), {
122
- status,
123
- headers: {
124
- "content-type": "application/json",
125
- "cache-control": "no-store",
126
- "x-content-type-options": "nosniff"
127
- }
128
- });
129
- }
130
- async function requestJson(request, maximum, stopped) {
131
- const signal = AbortSignal.any([request.signal, stopped, AbortSignal.timeout(5000)]);
132
- const reader = request.body?.getReader();
133
- if (!reader) {
134
- throw new Error("setup_request_invalid");
135
- }
136
- const chunks = [];
137
- let bytes = 0;
138
- try {
139
- while (true) {
140
- let abort;
141
- const cancelled = new Promise((_resolve, reject) => {
142
- abort = () => reject(new Error("setup_request_closed"));
143
- signal.addEventListener("abort", abort, { once: true });
144
- });
145
- let result;
146
- try {
147
- if (signal.aborted) {
148
- throw new Error("setup_request_closed");
149
- }
150
- result = await Promise.race([reader.read(), cancelled]);
151
- } finally {
152
- signal.removeEventListener("abort", abort);
153
- }
154
- if (result.done) {
155
- break;
156
- }
157
- bytes += result.value.length;
158
- if (bytes > maximum || signal.aborted) {
159
- throw new Error("setup_request_invalid");
160
- }
161
- chunks.push(result.value);
162
- }
163
- const parsed = parseNativeJson2(Buffer.concat(chunks, bytes), { object_bytes: maximum });
164
- if (!parsed.ok) {
165
- throw new Error("setup_request_invalid");
166
- }
167
- return object2(parsed.value);
168
- } finally {
169
- reader.cancel().catch(() => {});
170
- reader.releaseLock();
171
- }
172
- }
173
- function device(value) {
174
- const input = object2(value);
175
- fields2(input, ["machine_id", "recovery_generation", "credential"]);
176
- id2(input.machine_id);
177
- id2(input.recovery_generation);
178
- if (typeof input.credential !== "string" || !/^[A-Za-z0-9_-]{32,256}$/.test(input.credential)) {
179
- throw new Error("setup_request_invalid");
180
- }
181
- return input;
182
- }
183
-
184
- class GatewayPairingHttp {
185
- state;
186
- approval;
187
- native;
188
- repaired;
189
- #exchanges = new Map;
190
- #requests = new Set;
191
- #stop = new AbortController;
192
- #closed = false;
193
- constructor(state, approval, native, repaired = () => {}) {
194
- this.state = state;
195
- this.approval = approval;
196
- this.native = native;
197
- this.repaired = repaired;
198
- }
199
- async authenticated(credential, run) {
200
- const host = this.state.lifecycle.identityHost;
201
- const active = host.authenticate(credential.machine_id, credential.recovery_generation, credential.credential);
202
- try {
203
- host.assert(active.identity, active.lease);
204
- const value = await run();
205
- host.assert(active.identity, active.lease);
206
- return value;
207
- } finally {
208
- host.disconnect(active.identity);
209
- }
210
- }
211
- handle(request) {
212
- const url = new URL(request.url);
213
- if (!["/pairing/redeem", "/pairing/ack", "/pairing/status", "/pairing/native"].includes(url.pathname)) {
214
- return;
215
- }
216
- if (this.#closed || request.method !== "POST" || request.headers.has("origin") || url.search || url.hash || url.username || url.password || request.headers.get("content-type")?.split(";")[0]?.trim() !== "application/json") {
217
- return Promise.resolve(setupReply({ error: "setup_request_refused" }, 403));
218
- }
219
- if (this.#requests.size >= 16) {
220
- return Promise.resolve(setupReply({ error: "setup_busy" }, 503));
221
- }
222
- const task = this.respond(request, url.pathname);
223
- this.#requests.add(task);
224
- task.then(() => this.#requests.delete(task), () => this.#requests.delete(task));
225
- return task;
226
- }
227
- async respond(request, path) {
228
- try {
229
- const input = await requestJson(request, path === "/pairing/native" ? 1048576 : 16384, this.#stop.signal);
230
- if (this.#closed) {
231
- throw new Error("setup_closed");
232
- }
233
- if (path === "/pairing/redeem") {
234
- return await this.redeem(input);
235
- }
236
- const credential = device(input.authentication);
237
- id2(input.attempt_id);
238
- if (this.approval.read(input.attempt_id).phase === "ready") {
239
- throw new Error("setup_closed");
240
- }
241
- return await this.authenticated(credential, async () => {
242
- const plan = this.approval.read(input.attempt_id);
243
- if (plan.machine_id !== credential.machine_id || plan.generation !== credential.recovery_generation) {
244
- throw new Error("setup_target_mismatch");
245
- }
246
- if (path === "/pairing/ack") {
247
- fields2(input, ["attempt_id", "authentication", "acknowledgement"]);
248
- return await this.acknowledge(input.attempt_id, credential, input.acknowledgement);
249
- }
250
- if (path === "/pairing/status") {
251
- fields2(input, ["attempt_id", "authentication"], ["providers", "sessions"]);
252
- const providers = setupProviderKinds(input.providers ?? ["luvus"]);
253
- const sessions = setupSessions(input.sessions);
254
- if (plan.providers && boundedJson2(plan.providers) !== boundedJson2(providers)) {
255
- throw new Error("setup_provider_selection_changed");
256
- }
257
- return setupReply({
258
- attempt_id: plan.attempt_id,
259
- machine_id: plan.machine_id,
260
- recovery_generation: plan.generation,
261
- phase: plan.phase,
262
- ...plan.lineage ? { lineage: plan.lineage } : {},
263
- authorization: selectedSetupPolicies(plan, credential.machine_id, providers, sessions)
264
- });
265
- }
266
- fields2(input, ["attempt_id", "authentication", "installation"]);
267
- if (!["paired", "native_approved", "configured"].includes(plan.phase)) {
268
- throw new Error("setup_not_paired");
269
- }
270
- return setupReply(await this.native({
271
- plan,
272
- credential,
273
- input: input.installation,
274
- signal: AbortSignal.any([request.signal, this.#stop.signal])
275
- }));
276
- });
277
- } catch {
278
- return setupReply({ error: "setup_not_completed" }, 409);
279
- }
280
- }
281
- async redeem(input) {
282
- fields2(input, ["attempt_id", "machine_id", "enrollment"]);
283
- id2(input.attempt_id);
284
- id2(input.machine_id);
285
- if (this.#exchanges.size >= 4 || this.#exchanges.has(input.attempt_id)) {
286
- throw new Error("setup_exchange_unavailable");
287
- }
288
- const plan = this.approval.claim(input.attempt_id, input.machine_id, input.enrollment);
289
- let deliver;
290
- let refuse;
291
- const response = new Promise((resolve, reject) => {
292
- deliver = resolve;
293
- refuse = reject;
294
- });
295
- let acknowledge;
296
- let rejectAck;
297
- const ack = new Promise((resolve, reject) => {
298
- acknowledge = resolve;
299
- rejectAck = reject;
300
- });
301
- ack.catch(() => {});
302
- const custody = createPairingDelivery2({
303
- backend: this.state.store,
304
- identities: this.state.credentials,
305
- authorizeChannel: (context) => {
306
- if (this.#closed || context.operation !== "enroll" || context.machine_id !== plan.machine_id) {
307
- throw new Error("setup_channel_refused");
308
- }
309
- return {
310
- deliver: async (credential, signal, delivery) => {
311
- if (this.#closed || signal.aborted || credential.machine_id !== plan.machine_id || credential.recovery_generation !== plan.generation) {
312
- throw new Error("setup_channel_refused");
313
- }
314
- exchange.context = delivery;
315
- exchange.credential_sha256 = createHash("sha256").update(credential.credential).digest();
316
- exchange.acknowledgement = acknowledge;
317
- deliver(setupReply({
318
- attempt_id: plan.attempt_id,
319
- ...delivery,
320
- ...credential,
321
- state: "persist_and_acknowledge",
322
- expires_at: plan.expires_at
323
- }));
324
- return await ack;
325
- },
326
- close: () => {
327
- rejectAck(new Error("setup_delivery_closed"));
328
- }
329
- };
330
- }
331
- });
332
- const task = custody.redeem({ registration_id: plan.machine_id, enrollment: input.enrollment }).then((receipt) => {
333
- this.approval.finishDelivery(plan.attempt_id, true);
334
- return receipt;
335
- }, (error) => {
336
- try {
337
- this.approval.finishDelivery(plan.attempt_id, false);
338
- } catch {}
339
- refuse(new Error("setup_delivery_unknown"));
340
- throw error;
341
- }).finally(() => {
342
- custody.close();
343
- exchange.credential_sha256?.fill(0);
344
- exchange.credential_sha256 = undefined;
345
- exchange.acknowledgement = undefined;
346
- });
347
- const exchange = {
348
- plan,
349
- task,
350
- close: () => custody.close()
351
- };
352
- this.#exchanges.set(plan.attempt_id, exchange);
353
- task.catch(() => {});
354
- return await response;
355
- }
356
- async acknowledge(attempt, credential, value) {
357
- const ack = object2(value);
358
- fields2(ack, ["delivery_id", "revision", "machine_id", "recovery_generation"]);
359
- const exchange = this.#exchanges.get(attempt);
360
- if (!exchange?.context || !exchange.acknowledgement || !exchange.credential_sha256 || ack.delivery_id !== exchange.context.delivery_id || ack.revision !== exchange.context.revision || ack.machine_id !== credential.machine_id || ack.recovery_generation !== credential.recovery_generation || !timingSafeEqual(createHash("sha256").update(credential.credential).digest(), exchange.credential_sha256)) {
361
- throw new Error("setup_ack_refused");
362
- }
363
- exchange.acknowledgement(ack);
364
- const receipt = await exchange.task;
365
- if (this.approval.read(attempt).repair) {
366
- this.repaired();
367
- }
368
- return setupReply({ ...receipt, attempt_id: attempt });
369
- }
370
- async close() {
371
- this.#closed = true;
372
- this.#stop.abort();
373
- for (const exchange of this.#exchanges.values()) {
374
- exchange.close();
375
- }
376
- await Promise.allSettled([...this.#exchanges.values()].map((value) => value.task));
377
- await Promise.allSettled(this.#requests);
378
- this.#exchanges.clear();
379
- }
380
- }
381
-
382
- // src/cli/setup-manifest.ts
383
- function label(value) {
384
- if (value === null) {
385
- return "unavailable";
386
- }
387
- if (!value.length || value.length > 128 || [...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127)) {
388
- throw new CliError2("configuration_required");
389
- }
390
- return value;
391
- }
392
- async function reviewSetupManifest(input, plan, machine, directory, signal) {
393
- const row = object2(input);
394
- let entries;
395
- if (Object.hasOwn(row, "installations")) {
396
- fields2(row, ["installations"]);
397
- if (!Array.isArray(row.installations)) {
398
- throw new CliError2("configuration_required");
399
- }
400
- entries = row.installations;
401
- } else {
402
- fields2(row, ["reference", "value"]);
403
- entries = [row];
404
- }
405
- const kinds = setupProviderKinds(entries.map((entry) => object2(object2(entry).value).kind));
406
- if (plan.providers && boundedJson2(plan.providers) !== boundedJson2(kinds)) {
407
- throw new CliError2("configuration_required");
408
- }
409
- const luvusIndex = kinds.indexOf("luvus");
410
- const sessions = setupSessions(luvusIndex < 0 ? undefined : { luvus: object2(object2(object2(entries[luvusIndex]).value).target).session });
411
- const authorization = selectedSetupPolicies(plan, machine, kinds, sessions);
412
- const reviewed = [];
413
- for (let index = 0;index < entries.length; index++) {
414
- const kind = kinds[index];
415
- const entry = object2(entries[index]);
416
- fields2(entry, ["reference", "value"]);
417
- const reference = setupReference(plan.attempt_id, kind);
418
- const target = setupTarget(machine, kind, sessions);
419
- if (entry.reference !== reference) {
420
- throw new CliError2("configuration_required");
421
- }
422
- if (kind === "luvus") {
423
- const remote = checkedRemoteLuvusConfig(reference, entry.value);
424
- const expectedGrant = authorization.policies.caller.grants.find((grant) => boundedJson2(grant.target) === boundedJson2(target));
425
- if (boundedJson2(remote.target) !== boundedJson2(target) || remote.binding.binding_revision !== 1 || !remote.binding.enabled || remote.peer_policy_revision !== authorization.policies.local.revision || boundedJson2(remote.raw_grant) !== boundedJson2({ principal_id: authorization.principal_id, methods: expectedGrant.methods })) {
426
- throw new CliError2("configuration_required");
427
- }
428
- const verified = await createPinnedRemoteLuvus(remote);
429
- verified.close();
430
- reviewed.push({
431
- kind,
432
- reference,
433
- target,
434
- binding: remote.binding,
435
- value: remote,
436
- label: label(remote.contract.binary_version),
437
- binary_sha256: remote.contract.binary_sha256
438
- });
439
- } else {
440
- const remote = object2(entry.value);
441
- fields2(remote, ["kind", "config", "schema_bundle"]);
442
- const config = object2(remote.config);
443
- fields2(config, ["version", "target", "binding_revision", "config_ref", "expected_source", "peer_policy_revision"]);
444
- if (remote.kind !== "herdr" || typeof remote.schema_bundle !== "string" || Buffer.byteLength(remote.schema_bundle) > 524288) {
445
- throw new CliError2("configuration_required");
446
- }
447
- const path = runtimeReference(directory, "herdr-schema_" + plan.attempt_id);
448
- const parsed = parseHerdrProviderRefConfig2({ ...config, schema_path: path });
449
- const bytes = Buffer.from(remote.schema_bundle, "utf8");
450
- if (parsed.config_ref !== reference || parsed.binding_revision !== 1 || boundedJson2(parsed.target) !== boundedJson2(target) || parsed.peer_policy_revision !== authorization.policies.local.revision || hash2(bytes) !== parsed.expected_source.schema_sha256) {
451
- throw new CliError2("configuration_required");
452
- }
453
- const compiled = await compileHerdrSchemas2(bytes, signal);
454
- createHerdrCodec2(parsed.expected_source, compiled);
455
- const binding = {
456
- machine_id: machine,
457
- provider_id: kind,
458
- kind,
459
- config_ref: reference,
460
- binding_revision: 1,
461
- enabled: true
462
- };
463
- reviewed.push({
464
- kind,
465
- reference,
466
- target,
467
- binding,
468
- value: { kind, config: parsed },
469
- label: label(parsed.expected_source.binary_version),
470
- binary_sha256: parsed.expected_source.binary_sha256,
471
- schema: { path, bytes }
472
- });
473
- }
474
- }
475
- return { kinds, reviewed, authorization, fingerprint: hash2(boundedJson2(input, 1048576)) };
476
- }
477
- async function publishSetupSchemas(entries, signal) {
478
- for (const entry of entries) {
479
- if (!entry.schema) {
480
- continue;
481
- }
482
- const { path, bytes } = entry.schema;
483
- let previous;
484
- try {
485
- previous = readRuntimeFile(path, 524288);
486
- } catch (error) {
487
- if (!(error instanceof CliError2 && error.code === "backend_unavailable")) {
488
- throw error;
489
- }
490
- }
491
- if (previous === undefined) {
492
- writeSetupFile(path, bytes);
493
- } else if (!previous.equals(bytes)) {
494
- throw new CliError2("configuration_required");
495
- }
496
- const value = entry.value;
497
- const verified = await createHerdrGatewayResources2({ config: value.config, signal });
498
- await verified.release();
499
- }
500
- }
501
-
502
- // src/cli/setup-gateway.ts
503
- import { createHash as createHash3, randomBytes } from "crypto";
504
-
505
- // src/cli/setup-approval.ts
506
- import { createHash as createHash2, randomUUID, timingSafeEqual as timingSafeEqual2 } from "crypto";
507
- var phases = new Set([
508
- "registering",
509
- "reserved",
510
- "issued",
511
- "redeeming",
512
- "paired",
513
- "native_approved",
514
- "configured",
515
- "ready",
516
- "unknown"
517
- ]);
518
- function plan(value) {
519
- try {
520
- const row = object2(value);
521
- fields2(row, ["schema_version", "attempt_id", "actor", "uid", "generation", "name", "created_at", "expires_at", "phase"], ["machine_id", "enrollment_sha256", "native_fingerprint", "providers", "repair", "lineage"]);
522
- for (const key of ["attempt_id", "actor", "generation", "name", ...row.machine_id ? ["machine_id"] : []]) {
523
- id2(row[key]);
524
- }
525
- if (row.schema_version !== 1 || !Number.isSafeInteger(row.uid) || Number(row.uid) < 0 || !Number.isSafeInteger(row.created_at) || !Number.isSafeInteger(row.expires_at) || Number(row.expires_at) <= Number(row.created_at) || !phases.has(String(row.phase))) {
526
- throw new CliError2("invalid_config");
527
- }
528
- if (row.enrollment_sha256 !== undefined) {
529
- sha2(row.enrollment_sha256);
530
- }
531
- if (row.native_fingerprint !== undefined) {
532
- sha2(row.native_fingerprint);
533
- }
534
- if (row.providers !== undefined) {
535
- setupProviderKinds(row.providers);
536
- }
537
- if (row.repair !== undefined && (row.repair !== true || row.providers === undefined)) {
538
- throw new CliError2("invalid_config");
539
- }
540
- if (row.lineage !== undefined) {
541
- id2(row.lineage);
542
- }
543
- if (!["registering", "reserved"].includes(String(row.phase)) && (!row.machine_id || !row.enrollment_sha256)) {
544
- throw new CliError2("invalid_config");
545
- }
546
- return Object.freeze({
547
- ...row,
548
- ...row.providers ? { providers: setupProviderKinds(row.providers) } : {}
549
- });
550
- } catch {
551
- throw new CliError2("invalid_config");
552
- }
553
- }
554
-
555
- class SetupApproval {
556
- terminal;
557
- directory;
558
- #providers = new Map;
559
- #resets = new Map;
560
- #state;
561
- constructor(terminal, directory) {
562
- this.terminal = terminal;
563
- this.directory = directory;
564
- }
565
- attach(state) {
566
- this.terminal.assertOwner();
567
- this.#state = state;
568
- }
569
- detach(state) {
570
- if (this.#state === state) {
571
- this.#state = undefined;
572
- }
573
- }
574
- state() {
575
- this.terminal.assertOwner();
576
- if (!this.#state) {
577
- throw new CliError2("backend_unavailable", 3);
578
- }
579
- return this.#state;
580
- }
581
- path(attempt) {
582
- id2(attempt);
583
- return runtimeReference(this.directory, "setup_" + attempt);
584
- }
585
- read(attempt) {
586
- const state = this.state();
587
- const result = plan(readSetupJson(this.path(attempt)));
588
- if (result.attempt_id !== attempt || result.actor !== this.terminal.actor || result.uid !== this.terminal.uid || result.generation !== state.store.generation()) {
589
- throw new CliError2("configuration_required");
590
- }
591
- return result;
592
- }
593
- active() {
594
- const raw = readSetupJson(runtimeReference(this.directory, "setup-active"));
595
- if (raw === undefined) {
596
- return;
597
- }
598
- const value = object2(raw);
599
- fields2(value, ["attempt_id"]);
600
- id2(value.attempt_id);
601
- return this.read(value.attempt_id);
602
- }
603
- save(value, replace) {
604
- const checked = plan(value);
605
- writeSetupJson(this.path(checked.attempt_id), checked, replace);
606
- return checked;
607
- }
608
- async issue() {
609
- const state = this.state();
610
- const previous = this.active();
611
- if (previous && previous.phase !== "ready") {
612
- if (!["issued", "paired", "native_approved"].includes(previous.phase) || previous.phase === "issued" && Date.now() >= previous.expires_at) {
613
- throw new CliError2("configuration_required");
614
- }
615
- if (!await this.terminal.confirm(cliMessages2(this.terminal.io.language)["setup.approval.resumeConfirm"])) {
616
- throw new CliError2("configuration_required");
617
- }
618
- return { plan: previous };
619
- }
620
- if (!await this.terminal.confirm(cliMessages2(this.terminal.io.language)["setup.approval.pairConfirm"])) {
621
- return { declined: true };
622
- }
623
- const now = Date.now();
624
- const attempt = randomUUID();
625
- const lineage = previous?.phase === "ready" ? previous.lineage ?? previous.attempt_id : undefined;
626
- let current = this.save({
627
- schema_version: 1,
628
- attempt_id: attempt,
629
- actor: this.terminal.actor,
630
- uid: this.terminal.uid,
631
- generation: state.store.generation(),
632
- ...lineage ? { lineage } : {},
633
- name: "connector-" + attempt.slice(0, 8),
634
- created_at: now,
635
- expires_at: now + 300000,
636
- phase: "registering"
637
- }, false);
638
- writeSetupJson(runtimeReference(this.directory, "setup-active"), { attempt_id: attempt }, previous !== undefined);
639
- let registration = false;
640
- let issuance = false;
641
- const admin = new PairingAdmin2(state.store, {
642
- authorize: (action, machine) => {
643
- this.state();
644
- if (this.#state !== state || Date.now() >= current.expires_at) {
645
- throw new CliError2("configuration_required");
646
- }
647
- if (action === "register" && machine === undefined && !registration && current.phase === "registering") {
648
- registration = true;
649
- return;
650
- }
651
- if (action === "enroll" && machine === current.machine_id && !issuance && current.phase === "reserved") {
652
- issuance = true;
653
- return;
654
- }
655
- throw new CliError2("configuration_required");
656
- }
657
- });
658
- const machine = admin.register(current.name);
659
- current = this.save({ ...current, phase: "reserved", machine_id: machine.machine_id }, true);
660
- const enrollment = admin.issue(machine.machine_id, Math.max(1, current.expires_at - Date.now()));
661
- current = this.save({ ...current, phase: "issued", enrollment_sha256: createHash2("sha256").update(enrollment).digest("hex") }, true);
662
- return { plan: current, code: machine.machine_id + "." + current.attempt_id + "." + enrollment };
663
- }
664
- async issueRepair() {
665
- const state = this.state();
666
- const previous = this.active();
667
- const retry = previous?.repair === true && ["issued", "redeeming", "unknown"].includes(previous.phase);
668
- if (!previous?.machine_id || !previous.providers || previous.phase !== "ready" && !retry) {
669
- throw new CliError2("configuration_required");
670
- }
671
- const machine = previous.machine_id;
672
- const device = state.store.machine(machine);
673
- if (device.status === "revoked") {
674
- throw new CliError2("configuration_required");
675
- }
676
- if (!await this.terminal.confirm(formatMessage2(cliMessages2(this.terminal.io.language)["setup.approval.repairDeviceConfirm"], {
677
- name: device.name,
678
- machine
679
- }))) {
680
- throw new CliError2("configuration_required");
681
- }
682
- for (let attempt = 0;; attempt++) {
683
- const epoch = state.store.deviceEpoch(machine);
684
- const generation = state.store.generation();
685
- this.#resets.set(machine, {
686
- confirmation: randomUUID(),
687
- expires: Date.now() + 60000,
688
- fence: boundedJson2({ recovery_generation: generation, connection_epoch: epoch })
689
- });
690
- try {
691
- state.store.resetDeviceEnrollment(machine, generation, epoch);
692
- state.channels.blockAndClose(machine);
693
- break;
694
- } catch (error) {
695
- this.#resets.delete(machine);
696
- if (attempt >= 2) {
697
- throw error;
698
- }
699
- }
700
- }
701
- const now = Date.now();
702
- const attempt = randomUUID();
703
- let current = this.save({
704
- schema_version: 1,
705
- attempt_id: attempt,
706
- actor: this.terminal.actor,
707
- uid: this.terminal.uid,
708
- generation: state.store.generation(),
709
- name: previous.name,
710
- created_at: now,
711
- expires_at: now + 300000,
712
- phase: "reserved",
713
- machine_id: machine,
714
- providers: previous.providers,
715
- repair: true,
716
- ...previous.lineage ? { lineage: previous.lineage } : {}
717
- }, false);
718
- let issuance = false;
719
- const admin = new PairingAdmin2(state.store, {
720
- authorize: (action, target) => {
721
- this.state();
722
- if (this.#state !== state || Date.now() >= current.expires_at || action !== "enroll" || target !== machine || issuance) {
723
- throw new CliError2("configuration_required");
724
- }
725
- issuance = true;
726
- }
727
- });
728
- const enrollment = admin.issue(machine, Math.max(1, current.expires_at - Date.now()));
729
- current = this.save({ ...current, phase: "issued", enrollment_sha256: createHash2("sha256").update(enrollment).digest("hex") }, true);
730
- writeSetupJson(runtimeReference(this.directory, "setup-active"), { attempt_id: attempt }, true);
731
- return { plan: current, code: machine + "." + current.attempt_id + "." + enrollment };
732
- }
733
- claim(attempt, machine, enrollment) {
734
- const current = this.read(attempt);
735
- if (current.phase !== "issued" || current.machine_id !== machine || Date.now() >= current.expires_at || typeof enrollment !== "string" || !/^[A-Za-z0-9_-]{32,256}$/.test(enrollment) || !timingSafeEqual2(createHash2("sha256").update(enrollment).digest(), Buffer.from(current.enrollment_sha256, "hex"))) {
736
- throw new CliError2("configuration_required");
737
- }
738
- return this.save({ ...current, phase: "redeeming" }, true);
739
- }
740
- finishDelivery(attempt, delivered) {
741
- const current = this.read(attempt);
742
- if (current.phase !== "redeeming") {
743
- throw new CliError2("configuration_required");
744
- }
745
- return this.save({ ...current, phase: delivered ? current.repair ? "configured" : "paired" : "unknown" }, true);
746
- }
747
- async approveNative(attempt, provider, fingerprint, description, signal = this.terminal.io.signal) {
748
- const state = this.state();
749
- const current = this.read(attempt);
750
- const providers = setupProviderKinds(typeof provider === "string" ? [provider] : provider);
751
- sha2(fingerprint);
752
- if (!["paired", "native_approved"].includes(current.phase)) {
753
- throw new CliError2("configuration_required");
754
- }
755
- if (current.phase === "native_approved" && current.native_fingerprint !== fingerprint) {
756
- throw new CliError2("configuration_required");
757
- }
758
- if (current.providers && boundedJson2(current.providers) !== boundedJson2(providers)) {
759
- throw new CliError2("configuration_required");
760
- }
761
- if (!await this.terminal.confirm(description, signal)) {
762
- throw new CliError2("configuration_required");
763
- }
764
- const checked = this.read(attempt);
765
- if (checked.phase !== current.phase || checked.native_fingerprint !== current.native_fingerprint || this.#state !== state) {
766
- throw new CliError2("configuration_required");
767
- }
768
- for (const kind of providers) {
769
- this.#providers.set(current.machine_id + "/" + kind, {
770
- confirmation: randomUUID(),
771
- expires: Date.now() + 300000,
772
- generation: current.generation
773
- });
774
- }
775
- return this.save({ ...current, phase: "native_approved", native_fingerprint: fingerprint, providers }, true);
776
- }
777
- authorizeManagement(request) {
778
- const state = this.state();
779
- if (request.action === "device_enrollment_reset") {
780
- const reset = this.#resets.get(request.target);
781
- if (!reset || reset.fence !== request.fence || Date.now() >= reset.expires) {
782
- throw new CliError2("configuration_required");
783
- }
784
- this.#resets.delete(request.target);
785
- return { actor: this.terminal.actor, confirmation_id: reset.confirmation };
786
- }
787
- const permit = this.#providers.get(request.target);
788
- if (request.action !== "providers_manage" || request.fence !== "new" || !permit || permit.generation !== state.store.generation() || Date.now() >= permit.expires) {
789
- throw new CliError2("configuration_required");
790
- }
791
- this.#providers.delete(request.target);
792
- return { actor: this.terminal.actor, confirmation_id: permit.confirmation };
793
- }
794
- finishNative(attempt, fingerprint) {
795
- const current = this.read(attempt);
796
- if (current.phase !== "native_approved" || current.native_fingerprint !== fingerprint) {
797
- throw new CliError2("configuration_required");
798
- }
799
- return this.save({ ...current, phase: "configured" }, true);
800
- }
801
- finishReady(attempt) {
802
- const current = this.read(attempt);
803
- if (current.phase !== "configured") {
804
- throw new CliError2("configuration_required");
805
- }
806
- this.save({ ...current, phase: "ready" }, true);
807
- }
808
- }
809
-
810
- // src/cli/setup-gateway.ts
811
- function publish(path, value, maximum = 16384) {
812
- const current = readSetupJson(path, maximum);
813
- if (current === undefined) {
814
- writeSetupJson(path, value, false, maximum);
815
- } else if (boundedJson2(current, maximum) !== boundedJson2(value, maximum)) {
816
- throw new CliError2("configuration_required");
817
- }
818
- }
819
- function unionGrants(existing, added) {
820
- const merged = [...existing];
821
- for (const grant of added) {
822
- const found = merged.find((entry) => boundedJson2(entry.target) === boundedJson2(grant.target));
823
- if (!found) {
824
- merged.push(grant);
825
- } else if (boundedJson2(found) !== boundedJson2(grant)) {
826
- throw new CliError2("configuration_required");
827
- }
828
- }
829
- return merged;
830
- }
831
- function publishFirstCallers(path, resource, authorization, caller) {
832
- const previous = readSetupJson(path, REGISTRY_MAX_BYTES);
833
- const created = {
834
- schema_version: 1,
835
- kind: "mcp-caller-source",
836
- resource,
837
- local_policy: authorization.policies.local,
838
- callers: [caller]
839
- };
840
- if (previous === undefined) {
841
- parseRegistry(JSON.parse(boundedJson2(created, REGISTRY_MAX_BYTES)));
842
- writeSetupFile(path, boundedJson2(created, REGISTRY_MAX_BYTES), false);
843
- return;
844
- }
845
- const registry = parseRegistry(previous, resource);
846
- const kept = registry.callers.filter((entry) => isAdminPlaceholder(entry) || entry.scope === "gateway" || boundedJson2(entry, REGISTRY_MAX_BYTES) === boundedJson2(caller, REGISTRY_MAX_BYTES));
847
- if (kept.length !== registry.callers.length) {
848
- throw new CliError2("configuration_required");
849
- }
850
- const callers = [...kept];
851
- if (!callers.some((entry) => boundedJson2(entry, REGISTRY_MAX_BYTES) === boundedJson2(caller, REGISTRY_MAX_BYTES))) {
852
- callers.push(caller);
853
- }
854
- const merged = { ...created, callers };
855
- parseRegistry(JSON.parse(boundedJson2(merged, REGISTRY_MAX_BYTES)), resource);
856
- const stamp = setupFileStamp(path);
857
- writeSetupFile(path, boundedJson2(merged, REGISTRY_MAX_BYTES), true, stamp);
858
- }
859
-
860
- class GatewaySetup {
861
- config;
862
- terminal;
863
- options;
864
- approval;
865
- #begun = false;
866
- #active;
867
- #timer;
868
- #reload;
869
- reconfigure = new Promise(() => {});
870
- constructor(config, terminal, options = {}) {
871
- this.config = config;
872
- this.terminal = terminal;
873
- this.options = options;
874
- this.approval = new SetupApproval(terminal, config.state_dir);
875
- }
876
- attach(state) {
877
- this.approval.attach(state);
878
- this.reconfigure = new Promise((resolve) => {
879
- this.#reload = resolve;
880
- });
881
- const http = new GatewayPairingHttp(state, this.approval, (request) => this.native(state, request), () => setImmediate(() => this.#reload?.()));
882
- this.#active = http;
883
- return http;
884
- }
885
- async listening(gateway, mcpUrl) {
886
- if (this.options.repair && !this.#begun) {
887
- this.#begun = true;
888
- const active = this.approval.active();
889
- if (active?.repair && active.phase === "configured") {
890
- this.terminal.show(cliMessages2(this.terminal.io.language)["setup.gateway.resumeRePairing"]);
891
- } else {
892
- const issued = await this.approval.issueRepair();
893
- this.terminal.show(formatMessage2(cliMessages2(this.terminal.io.language)["setup.gateway.repairCodeBlock"], {
894
- mcpUrl,
895
- endpoint: gateway.transport.url.replace(/\/connector$/, ""),
896
- code: issued.code
897
- }));
898
- }
899
- }
900
- const current = this.approval.active();
901
- if (!this.#begun && (current?.phase === "ready" ? this.options.addDevice === true : current?.phase !== "configured")) {
902
- this.#begun = true;
903
- this.terminal.show(formatMessage2(cliMessages2(this.terminal.io.language)["setup.gateway.endpoints"], {
904
- mcpUrl,
905
- endpoint: gateway.transport.url.replace(/\/connector$/, "")
906
- }));
907
- const issued = await this.approval.issue();
908
- if (issued.declined) {
909
- const page = this.config.management_enabled ? "http://127.0.0.1:" + this.config.management_port + "/" : "";
910
- this.terminal.show(cliMessages2(this.terminal.io.language)["setup.gateway.unpairedRunning"] + (page ? formatMessage2(cliMessages2(this.terminal.io.language)["setup.gateway.managementFirstPairing"], { page }) : cliMessages2(this.terminal.io.language)["setup.gateway.managementDisabled"]));
911
- this.terminal.finishQuestions();
912
- return;
913
- }
914
- if (issued.code) {
915
- this.terminal.show(formatMessage2(cliMessages2(this.terminal.io.language)["setup.gateway.pairingCodeBlock"], { code: issued.code }));
916
- } else {
917
- this.terminal.show(cliMessages2(this.terminal.io.language)["setup.gateway.resumingExisting"]);
918
- }
919
- }
920
- const plan = this.approval.active();
921
- if (plan?.phase === "ready") {
922
- this.terminal.finishQuestions();
923
- return;
924
- }
925
- if (plan?.phase === "configured") {
926
- this.terminal.finishQuestions();
927
- let previous = "";
928
- this.#timer = setInterval(() => {
929
- try {
930
- const device = gateway.transport.status().devices.find((entry) => entry.machine_id === plan.machine_id);
931
- const current = device?.authenticated ? "connector_authenticated native_pending" : "gateway_waiting_for_connector";
932
- if (current !== previous) {
933
- previous = current;
934
- this.terminal.show(current + `
935
- `);
936
- }
937
- if (device?.authenticated && device.ready && (plan.providers ?? ["luvus"]).every((kind) => device.providers.some((entry) => entry.provider_id === kind && entry.ready))) {
938
- this.approval.finishReady(plan.attempt_id);
939
- this.terminal.show(cliMessages2(this.terminal.io.language)["setup.gateway.connectorReady"]);
940
- clearInterval(this.#timer);
941
- this.#timer = undefined;
942
- }
943
- } catch {
944
- this.terminal.io.stderr.write(`setup_observation_unavailable
945
- `);
946
- clearInterval(this.#timer);
947
- this.#timer = undefined;
948
- }
949
- }, 250);
950
- }
951
- }
952
- async native(state, request) {
953
- const { reviewed, kinds, authorization, fingerprint } = await reviewSetupManifest(request.input, request.plan, request.credential.machine_id, this.config.state_dir, request.signal);
954
- if (request.plan.phase === "configured") {
955
- if (request.plan.native_fingerprint !== fingerprint) {
956
- throw new CliError2("configuration_required");
957
- }
958
- return { state: "configured", fingerprint };
959
- }
960
- if (request.signal.aborted) {
961
- throw new CliError2("configuration_required");
962
- }
963
- const providers = readRuntimeConfig2(this.config).providers;
964
- const additional = request.plan.lineage !== undefined;
965
- if (providers.some((entry) => {
966
- const expected = reviewed.find((value) => value.reference === entry.reference);
967
- return expected ? boundedJson2(entry.value, 1048576) !== boundedJson2(expected.value, 1048576) : !additional;
968
- })) {
969
- throw new CliError2("configuration_required");
970
- }
971
- await this.approval.approveNative(request.plan.attempt_id, kinds, fingerprint, formatMessage2(cliMessages2(this.terminal.io.language)["setup.prompt.gatewayNativeInstallations"], {
972
- entries: reviewed.map((entry) => entry.kind + " " + entry.label + ", Target " + boundedJson2(entry.target) + ", binary SHA-256 " + entry.binary_sha256).join("; ")
973
- }), request.signal);
974
- this.terminal.assertOwner();
975
- if (request.signal.aborted) {
976
- throw new CliError2("configuration_required");
977
- }
978
- await publishSetupSchemas(reviewed, request.signal);
979
- const hostname = canonicalLoopbackHost(this.config.mcp_hostname);
980
- const resource = new URL("http://" + hostname + ":" + this.config.mcp_port + "/mcp").href;
981
- const clientPath = runtimeReference(this.config.state_dir, "mcp-client");
982
- const original = readSetupJson(clientPath);
983
- if (additional && original === undefined) {
984
- throw new CliError2("configuration_required");
985
- }
986
- const client = original === undefined ? {
987
- schema_version: 1,
988
- kind: "mcp-client-credential",
989
- attempt_id: request.plan.attempt_id,
990
- resource,
991
- principal_id: authorization.principal_id,
992
- token: randomBytes(32).toString("base64url"),
993
- issued_at: Date.now(),
994
- expires_at: Date.now() + 30 * 86400000
995
- } : object2(original);
996
- fields2(client, [
997
- "schema_version",
998
- "kind",
999
- "attempt_id",
1000
- "resource",
1001
- "principal_id",
1002
- "token",
1003
- "issued_at",
1004
- "expires_at"
1005
- ]);
1006
- if (client.schema_version !== 1 || client.kind !== "mcp-client-credential" || client.attempt_id !== (request.plan.lineage ?? request.plan.attempt_id) || client.resource !== resource || client.principal_id !== authorization.principal_id || typeof client.token !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(client.token) || !Number.isSafeInteger(client.issued_at) || !Number.isSafeInteger(client.expires_at) || Number(client.expires_at) <= Date.now()) {
1007
- throw new CliError2("configuration_required");
1008
- }
1009
- publish(clientPath, client);
1010
- const callersPath = runtimeReference(this.config.state_dir, "mcp-callers");
1011
- const providersPath = runtimeReference(this.config.state_dir, "providers");
1012
- const reviewedEntries = reviewed.map((entry) => ({
1013
- reference: entry.reference,
1014
- value: entry.value
1015
- }));
1016
- if (!additional) {
1017
- publishFirstCallers(callersPath, resource, authorization, {
1018
- principal: { id: authorization.principal_id, policy: authorization.policies.caller },
1019
- token_sha256: createHash3("sha256").update(client.token).digest("hex"),
1020
- issued_at: client.issued_at,
1021
- expires_at: client.expires_at
1022
- });
1023
- publish(providersPath, { schema_version: 1, role: "gateway", providers: reviewedEntries }, 1048576);
1024
- } else {
1025
- mergeSetupCallers(callersPath, resource, authorization);
1026
- mergeSetupProviders(providersPath, reviewedEntries);
1027
- }
1028
- for (const entry of reviewed) {
1029
- const binding = state.store.provider(entry.target.machine_id, entry.target.provider_id);
1030
- if (!binding) {
1031
- state.store.defineProvider(entry.binding);
1032
- } else if (boundedJson2(binding) !== boundedJson2(entry.binding)) {
1033
- throw new CliError2("configuration_required");
1034
- }
1035
- }
1036
- this.approval.finishNative(request.plan.attempt_id, fingerprint);
1037
- let adminNote = "";
1038
- if (this.config.management_enabled && !managementSourceExists2(this.config)) {
1039
- const page = "http://127.0.0.1:" + this.config.management_port + "/";
1040
- try {
1041
- const admin = writeAdminFiles2(this.config, "admin", Date.now());
1042
- adminNote = formatMessage2(cliMessages2(this.terminal.io.language)["setup.gateway.adminBlock"], {
1043
- page,
1044
- secret: admin.secret
1045
- });
1046
- } catch {
1047
- adminNote = cliMessages2(this.terminal.io.language)["setup.gateway.adminNotCreated"];
1048
- }
1049
- }
1050
- this.terminal.show(formatMessage2(cliMessages2(this.terminal.io.language)["setup.gateway.nativeSaved"], {
1051
- clientPath,
1052
- url: this.config.mcp_public_url ?? resource,
1053
- targets: boundedJson2(reviewed.map((entry) => entry.target))
1054
- }) + (additional ? cliMessages2(this.terminal.io.language)["setup.gateway.bearerAdditional"] : formatMessage2(cliMessages2(this.terminal.io.language)["setup.gateway.bearerSecret"], {
1055
- token: client.token
1056
- })) + adminNote + `
1057
- `);
1058
- setImmediate(() => this.#reload?.());
1059
- return { state: "configured", fingerprint };
1060
- }
1061
- async detach(state) {
1062
- if (this.#timer) {
1063
- clearInterval(this.#timer);
1064
- this.#timer = undefined;
1065
- }
1066
- const http = this.#active;
1067
- this.#active = undefined;
1068
- await http?.close();
1069
- this.approval.detach(state);
1070
- }
1071
- }
1072
- function mergeSetupCallers(path, resource, authorization) {
1073
- const stamp = setupFileStamp(path);
1074
- const registry = structuredClone(parseRegistry(readRuntimeJson(path, REGISTRY_MAX_BYTES), resource));
1075
- if (registry.local_policy.revision !== authorization.policies.local.revision) {
1076
- throw new CliError2("configuration_required");
1077
- }
1078
- registry.local_policy.grants = unionGrants(registry.local_policy.grants, authorization.policies.local.grants);
1079
- for (const entry of registry.callers) {
1080
- if (entry.principal.id === authorization.principal_id) {
1081
- if (entry.principal.policy.revision !== authorization.policies.caller.revision) {
1082
- throw new CliError2("configuration_required");
1083
- }
1084
- entry.principal.policy.grants = unionGrants(entry.principal.policy.grants, authorization.policies.caller.grants);
1085
- }
1086
- }
1087
- parseRegistry(JSON.parse(boundedJson2(registry, REGISTRY_MAX_BYTES)), resource);
1088
- writeSetupFile(path, boundedJson2(registry, REGISTRY_MAX_BYTES), true, stamp);
1089
- }
1090
- function mergeSetupProviders(path, entries) {
1091
- const stamp = setupFileStamp(path);
1092
- const document = object2(readRuntimeJson(path, 1048576));
1093
- fields2(document, ["schema_version", "role", "providers"]);
1094
- if (document.schema_version !== 1 || document.role !== "gateway" || !Array.isArray(document.providers)) {
1095
- throw new CliError2("configuration_required");
1096
- }
1097
- const merged = [...document.providers];
1098
- for (const entry of entries) {
1099
- const found = merged.find((value) => value.reference === entry.reference);
1100
- if (!found) {
1101
- merged.push(entry);
1102
- } else if (boundedJson2(found.value, 1048576) !== boundedJson2(entry.value, 1048576)) {
1103
- throw new CliError2("configuration_required");
1104
- }
1105
- }
1106
- if (merged.length > 32) {
1107
- throw new CliError2("configuration_required");
1108
- }
1109
- writeSetupFile(path, boundedJson2({ ...document, providers: merged }, 1048576), true, stamp);
1110
- }
1111
-
1112
- export { setupSessions, setupTarget, setupProviderKinds, selectedSetupPolicies, setupReference, setupSessionsFor, setupReply, requestJson, device, reviewSetupManifest, publishSetupSchemas, publish, publishFirstCallers, GatewaySetup, mergeSetupCallers, mergeSetupProviders };
1113
-
1114
- //# debugId=0EFB955908D6EDF264756E2164756E21