@scp-dev/broker 0.1.3 → 0.1.5

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/dist/index.js CHANGED
@@ -732,7 +732,8 @@ function startBridge(options) {
732
732
  labels: view.labels,
733
733
  status: view.status ? { state: view.status.state, note: options.redact ? "" : view.status.note } : null,
734
734
  presence: view.presence,
735
- last_seen: view.last_seen
735
+ last_seen: view.last_seen,
736
+ ...view.kind === "agent" ? { wake: view.wake ?? null } : {}
736
737
  };
737
738
  }
738
739
  function localViews() {
@@ -1060,11 +1061,11 @@ import { setTimeout as sleep2 } from "timers/promises";
1060
1061
  import {
1061
1062
  BROKER_CAPABILITIES,
1062
1063
  CLI_NAME,
1063
- LIMITS as LIMITS2,
1064
+ LIMITS,
1064
1065
  parsePolicy,
1065
1066
  PREAMBLE,
1066
1067
  PROTOCOL as PROTOCOL3,
1067
- RelayError as RelayError4,
1068
+ RelayError as RelayError3,
1068
1069
  SERVER_FIELDS,
1069
1070
  stripControl
1070
1071
  } from "@scp-dev/core";
@@ -1072,460 +1073,20 @@ import { FederatedCore } from "@scp-dev/hub";
1072
1073
 
1073
1074
  // src/judge-gate.ts
1074
1075
  import { accessSync, constants, readFileSync as readFileSync4 } from "fs";
1075
- import {
1076
- applyRedactions,
1077
- DEFAULT_JUDGE_BUDGET,
1078
- findSecret as findSecret2,
1079
- HttpJudgeClient,
1080
- JUDGE_PROMPT_VERSION,
1081
- judgeBoundary,
1082
- LIMITS,
1083
- mergeVerdicts,
1084
- newId as newId2,
1085
- newJudgeStats,
1086
- one,
1087
- RelayError as RelayError3,
1088
- runJudge,
1089
- sha256Hex,
1090
- VerdictCache
1091
- } from "@scp-dev/core";
1092
- var HELD_PER_SENDER = 100;
1093
- var iso = (ms) => ms === null ? null : new Date(ms).toISOString();
1094
- var JudgeGate = class {
1095
- constructor(o) {
1096
- this.o = o;
1097
- o.sql.all(
1098
- `CREATE TABLE IF NOT EXISTS judge_holds (id TEXT PRIMARY KEY, from_ep TEXT NOT NULL, params TEXT NOT NULL, origin TEXT NOT NULL, verdict TEXT NOT NULL,
1099
- created_at INTEGER NOT NULL, expires_at INTEGER NOT NULL, state TEXT NOT NULL, by TEXT, resolved_at INTEGER)`
1100
- );
1101
- o.sql.all(
1102
- `CREATE TABLE IF NOT EXISTS judge_sent (from_ep TEXT NOT NULL, client_id TEXT NOT NULL, fingerprint TEXT NOT NULL, result TEXT NOT NULL, created_at INTEGER NOT NULL,
1103
- PRIMARY KEY (from_ep, client_id))`
1104
- );
1105
- o.sql.all(`UPDATE judge_holds SET state = 'held' WHERE state = 'releasing'`);
1106
- }
1107
- o;
1108
- stats = newJudgeStats();
1109
- policy;
1110
- version = "";
1111
- client = null;
1112
- cache = new VerdictCache();
1113
- shadows = /* @__PURE__ */ new Set();
1114
- /** judged sends in the last hour per budget: `ep:<id>`, `tree:<root id>` and `*` for the whole broker */
1115
- spent = /* @__PURE__ */ new Map();
1116
- /** the policy's judge section; a model client only when its key is readable, else the prefilter alone */
1117
- configure(policy) {
1118
- this.policy = policy;
1119
- this.version = policy ? sha256Hex(JSON.stringify(policy)) : "";
1120
- this.client = null;
1121
- const model = policy?.model;
1122
- if (!policy || policy.mode === "off" || !model) return;
1123
- const key = this.keyReader(model);
1124
- if (!key) {
1125
- this.o.log(
1126
- `relay broker: the judge model's key (${model.key_env ? `key_env ${model.key_env}` : `key_file ${model.key_file}`}) is not readable; judging with the prefilter only`
1127
- );
1128
- return;
1129
- }
1130
- this.client = (this.o.client ?? ((m, k) => new HttpJudgeClient({ model: m.id, effort: m.effort, key: k })))(model, key);
1131
- }
1132
- keyReader(model) {
1133
- if (model.key_env) {
1134
- const name = model.key_env;
1135
- return this.o.env[name] ? async () => this.o.env[name] ?? "" : null;
1136
- }
1137
- const file = model.key_file;
1138
- try {
1139
- accessSync(file, constants.R_OK);
1140
- } catch {
1141
- return null;
1142
- }
1143
- return async () => readFileSync4(file, "utf8").trim();
1144
- }
1145
- /** resolves once every shadow judgement started so far finished */
1146
- async settled() {
1147
- await Promise.all([...this.shadows]);
1148
- }
1149
- /** a send or ask through the judge; an unjudged one goes straight to `deliver` */
1150
- async send(from, params, origin, deliver) {
1151
- const policy = this.policy;
1152
- const body = params.body;
1153
- if (!policy || policy.mode === "off" || typeof body !== "string" || new TextEncoder().encode(body).length > LIMITS.bodyBytes || findSecret2(body))
1154
- return deliver(from, params, origin);
1155
- const targets = Array.isArray(params.to) ? params.to : [params.to];
1156
- if (targets.some((to) => this.o.core.foreignAccount(to) !== null)) {
1157
- if (policy.mode === "enforce") {
1158
- this.o.core.auditJudge("judge_skipped", from, null, {
1159
- side: "local",
1160
- boundary: "other account",
1161
- prompt_version: JUDGE_PROMPT_VERSION,
1162
- categories: [],
1163
- sha256: sha256Hex(body),
1164
- verdict: "skipped",
1165
- reason: "other_account_judged_by_hub",
1166
- to: targets.map(String)
1167
- });
1168
- }
1169
- return deliver(from, params, origin);
1170
- }
1171
- if (policy.mode === "shadow") {
1172
- const result = await deliver(from, params, origin);
1173
- if (!result.duplicate) this.shadow(policy, from, params, origin, result.id);
1174
- return result;
1175
- }
1176
- const { groups, tree } = this.boundaries(policy, from, params, origin);
1177
- if (!groups.size) return deliver(from, params, origin);
1178
- const ttl = params.ttl_s ?? LIMITS.ttlDefaultS;
1179
- if (!Number.isInteger(ttl) || ttl < 1 || ttl > LIMITS.ttlMaxS) throw new RelayError3("invalid_params", `ttl_s must be 1 to ${LIMITS.ttlMaxS}`);
1180
- const seen = params.client_id === void 0 ? void 0 : this.seen(from, params);
1181
- if (seen) return seen;
1182
- const waiting = one(this.o.sql, `SELECT COUNT(*) AS n FROM judge_holds WHERE from_ep = ? AND state IN ('held', 'releasing')`, from).n;
1183
- if (waiting >= HELD_PER_SENDER)
1184
- throw new RelayError3("rate_limited", `${waiting} of your messages wait for review by your human; send more once they released or refused some`, 3600);
1185
- this.spend(policy, from, tree);
1186
- const verdict = await this.judge(policy, from, body, groups);
1187
- const detail = this.detail(verdict, body, groups);
1188
- switch (verdict.verdict) {
1189
- case "refuse":
1190
- this.o.core.auditJudge("judge_refuse", from, null, detail);
1191
- throw new RelayError3(
1192
- "judge_refused",
1193
- `the relay judge refused this message (local; ${verdict.categories.join(", ") || "no category"}): ${verdict.reason}`,
1194
- void 0,
1195
- {
1196
- side: "local",
1197
- categories: verdict.categories,
1198
- reason: verdict.reason
1199
- }
1200
- );
1201
- case "hold":
1202
- return this.hold(from, params, origin, verdict, groups, detail);
1203
- case "redact": {
1204
- const applied = applyRedactions(body, verdict.redactions);
1205
- const result = await deliver(from, { ...params, body: applied.text }, origin);
1206
- const redacted = { n: applied.n, kinds: applied.kinds };
1207
- this.o.core.auditJudge(verdict.source === "error" ? "judge_skipped" : "judge_redact", from, result.id, { ...detail, redacted });
1208
- return this.remember(from, params, { ...result, redacted });
1209
- }
1210
- case "pass": {
1211
- const result = await deliver(from, params, origin);
1212
- if (verdict.source === "error") this.o.core.auditJudge("judge_skipped", from, result.id, detail);
1213
- return this.remember(from, params, result);
1214
- }
1215
- }
1216
- }
1217
- /** an earlier judged send or waiting hold under the send's client_id: the same message is answered as before, another one refused as core refuses it */
1218
- seen(from, params) {
1219
- const clientId = params.client_id;
1220
- const held2 = one(
1221
- this.o.sql,
1222
- `SELECT id, params FROM judge_holds WHERE from_ep = ? AND json_extract(params, '$.client_id') = ? AND state IN ('held', 'releasing')`,
1223
- from,
1224
- clientId
1225
- );
1226
- const sent = held2 ? void 0 : one(
1227
- this.o.sql,
1228
- `SELECT fingerprint, result FROM judge_sent WHERE from_ep = ? AND client_id = ? AND created_at > ?`,
1229
- from,
1230
- clientId,
1231
- this.o.now() - LIMITS.idempotencyS * 1e3
1232
- );
1233
- const before = held2 ? fingerprint(JSON.parse(held2.params)) : sent?.fingerprint;
1234
- if (before === void 0) return void 0;
1235
- if (before !== fingerprint(params)) throw new RelayError3("invalid_params", `client_id ${clientId} was already used for a different message`);
1236
- return held2 ? heldResult(held2.id, params) : { ...JSON.parse(sent.result), duplicate: true };
1237
- }
1238
- remember(from, params, result) {
1239
- if (params.client_id !== void 0 && !result.duplicate) {
1240
- this.o.sql.all(
1241
- `INSERT OR REPLACE INTO judge_sent (from_ep, client_id, fingerprint, result, created_at) VALUES (?, ?, ?, ?, ?)`,
1242
- from,
1243
- params.client_id,
1244
- fingerprint(params),
1245
- JSON.stringify(result),
1246
- this.o.now()
1247
- );
1248
- }
1249
- return result;
1250
- }
1251
- /**
1252
- * a judged send counts before any model call against its sender's, its lineage tree's and the whole broker's budget, since refused and
1253
- * held sends never reach the send's own limits, and a new handle or a helper process must not bring a fresh budget
1254
- */
1255
- spend(policy, from, tree) {
1256
- const now = this.o.now();
1257
- const global = policy.budget ?? DEFAULT_JUDGE_BUDGET;
1258
- const budgets = [
1259
- [`ep:${from}`, LIMITS.pairPerMinute, LIMITS.pairPerHour, "from this endpoint"],
1260
- [`tree:${tree}`, LIMITS.pairPerMinute * 2, LIMITS.pairPerHour * 2, "from this lineage tree"],
1261
- ["*", global.per_minute, global.per_hour, "on this broker"]
1262
- ];
1263
- const recent = budgets.map(([key]) => (this.spent.get(key) ?? []).filter((at) => at > now - 36e5));
1264
- budgets.forEach(([key, perMinute, perHour, what], i) => {
1265
- for (const [limit, windowS] of [
1266
- [perMinute, 60],
1267
- [perHour, 3600]
1268
- ]) {
1269
- const inWindow = recent[i].filter((at) => at > now - windowS * 1e3);
1270
- if (inWindow.length >= limit) {
1271
- this.spent.set(key, recent[i]);
1272
- throw new RelayError3(
1273
- "rate_limited",
1274
- `${limit} judged messages ${what} in ${windowS} s`,
1275
- Math.max(1, Math.ceil((inWindow[0] + windowS * 1e3 - now) / 1e3))
1276
- );
1277
- }
1278
- }
1279
- });
1280
- for (const [i, [key]] of budgets.entries()) this.spent.set(key, [...recent[i], now]);
1281
- }
1282
- /** how many budgets are tracked (idle ones are evicted by the sweep) */
1283
- budgetKeys() {
1284
- return this.spent.size;
1285
- }
1286
- /** the held messages, newest first, without bodies */
1287
- holds(state) {
1288
- const rows = state ? this.o.sql.all(`SELECT * FROM judge_holds WHERE state = ? ORDER BY created_at DESC, rowid DESC LIMIT 200`, state) : this.o.sql.all(`SELECT * FROM judge_holds ORDER BY created_at DESC, rowid DESC LIMIT 200`);
1289
- return rows.map((row) => this.view(row));
1290
- }
1291
- /** one hold with its body and the judge's redactions while it waits */
1292
- show(id) {
1293
- const row = this.row(id);
1294
- const params = JSON.parse(row.params);
1295
- const verdict = JSON.parse(row.verdict);
1296
- return { ...this.view(row), ...typeof params.body === "string" ? { body: params.body } : {}, redactions: verdict.redactions };
1297
- }
1298
- /** delivers a held message as its sender, as stored or redacted: scope, policy and limits are checked again by the send itself */
1299
- async release(id, options, deliver) {
1300
- const row = one(
1301
- this.o.sql,
1302
- `UPDATE judge_holds SET state = 'releasing' WHERE id = ? AND state = 'held' AND expires_at > ? RETURNING *`,
1303
- id,
1304
- this.o.now()
1305
- );
1306
- if (!row) throw new RelayError3("not_found", `no held message ${id}`);
1307
- const params = JSON.parse(row.params);
1308
- const verdict = JSON.parse(row.verdict);
1309
- let sent = params;
1310
- let redacted;
1311
- if (options.redacted) {
1312
- const applied = applyRedactions(params.body, verdict.redactions);
1313
- if (!applied.n) {
1314
- this.o.sql.all(`UPDATE judge_holds SET state = 'held' WHERE id = ?`, id);
1315
- throw new RelayError3("invalid_params", `hold ${id} has no redactions: release it as it is or refuse it`);
1316
- }
1317
- sent = { ...params, body: applied.text };
1318
- redacted = { n: applied.n, kinds: applied.kinds };
1319
- }
1320
- let result;
1321
- try {
1322
- result = await deliver(row.from_ep, sent, JSON.parse(row.origin));
1323
- } catch (error) {
1324
- this.o.sql.all(`UPDATE judge_holds SET state = 'held' WHERE id = ?`, id);
1325
- throw error;
1326
- }
1327
- this.remember(row.from_ep, params, { ...result, ...redacted ? { redacted } : {} });
1328
- this.resolve(row, "released", options.by);
1329
- this.o.core.auditJudge("judge_override", row.from_ep, id, {
1330
- ...this.storedDetail(row, verdict),
1331
- by: options.by,
1332
- action: redacted ? "release_redacted" : "release",
1333
- ...redacted ? { redacted } : {}
1334
- });
1335
- this.tell(
1336
- row.from_ep,
1337
- `relay: your message held for review as ${id} was released by your human${redacted ? " with redactions" : ""} and delivered as ${result.id}`
1338
- );
1339
- return { ...result, ...redacted ? { redacted } : {} };
1340
- }
1341
- /** refuses a held message: it is never delivered, and its sender hears the human's reason */
1342
- refuse(id, reason, by) {
1343
- this.expire(this.o.now());
1344
- const row = one(this.o.sql, `SELECT * FROM judge_holds WHERE id = ? AND state = 'held'`, id);
1345
- if (!row) throw new RelayError3("not_found", `no held message ${id}`);
1346
- const verdict = JSON.parse(row.verdict);
1347
- this.resolve(row, "refused", by);
1348
- this.o.core.auditJudge("judge_override", row.from_ep, id, { ...this.storedDetail(row, verdict), by, action: "refuse", reason });
1349
- this.tell(row.from_ep, `relay: your message held for review as ${id} was refused by your human: ${reason}`);
1350
- return this.view(this.row(id));
1351
- }
1352
- /** expires holds past their message TTL (body purged, sender told) and drops resolved ones past the audit window */
1353
- sweep() {
1354
- const now = this.o.now();
1355
- this.expire(now);
1356
- this.o.sql.all(`DELETE FROM judge_holds WHERE state IN ('released', 'refused', 'expired') AND resolved_at <= ?`, now - LIMITS.auditKeepS * 1e3);
1357
- this.o.sql.all(`DELETE FROM judge_sent WHERE created_at <= ?`, now - LIMITS.idempotencyS * 1e3);
1358
- for (const [key, times] of this.spent) if (!times.length || times[times.length - 1] <= now - 36e5) this.spent.delete(key);
1359
- }
1360
- expire(now) {
1361
- for (const row of this.o.sql.all(`SELECT * FROM judge_holds WHERE state = 'held' AND expires_at <= ?`, now)) {
1362
- const verdict = JSON.parse(row.verdict);
1363
- this.resolve(row, "expired", null);
1364
- this.o.core.auditJudge("judge_expire", row.from_ep, row.id, this.storedDetail(row, verdict));
1365
- this.tell(row.from_ep, `relay: your message held for review as ${row.id} expired without review and was not delivered`);
1366
- }
1367
- }
1368
- /** the recipients by the boundary their message crosses; throws as send does for a target the sender cannot reach */
1369
- boundaries(policy, from, params, origin) {
1370
- const { sender, recipients, tree } = this.o.core.judgeSubjects(from, params, origin);
1371
- const groups = /* @__PURE__ */ new Map();
1372
- for (const recipient of recipients) {
1373
- const label = judgeBoundary(policy, sender, recipient);
1374
- if (label !== null) groups.set(label, [...groups.get(label) ?? [], recipient.address]);
1375
- }
1376
- return { groups, tree };
1377
- }
1378
- async judge(policy, from, body, groups) {
1379
- const verdicts = await Promise.all(
1380
- [...groups].map(
1381
- ([boundary, to]) => runJudge({
1382
- body,
1383
- side: "local",
1384
- boundary,
1385
- configVersion: this.version,
1386
- client: this.client,
1387
- cache: this.cache,
1388
- timeoutMs: policy.timeout_s * 1e3,
1389
- failMode: policy.fail,
1390
- ...this.client && policy.model ? { model: policy.model.id } : {},
1391
- from: this.o.core.addressOf(from),
1392
- to,
1393
- now: this.o.now,
1394
- stats: this.stats
1395
- })
1396
- )
1397
- );
1398
- return mergeVerdicts(verdicts);
1399
- }
1400
- shadow(policy, from, params, origin, ref) {
1401
- const task = (async () => {
1402
- try {
1403
- const { groups, tree } = this.boundaries(policy, from, params, origin);
1404
- if (!groups.size) return;
1405
- this.spend(policy, from, tree);
1406
- const body = params.body;
1407
- const verdict = await this.judge(policy, from, body, groups);
1408
- this.o.core.auditJudge("judge_shadow", from, ref, this.detail(verdict, body, groups));
1409
- } catch (error) {
1410
- this.o.log(`relay broker: a shadow judgement failed: ${error instanceof Error ? error.message : String(error)}`);
1411
- }
1412
- })();
1413
- this.shadows.add(task);
1414
- void task.finally(() => this.shadows.delete(task));
1415
- }
1416
- hold(from, params, origin, verdict, groups, detail) {
1417
- const now = this.o.now();
1418
- const id = newId2("hold", now);
1419
- const stored = { ...params, client_id: params.client_id ?? id };
1420
- const to = [...groups.values()].flat();
1421
- const record = { ...verdict, boundary: [...groups.keys()].join(" | "), to };
1422
- this.o.sql.all(
1423
- `INSERT INTO judge_holds (id, from_ep, params, origin, verdict, created_at, expires_at, state) VALUES (?, ?, ?, ?, ?, ?, ?, 'held')`,
1424
- id,
1425
- from,
1426
- JSON.stringify(stored),
1427
- JSON.stringify(origin),
1428
- JSON.stringify(record),
1429
- now,
1430
- now + (params.ttl_s ?? LIMITS.ttlDefaultS) * 1e3
1431
- );
1432
- this.o.core.auditJudge("judge_hold", from, id, detail);
1433
- return heldResult(id, params);
1434
- }
1435
- detail(verdict, body, groups) {
1436
- return {
1437
- side: "local",
1438
- boundary: [...groups.keys()].join(" | "),
1439
- prompt_version: verdict.prompt_version,
1440
- categories: verdict.categories,
1441
- sha256: sha256Hex(body),
1442
- verdict: verdict.verdict,
1443
- source: verdict.source,
1444
- reason: verdict.reason,
1445
- to: [...groups.values()].flat(),
1446
- ...verdict.model ? { model: verdict.model } : {},
1447
- ...verdict.ms !== void 0 ? { ms: verdict.ms } : {}
1448
- };
1449
- }
1450
- storedDetail(row, verdict) {
1451
- const params = JSON.parse(row.params);
1452
- return {
1453
- side: "local",
1454
- boundary: verdict.boundary,
1455
- prompt_version: verdict.prompt_version,
1456
- categories: verdict.categories,
1457
- sha256: params.sha256 ?? sha256Hex(params.body ?? ""),
1458
- verdict: verdict.verdict,
1459
- to: verdict.to,
1460
- ...verdict.model ? { model: verdict.model } : {}
1461
- };
1462
- }
1463
- /** marks a hold resolved and purges its body and the judge's quotes of it */
1464
- resolve(row, state, by) {
1465
- const { body, ...params } = JSON.parse(row.params);
1466
- const verdict = JSON.parse(row.verdict);
1467
- const purged = { ...params, bytes: new TextEncoder().encode(body).length, sha256: sha256Hex(body) };
1468
- const kinds = { ...verdict, redactions: verdict.redactions.map((r) => ({ kind: r.kind })) };
1469
- this.o.sql.all(
1470
- `UPDATE judge_holds SET state = ?, by = ?, resolved_at = ?, params = ?, verdict = ? WHERE id = ?`,
1471
- state,
1472
- by,
1473
- this.o.now(),
1474
- JSON.stringify(purged),
1475
- JSON.stringify(kinds),
1476
- row.id
1477
- );
1478
- }
1479
- tell(endpoint, text) {
1480
- try {
1481
- this.o.core.judgeNotice(endpoint, text);
1482
- this.o.wake(endpoint);
1483
- } catch (error) {
1484
- if (!(error instanceof RelayError3)) throw error;
1485
- }
1486
- }
1487
- row(id) {
1488
- const row = one(this.o.sql, `SELECT * FROM judge_holds WHERE id = ?`, id);
1489
- if (!row) throw new RelayError3("not_found", `no held message ${id}`);
1490
- return row;
1076
+ import { JudgeGate as HubJudgeGate } from "@scp-dev/hub";
1077
+ function readKeyFile(file) {
1078
+ try {
1079
+ accessSync(file, constants.R_OK);
1080
+ } catch {
1081
+ return null;
1491
1082
  }
1492
- view(row) {
1493
- const params = JSON.parse(row.params);
1494
- const verdict = JSON.parse(row.verdict);
1495
- return {
1496
- id: row.id,
1497
- from: this.o.core.addressOf(row.from_ep),
1498
- to: verdict.to,
1499
- state: row.state,
1500
- created_at: iso(row.created_at),
1501
- expires_at: iso(row.expires_at),
1502
- categories: verdict.categories,
1503
- reason: verdict.reason,
1504
- boundary: verdict.boundary,
1505
- source: verdict.source,
1506
- bytes: params.bytes ?? new TextEncoder().encode(params.body ?? "").length,
1507
- by: row.by,
1508
- resolved_at: iso(row.resolved_at)
1509
- };
1083
+ return async () => readFileSync4(file, "utf8").trim();
1084
+ }
1085
+ var JudgeGate = class extends HubJudgeGate {
1086
+ constructor(options) {
1087
+ super({ keyFile: readKeyFile, ...options });
1510
1088
  }
1511
1089
  };
1512
- var fingerprint = (p) => sha256Hex(
1513
- JSON.stringify([
1514
- [p.to].flat().map(String).sort(),
1515
- p.body,
1516
- p.kind ?? "message",
1517
- p.thread ?? null,
1518
- p.reply_to ?? null,
1519
- p.data_class ?? "public",
1520
- (p.attachments ?? []).map((a) => a?.path).sort(),
1521
- p.mime ?? null,
1522
- p.ttl_s ?? LIMITS.ttlDefaultS,
1523
- p.require_reply ?? null
1524
- ])
1525
- );
1526
- function heldResult(id, params) {
1527
- return { id, thread: params.thread ?? "", hop: 0, deliveries: [], held: { hold_id: id, side: "local" } };
1528
- }
1529
1090
 
1530
1091
  // src/sessions.ts
1531
1092
  import { createHash as createHash2, randomUUID } from "crypto";
@@ -1650,7 +1211,19 @@ var SCOPE_METHODS = /* @__PURE__ */ new Set([
1650
1211
  var CHANGES_SCOPE = /* @__PURE__ */ new Set(["rules_set", "rules_widen", "rules_lift", "group"]);
1651
1212
  var ASK_RECHECK_MS = 1e3;
1652
1213
  var STOP_RECHECK_MS = 1e3;
1653
- var TRANSIENT_METHODS = /* @__PURE__ */ new Set(["event", "status", "whoami", "inject", "injected", "peek_turn", "stop_wait", "prompted", "title_infer"]);
1214
+ var TRANSIENT_METHODS = /* @__PURE__ */ new Set([
1215
+ "event",
1216
+ "status",
1217
+ "whoami",
1218
+ "inject",
1219
+ "injected",
1220
+ "peek_turn",
1221
+ "stop_wait",
1222
+ "prompted",
1223
+ "title_infer",
1224
+ "subagent_title",
1225
+ "wake"
1226
+ ]);
1654
1227
  var GRANT_UNBOUND = `a spawn-env grant binds only a new harness process: run the harness with it, or name the child (${CLI_NAME} spawn-env --name / --as)`;
1655
1228
  function workerResults(result) {
1656
1229
  return result.messages.length - (result.others_shown ?? 0) + result.more;
@@ -1773,21 +1346,17 @@ async function startBroker(options) {
1773
1346
  }
1774
1347
  for (const id of known.keys()) if (!live.has(id)) known.delete(id);
1775
1348
  }, options.presenceEveryMs ?? PRESENCE_MS);
1776
- if (options.bridge) {
1777
- bridge = startBridge({
1778
- ...options.bridge,
1779
- core,
1780
- onRemote: {
1781
- wake,
1782
- fleet: notifyFleet,
1783
- policyError: () => {
1784
- loadPolicy();
1785
- return policyError;
1786
- },
1787
- log
1788
- }
1789
- });
1790
- }
1349
+ const remoteHooks = {
1350
+ wake,
1351
+ fleet: notifyFleet,
1352
+ policyError: () => {
1353
+ loadPolicy();
1354
+ return policyError;
1355
+ },
1356
+ log
1357
+ };
1358
+ if (options.bridge) bridge = startBridge({ ...options.bridge, core, onRemote: remoteHooks });
1359
+ else if (options.link) bridge = options.link({ core, hooks: remoteHooks, send: (from, params, origin) => judge.send(from, params, origin, deliver) });
1791
1360
  async function relink() {
1792
1361
  const config = options.bridgeConfig ? await options.bridgeConfig() : options.bridge;
1793
1362
  if (!config || bridge && config.url === options.bridge?.url && config.machine === options.bridge?.machine) {
@@ -1943,7 +1512,7 @@ async function startBroker(options) {
1943
1512
  endpoint: core.whoami(endpoint),
1944
1513
  capabilities: BROKER_CAPABILITIES,
1945
1514
  server_time: (/* @__PURE__ */ new Date()).toISOString(),
1946
- limits: LIMITS2,
1515
+ limits: LIMITS,
1947
1516
  preamble: PREAMBLE,
1948
1517
  broker: brokerInfo()
1949
1518
  };
@@ -1973,16 +1542,16 @@ async function startBroker(options) {
1973
1542
  }
1974
1543
  async function gather(me, result, peek, s) {
1975
1544
  const gathering = (r) => workerResults(r) > 0 && !r.more && !r.prompted && othersRunning(r) > 0;
1976
- const cap = Date.now() + LIMITS2.pushGraceS * 1e3;
1977
- let quiet = Date.now() + LIMITS2.pushQuietS * 1e3;
1545
+ const cap = Date.now() + LIMITS.pushGraceS * 1e3;
1546
+ let quiet = Date.now() + LIMITS.pushQuietS * 1e3;
1978
1547
  let results = workerResults(result);
1979
1548
  while (gathering(result) && !s.data.closed && Date.now() < Math.min(quiet, cap)) {
1980
1549
  await waitFor(me, Math.min(quiet, cap) - Date.now(), s);
1981
- if (closing) throw new RelayError4("unauthorized", "the relay broker is stopping");
1550
+ if (closing) throw new RelayError3("unauthorized", "the relay broker is stopping");
1982
1551
  result = core.stopWait(me, peek);
1983
1552
  if (workerResults(result) > results) {
1984
1553
  results = workerResults(result);
1985
- quiet = Date.now() + LIMITS2.pushQuietS * 1e3;
1554
+ quiet = Date.now() + LIMITS.pushQuietS * 1e3;
1986
1555
  }
1987
1556
  }
1988
1557
  }
@@ -2076,7 +1645,7 @@ async function startBroker(options) {
2076
1645
  if (hasId) write(s, { jsonrpc: "2.0", id, result });
2077
1646
  } catch (error) {
2078
1647
  if (!hasId || closing) return;
2079
- if (error instanceof RelayError4) {
1648
+ if (error instanceof RelayError3) {
2080
1649
  const code = error.code === "method_not_found" ? -32601 : error.code === "invalid_params" ? -32602 : -32e3;
2081
1650
  write(s, {
2082
1651
  jsonrpc: "2.0",
@@ -2097,64 +1666,70 @@ async function startBroker(options) {
2097
1666
  }
2098
1667
  }
2099
1668
  function endpointOf(s) {
2100
- if (closing) throw new RelayError4("unauthorized", "the relay broker is stopping");
1669
+ if (closing) throw new RelayError3("unauthorized", "the relay broker is stopping");
2101
1670
  if (!s.data.endpoint)
2102
- throw new RelayError4("unauthorized", s.data.observer ? "this connection only observes: say hello as an agent or the human" : "call hello first");
1671
+ throw new RelayError3("unauthorized", s.data.observer ? "this connection only observes: say hello as an agent or the human" : "call hello first");
2103
1672
  core.touch(s.data.endpoint);
2104
1673
  return s.data.endpoint;
2105
1674
  }
2106
1675
  function waitSeconds(value) {
2107
1676
  if (value === void 0) return 0;
2108
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0) throw new RelayError4("invalid_params", "wait_s must be a number of seconds");
2109
- return Math.min(value, LIMITS2.waitMaxS) * 1e3;
1677
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) throw new RelayError3("invalid_params", "wait_s must be a number of seconds");
1678
+ return Math.min(value, LIMITS.waitMaxS) * 1e3;
2110
1679
  }
2111
1680
  function checkAttachments(attachments, s) {
2112
1681
  if (attachments === void 0) return void 0;
2113
- if (!Array.isArray(attachments)) throw new RelayError4("invalid_params", "attachments must be a list");
1682
+ if (!Array.isArray(attachments)) throw new RelayError3("invalid_params", "attachments must be a list");
2114
1683
  const tooWide = /* @__PURE__ */ new Set(["/", realOrSelf(homedir2()), realOrSelf(accountHome())]);
2115
1684
  const roots = [s.data.cwd, ...options.roots ?? []].filter((r) => typeof r === "string" && isAbsolute(r)).map(realOrSelf).filter((r) => !tooWide.has(r));
2116
1685
  return attachments.map((attachment) => {
2117
1686
  if (attachment?.kind !== "path" || typeof attachment.path !== "string" || !isAbsolute(attachment.path)) {
2118
- throw new RelayError4("invalid_params", "an attachment is {kind: path, path: an absolute path}");
1687
+ throw new RelayError3("invalid_params", "an attachment is {kind: path, path: an absolute path}");
2119
1688
  }
2120
1689
  let real;
2121
1690
  try {
2122
1691
  real = realpathSync(attachment.path);
2123
1692
  } catch {
2124
- throw new RelayError4("not_found", `no file at ${attachment.path}`);
1693
+ throw new RelayError3("not_found", `no file at ${attachment.path}`);
2125
1694
  }
2126
1695
  if (!roots.some((root) => real === root || real.startsWith(root.endsWith(sep) ? root : root + sep))) {
2127
- throw new RelayError4("invalid_params", `${attachment.path} is outside the sender's working directory and the relay's allowed roots`);
1696
+ throw new RelayError3("invalid_params", `${attachment.path} is outside the sender's working directory and the relay's allowed roots`);
2128
1697
  }
2129
1698
  return { kind: "path", path: real };
2130
1699
  });
2131
1700
  }
2132
1701
  async function dispatch(s, method, params) {
2133
1702
  if (method === "hello") {
2134
- if (s.data.endpoint || s.data.observer) throw new RelayError4("invalid_params", "hello was already called on this connection");
2135
- if (params.protocol !== PROTOCOL3) throw new RelayError4("invalid_params", `this broker speaks ${PROTOCOL3}`);
1703
+ if (s.data.endpoint || s.data.observer) throw new RelayError3("invalid_params", "hello was already called on this connection");
1704
+ if (params.protocol !== PROTOCOL3) throw new RelayError3("invalid_params", `this broker speaks ${PROTOCOL3}`);
2136
1705
  if (params.transient === true) {
2137
1706
  if (params.human === true || params.observe === true || params.resume !== void 0) {
2138
- throw new RelayError4("invalid_params", "a hook connection claims an agent endpoint: transient does not go with human, observe or resume");
1707
+ throw new RelayError3("invalid_params", "a hook connection claims an agent endpoint: transient does not go with human, observe or resume");
2139
1708
  }
2140
1709
  const join5 = params.join;
2141
1710
  if (join5 !== void 0 && (typeof join5 !== "object" || join5 === null || Array.isArray(join5)))
2142
- throw new RelayError4("invalid_params", "join is {handle?}");
1711
+ throw new RelayError3("invalid_params", "join is {handle?, under?}");
1712
+ const under = join5?.under;
2143
1713
  const before2 = new Map(core.list({ all: true }).map((view) => [view.id, view]));
2144
- const { endpoint: endpoint2, subagent, created } = join5 === void 0 ? core.claim(params.claim ?? {}, { transient: true }) : { endpoint: core.joinSession(params.claim ?? {}, join5.handle), subagent: void 0, created: void 0 };
1714
+ const { endpoint: endpoint2, subagent, created } = join5 === void 0 ? core.claim(params.claim ?? {}, { transient: true }) : {
1715
+ endpoint: core.joinSession(params.claim ?? {}, join5.handle, under),
1716
+ subagent: void 0,
1717
+ created: void 0
1718
+ };
2145
1719
  s.data.endpoint = endpoint2.id;
2146
1720
  s.data.transient = true;
2147
1721
  const key = params.claim?.process;
2148
1722
  if (typeof key === "string") s.data.process = key;
2149
1723
  const was2 = before2.get(endpoint2.id)?.address;
2150
1724
  if (join5 !== void 0) membership(endpoint2, core.sessionOf(endpoint2.id), true, was2?.slice(was2.lastIndexOf("/") + 1));
1725
+ if (under !== void 0 && before2.get(endpoint2.id)?.parent !== endpoint2.parent) bridge?.resync();
2151
1726
  else if ((before2.get(endpoint2.id)?.presence ?? "offline") !== endpoint2.presence) presenceChanged(endpoint2);
2152
1727
  return {
2153
1728
  endpoint: endpoint2,
2154
1729
  ...subagent ? { subagent, ...created ? { created } : {} } : {},
2155
1730
  capabilities: BROKER_CAPABILITIES,
2156
1731
  server_time: (/* @__PURE__ */ new Date()).toISOString(),
2157
- limits: LIMITS2,
1732
+ limits: LIMITS,
2158
1733
  preamble: PREAMBLE,
2159
1734
  broker: brokerInfo()
2160
1735
  };
@@ -2162,7 +1737,7 @@ async function startBroker(options) {
2162
1737
  if (params.observe === true) {
2163
1738
  const self = params.self === void 0 ? null : core.placeLineage(params.self);
2164
1739
  if (params.grant !== void 0) {
2165
- if (!core.grantBinds(params.grant, self)) throw new RelayError4("unauthorized", GRANT_UNBOUND);
1740
+ if (!core.grantBinds(params.grant, self)) throw new RelayError3("unauthorized", GRANT_UNBOUND);
2166
1741
  return { ...bindLineage(s, self, params, true), grant_bound: true };
2167
1742
  }
2168
1743
  if (self) return bindLineage(s, self, params, true);
@@ -2172,7 +1747,7 @@ async function startBroker(options) {
2172
1747
  endpoint: null,
2173
1748
  capabilities: BROKER_CAPABILITIES,
2174
1749
  server_time: (/* @__PURE__ */ new Date()).toISOString(),
2175
- limits: LIMITS2,
1750
+ limits: LIMITS,
2176
1751
  preamble: PREAMBLE,
2177
1752
  broker: brokerInfo()
2178
1753
  };
@@ -2180,7 +1755,10 @@ async function startBroker(options) {
2180
1755
  const claim = params.claim;
2181
1756
  if (params.human === true && params.resume === void 0 && params.lineage !== void 0) {
2182
1757
  const placed = core.placeLineage(params.lineage);
2183
- if (placed) return bindLineage(s, placed, params);
1758
+ if (placed) {
1759
+ s.data.terminal = params.tty === true;
1760
+ return bindLineage(s, placed, params);
1761
+ }
2184
1762
  } else if (params.human !== true && params.resume === void 0 && claim?.process === void 0 && typeof claim?.handle === "string") {
2185
1763
  const placed = claim.lineage === void 0 ? null : core.placeLineage(claim.lineage, claim.handle);
2186
1764
  if (placed && (claim.grant === void 0 || core.grantBinds(claim.grant, placed))) {
@@ -2215,17 +1793,17 @@ async function startBroker(options) {
2215
1793
  ...token ? { token } : {},
2216
1794
  capabilities: BROKER_CAPABILITIES,
2217
1795
  server_time: (/* @__PURE__ */ new Date()).toISOString(),
2218
- limits: LIMITS2,
1796
+ limits: LIMITS,
2219
1797
  preamble: PREAMBLE,
2220
1798
  broker: brokerInfo()
2221
1799
  };
2222
1800
  }
2223
- if (closing) throw new RelayError4("unauthorized", "the relay broker is stopping");
1801
+ if (closing) throw new RelayError3("unauthorized", "the relay broker is stopping");
2224
1802
  if (s.data.observer && OBSERVER_METHODS.has(method)) return observe(s, method, params);
2225
1803
  if (s.data.observer && SCOPE_METHODS.has(method) && (s.data.lineage || method === "why")) return scope(s, method, params);
2226
- if (s.data.transient && SCOPE_METHODS.has(method)) throw new RelayError4("scope_refused", "a hook connection cannot read or change scopes, groups or grants");
1804
+ if (s.data.transient && SCOPE_METHODS.has(method)) throw new RelayError3("scope_refused", "a hook connection cannot read or change scopes, groups or grants");
2227
1805
  const me = endpointOf(s);
2228
- if (s.data.transient && !TRANSIENT_METHODS.has(method)) throw new RelayError4("unauthorized", "a hook connection only posts activity and status");
1806
+ if (s.data.transient && !TRANSIENT_METHODS.has(method)) throw new RelayError3("unauthorized", "a hook connection only posts activity and status");
2229
1807
  if (OBSERVER_METHODS.has(method)) return observe(s, method, params);
2230
1808
  if (SCOPE_METHODS.has(method)) return scope(s, method, params);
2231
1809
  switch (method) {
@@ -2233,13 +1811,13 @@ async function startBroker(options) {
2233
1811
  return s.data.transient ? core.whoami(me) : { ...core.whoami(me), ...core.peekState(me) };
2234
1812
  case "peek_turn":
2235
1813
  if (!s.data.transient || !core.hookOwns(me, s.data.process))
2236
- throw new RelayError4("unauthorized", "only a hook of the endpoint's own harness process posts digest turns");
1814
+ throw new RelayError3("unauthorized", "only a hook of the endpoint's own harness process posts digest turns");
2237
1815
  return core.peekTurn(me, { role: params.role, text: params.text });
2238
1816
  case "send":
2239
1817
  case "ask": {
2240
- for (const field of SERVER_FIELDS) if (field in params) throw new RelayError4("invalid_params", `${field} is set by the relay, not the sender`);
1818
+ for (const field of SERVER_FIELDS) if (field in params) throw new RelayError3("invalid_params", `${field} is set by the relay, not the sender`);
2241
1819
  loadPolicy();
2242
- if (policyError) throw new RelayError4("policy_refused", policyError);
1820
+ if (policyError) throw new RelayError3("policy_refused", policyError);
2243
1821
  const attachments = checkAttachments(params.attachments, s);
2244
1822
  const send = { ...params, ...attachments ? { attachments } : {} };
2245
1823
  if (method === "ask") send.kind = "request";
@@ -2255,16 +1833,16 @@ async function startBroker(options) {
2255
1833
  case "hold_release": {
2256
1834
  const id = required(reviewer(s, params), "id");
2257
1835
  loadPolicy();
2258
- if (policyError) throw new RelayError4("policy_refused", policyError);
1836
+ if (policyError) throw new RelayError3("policy_refused", policyError);
2259
1837
  return judge.release(id, { redacted: params.redacted === true, by: core.addressOf(me) }, deliver);
2260
1838
  }
2261
1839
  case "hold_refuse": {
2262
1840
  const reason = required(reviewer(s, params), "reason");
2263
- if (reason.length > LIMITS2.noteChars) throw new RelayError4("invalid_params", `reason takes at most ${LIMITS2.noteChars} characters`);
1841
+ if (reason.length > LIMITS.noteChars) throw new RelayError3("invalid_params", `reason takes at most ${LIMITS.noteChars} characters`);
2264
1842
  return judge.refuse(required(params, "id"), stripControl(reason), core.addressOf(me));
2265
1843
  }
2266
1844
  case "ask_wait": {
2267
- if (typeof params.id !== "string") throw new RelayError4("invalid_params", "id is the request's message id");
1845
+ if (typeof params.id !== "string") throw new RelayError3("invalid_params", "id is the request's message id");
2268
1846
  return awaitReply(s, me, params.id, waitSeconds(params.wait_s));
2269
1847
  }
2270
1848
  case "inbox": {
@@ -2274,7 +1852,7 @@ async function startBroker(options) {
2274
1852
  let page = read();
2275
1853
  while (!page.messages.length && !s.data.closed && Date.now() < deadline) {
2276
1854
  await waitFor(me, deadline - Date.now(), s);
2277
- if (closing) throw new RelayError4("unauthorized", "the relay broker is stopping");
1855
+ if (closing) throw new RelayError3("unauthorized", "the relay broker is stopping");
2278
1856
  page = read();
2279
1857
  }
2280
1858
  const { messages } = page;
@@ -2292,11 +1870,37 @@ async function startBroker(options) {
2292
1870
  if (changed) bridge?.changed(me);
2293
1871
  return { changed };
2294
1872
  }
1873
+ case "subagent_title": {
1874
+ const changed = core.subagentTitle(me, params, hookOf(s));
1875
+ if (changed) bridge?.changed(changed);
1876
+ return { changed: changed !== null };
1877
+ }
1878
+ case "wake": {
1879
+ if (s.data.lineage && !s.data.own) throw new RelayError3("unauthorized", "only the endpoint's own session says how it wakes");
1880
+ const changed = core.setWake(me, params.via, hookOf(s));
1881
+ if (changed) bridge?.changed(me);
1882
+ return { changed };
1883
+ }
2295
1884
  case "rename": {
2296
1885
  const renamed = core.rename(actorOf(s), { ...params.endpoint !== void 0 ? { endpoint: params.endpoint } : {}, handle: params.handle });
2297
1886
  membership(renamed.endpoint, renamed.session, true, renamed.from);
2298
1887
  return renamed.endpoint;
2299
1888
  }
1889
+ case "adopt": {
1890
+ const actor = actorOf(s);
1891
+ const adopted = core.adopt(
1892
+ actor,
1893
+ { endpoint: params.endpoint, ...params.parent !== void 0 ? { parent: params.parent } : {} },
1894
+ { tty: actor.kind === "endpoint" && s.data.terminal === true }
1895
+ );
1896
+ bridge?.resync();
1897
+ return adopted;
1898
+ }
1899
+ case "unlink": {
1900
+ const unlinked = core.unlink(actorOf(s), params.endpoint !== void 0 ? { endpoint: params.endpoint } : {});
1901
+ bridge?.resync();
1902
+ return unlinked;
1903
+ }
2300
1904
  case "leave": {
2301
1905
  const left = core.leaveSession(actorOf(s), params.endpoint !== void 0 ? { endpoint: params.endpoint } : {});
2302
1906
  for (const c of connections) if (c.data.endpoint === left.endpoint.id && !c.data.transient && !c.data.lineage && c !== s) c.end();
@@ -2332,7 +1936,7 @@ async function startBroker(options) {
2332
1936
  let result = core.inject(me, options2);
2333
1937
  while (!result.offered.length && !s.data.closed && Date.now() < deadline) {
2334
1938
  await waitFor(me, deadline - Date.now(), s);
2335
- if (closing) throw new RelayError4("unauthorized", "the relay broker is stopping");
1939
+ if (closing) throw new RelayError3("unauthorized", "the relay broker is stopping");
2336
1940
  result = core.inject(me, options2);
2337
1941
  }
2338
1942
  return result;
@@ -2346,7 +1950,7 @@ async function startBroker(options) {
2346
1950
  const idle = (r) => !r.messages.length && !r.more && r.workers.length + r.starting > 0 && r.pushes_left > 0 && !r.prompted;
2347
1951
  while (idle(result) && !s.data.closed && Date.now() < deadline) {
2348
1952
  await waitFor(me, Math.min(STOP_RECHECK_MS, deadline - Date.now()), s);
2349
- if (closing) throw new RelayError4("unauthorized", "the relay broker is stopping");
1953
+ if (closing) throw new RelayError3("unauthorized", "the relay broker is stopping");
2350
1954
  result = core.stopWait(me, peek);
2351
1955
  }
2352
1956
  if (waitMs > 0) await gather(me, result, peek, s);
@@ -2373,13 +1977,13 @@ async function startBroker(options) {
2373
1977
  case "receipts":
2374
1978
  return core.receipts(me, params.id);
2375
1979
  }
2376
- throw new RelayError4("method_not_found", `no method ${method}`);
1980
+ throw new RelayError3("method_not_found", `no method ${method}`);
2377
1981
  }
2378
1982
  const deliver = async (from, send, origin) => {
2379
1983
  let result;
2380
1984
  if (remoteSend(core, send.to)) {
2381
1985
  if (!bridge)
2382
- throw new RelayError4("not_found", `no endpoint ${[send.to].flat().join(", ")}: this broker is not linked to a hub (${CLI_NAME} cloud enable)`);
1986
+ throw new RelayError3("not_found", `no endpoint ${[send.to].flat().join(", ")}: this broker is not linked to a hub (${CLI_NAME} cloud enable)`);
2383
1987
  result = await bridge.forward(from, send, origin);
2384
1988
  } else {
2385
1989
  result = core.send(from, send, origin);
@@ -2399,7 +2003,7 @@ async function startBroker(options) {
2399
2003
  return result;
2400
2004
  };
2401
2005
  function reviewer(s, params) {
2402
- if (!atTerminal(s)) throw new RelayError4("unauthorized", "only your human at a terminal reviews held messages");
2006
+ if (!atTerminal(s)) throw new RelayError3("unauthorized", "only your human at a terminal reviews held messages");
2403
2007
  return params;
2404
2008
  }
2405
2009
  function observe(s, method, params) {
@@ -2407,7 +2011,7 @@ async function startBroker(options) {
2407
2011
  switch (method) {
2408
2012
  case "list":
2409
2013
  if (params.contacts === true) {
2410
- if (!bridge) throw new RelayError4("unavailable", `this broker is not linked to a hub (${CLI_NAME} cloud enable), so it reaches no contacts`);
2014
+ if (!bridge) throw new RelayError3("unavailable", `this broker is not linked to a hub (${CLI_NAME} cloud enable), so it reaches no contacts`);
2411
2015
  return bridge.contacts().then((contacts) => ({ contacts }));
2412
2016
  }
2413
2017
  return { endpoints: core.list({ ...params, viewer, peekable: viewer === void 0 }) };
@@ -2448,7 +2052,7 @@ async function startBroker(options) {
2448
2052
  case "bridge_relink":
2449
2053
  return relink();
2450
2054
  }
2451
- throw new RelayError4("method_not_found", `no method ${method}`);
2055
+ throw new RelayError3("method_not_found", `no method ${method}`);
2452
2056
  }
2453
2057
  function scope(s, method, params) {
2454
2058
  const result = scopeCall(s, method, params);
@@ -2527,7 +2131,7 @@ async function startBroker(options) {
2527
2131
  return result;
2528
2132
  }
2529
2133
  }
2530
- throw new RelayError4("method_not_found", `no method ${method}`);
2134
+ throw new RelayError3("method_not_found", `no method ${method}`);
2531
2135
  }
2532
2136
  function realOrSelf(path) {
2533
2137
  try {
@@ -2540,7 +2144,7 @@ async function startBroker(options) {
2540
2144
  const deadline = Date.now() + ms;
2541
2145
  const present = sent?.deliveries.filter((d) => !d.offline).map((d) => d.endpoint);
2542
2146
  for (; ; ) {
2543
- if (closing) throw new RelayError4("unauthorized", "the relay broker is stopping");
2147
+ if (closing) throw new RelayError3("unauthorized", "the relay broker is stopping");
2544
2148
  const reply = core.replyTo(me, request);
2545
2149
  if (reply) {
2546
2150
  core.ack(me, [reply.seq]);
@@ -2592,18 +2196,18 @@ function compact(fields) {
2592
2196
  function optional(params, key) {
2593
2197
  const value = params[key];
2594
2198
  if (value === void 0 || value === null) return void 0;
2595
- if (typeof value !== "string") throw new RelayError4("invalid_params", `${key} must be a string`);
2199
+ if (typeof value !== "string") throw new RelayError3("invalid_params", `${key} must be a string`);
2596
2200
  return value;
2597
2201
  }
2598
2202
  function required(params, key) {
2599
2203
  const value = optional(params, key);
2600
- if (!value) throw new RelayError4("invalid_params", `${key} is required`);
2204
+ if (!value) throw new RelayError3("invalid_params", `${key} is required`);
2601
2205
  return value;
2602
2206
  }
2603
2207
  function selectors(params, key) {
2604
2208
  const value = params[key];
2605
2209
  if (value === void 0 || value === null) return void 0;
2606
- if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) throw new RelayError4("invalid_params", `${key} must be a list of selectors`);
2210
+ if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) throw new RelayError3("invalid_params", `${key} must be a list of selectors`);
2607
2211
  return value;
2608
2212
  }
2609
2213
  export {