@securecoms/agent-bridge 0.30.2 → 0.30.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/bin.js +248 -47
- package/dist/bridge-agent.d.ts +60 -0
- package/dist/consumer-grants.d.ts +15 -0
- package/dist/grants/binding.d.ts +30 -2
- package/dist/grants/manifest.d.ts +11 -1
- package/dist/grants/search-providers.d.ts +9 -0
- package/dist/http-server.d.ts +2 -0
- package/dist/index.js +227 -27
- package/dist/preflight-upgrade.d.cts +1 -0
- package/dist/preflight.cjs +1 -0
- package/dist/service/runtime-config-dir.d.ts +4 -0
- package/dist/version.d.ts +1 -1
- package/dist/ws-server.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -29101,7 +29101,7 @@ function loadPackageMeta(importMetaUrl) {
|
|
|
29101
29101
|
}
|
|
29102
29102
|
|
|
29103
29103
|
// src/bridge-agent.ts
|
|
29104
|
-
import { existsSync as existsSync5, promises as fsp, statSync as statSync4 } from "node:fs";
|
|
29104
|
+
import { existsSync as existsSync5, promises as fsp, readFileSync as readFileSync10, statSync as statSync4 } from "node:fs";
|
|
29105
29105
|
import { join as join17, dirname as dirname5 } from "node:path";
|
|
29106
29106
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
29107
29107
|
|
|
@@ -34032,6 +34032,15 @@ import {
|
|
|
34032
34032
|
writeFileSync
|
|
34033
34033
|
} from "node:fs";
|
|
34034
34034
|
import { basename as basename4, isAbsolute, join as join15 } from "node:path";
|
|
34035
|
+
|
|
34036
|
+
// src/grants/search-providers.ts
|
|
34037
|
+
var SEARCH_PROVIDERS = /* @__PURE__ */ new Set(["brave", "tavily", "parallel", "exa", "firecrawl"]);
|
|
34038
|
+
var OWNER_DM_SEARCH_DAILY_CAP_MAX = 2e3;
|
|
34039
|
+
function isSearchProvider(value) {
|
|
34040
|
+
return typeof value === "string" && SEARCH_PROVIDERS.has(value);
|
|
34041
|
+
}
|
|
34042
|
+
|
|
34043
|
+
// src/service/runtime-config-dir.ts
|
|
34035
34044
|
var MEMORY_FILE = "MEMORY.md";
|
|
34036
34045
|
var REAL_FS = {
|
|
34037
34046
|
mkdir: (p, mode) => mkdirSync5(p, { recursive: true, mode }),
|
|
@@ -34214,10 +34223,14 @@ function readLocalReport(home, identityId) {
|
|
|
34214
34223
|
const persona = raw && typeof raw === "object" && !Array.isArray(raw) ? Object.fromEntries(
|
|
34215
34224
|
Object.entries(raw).filter(([, v]) => typeof v === "string")
|
|
34216
34225
|
) : {};
|
|
34226
|
+
const capRaw = doc.owner_dm_search_cap_effective;
|
|
34227
|
+
const ownerDmCap = typeof capRaw === "number" && Number.isInteger(capRaw) && capRaw >= 0 && capRaw <= OWNER_DM_SEARCH_DAILY_CAP_MAX ? capRaw : void 0;
|
|
34217
34228
|
return {
|
|
34218
34229
|
persona,
|
|
34219
34230
|
has_search_key: hasSearchKey,
|
|
34220
34231
|
has_llm_key: hasLlmKey,
|
|
34232
|
+
has_extract_provider: doc.has_extract_provider === true,
|
|
34233
|
+
...ownerDmCap !== void 0 ? { owner_dm_search_cap_effective: ownerDmCap } : {},
|
|
34221
34234
|
legacy_memory: localLegacySources(doc).map(({ path: _path, ...source }) => source),
|
|
34222
34235
|
secret_paths: parseSecretPathsReport(doc.secret_paths)
|
|
34223
34236
|
};
|
|
@@ -34416,7 +34429,8 @@ function loadOwnerGrantPin(opts) {
|
|
|
34416
34429
|
// src/grants/manifest.ts
|
|
34417
34430
|
var TOOL_GRANTS_DOMAIN = "SECURECOMS-TOOL-GRANTS-V1:";
|
|
34418
34431
|
var MANIFEST_KNOWN_TOOLS = Object.freeze({
|
|
34419
|
-
owner_dm: Object.freeze(["web_search", "fetch_url", "http_request", "sql_query"]),
|
|
34432
|
+
owner_dm: Object.freeze(["web_search", "web_extract", "fetch_url", "http_request", "sql_query"]),
|
|
34433
|
+
// web_extract: owner DM only, never team_room (spec §5.4, D5)
|
|
34420
34434
|
team_room: Object.freeze(["web_search"])
|
|
34421
34435
|
});
|
|
34422
34436
|
function manifestSignedBytes(manifest) {
|
|
@@ -34491,7 +34505,11 @@ function parseConfig(v) {
|
|
|
34491
34505
|
}
|
|
34492
34506
|
if ("search" in v) {
|
|
34493
34507
|
const sr = v.search;
|
|
34494
|
-
if (!isPlainObject(sr) || !exactKeys(sr, ["provider"]
|
|
34508
|
+
if (!isPlainObject(sr) || !exactKeys(sr, ["provider"], ["owner_dm_daily_cap"]) || !isSearchProvider(sr.provider)) return null;
|
|
34509
|
+
if ("owner_dm_daily_cap" in sr) {
|
|
34510
|
+
const cap = sr.owner_dm_daily_cap;
|
|
34511
|
+
if (typeof cap !== "number" || !Number.isSafeInteger(cap) || cap < 0 || cap > OWNER_DM_SEARCH_DAILY_CAP_MAX) return null;
|
|
34512
|
+
}
|
|
34495
34513
|
}
|
|
34496
34514
|
const mem = v.memory;
|
|
34497
34515
|
if (!isPlainObject(mem) || !exactKeys(mem, ["mode"], ["recall_days"])) return null;
|
|
@@ -34501,7 +34519,12 @@ function parseConfig(v) {
|
|
|
34501
34519
|
return Object.freeze({
|
|
34502
34520
|
persona_sha256: v.persona_sha256,
|
|
34503
34521
|
...isPlainObject(v.llm) ? { llm: Object.freeze({ provider: v.llm.provider, model: v.llm.model }) } : {},
|
|
34504
|
-
...isPlainObject(v.search) ? {
|
|
34522
|
+
...isPlainObject(v.search) ? {
|
|
34523
|
+
search: Object.freeze({
|
|
34524
|
+
provider: v.search.provider,
|
|
34525
|
+
..."owner_dm_daily_cap" in v.search ? { owner_dm_daily_cap: v.search.owner_dm_daily_cap } : {}
|
|
34526
|
+
})
|
|
34527
|
+
} : {},
|
|
34505
34528
|
...isStringArray(v.tools_allow) ? { tools_allow: Object.freeze([...v.tools_allow]) } : {},
|
|
34506
34529
|
flags: Object.freeze([...v.flags]),
|
|
34507
34530
|
memory: Object.freeze({ mode: mem.mode, ..."recall_days" in mem ? { recall_days: mem.recall_days } : {} }),
|
|
@@ -34732,7 +34755,20 @@ function bindTargets(raw, signed, equals) {
|
|
|
34732
34755
|
}
|
|
34733
34756
|
return { written, mismatches };
|
|
34734
34757
|
}
|
|
34735
|
-
function
|
|
34758
|
+
function signedManifestTools(manifest, audience, searchProvidersCapable) {
|
|
34759
|
+
return MANIFEST_KNOWN_TOOLS[audience].filter((t) => (manifest.grants[audience] ?? []).includes(t) && (t !== "web_extract" || searchProvidersCapable));
|
|
34760
|
+
}
|
|
34761
|
+
function manifestPolicyFor(manifest, switches, searchProvidersCapable) {
|
|
34762
|
+
return {
|
|
34763
|
+
v: 1,
|
|
34764
|
+
manifest_tools: [.../* @__PURE__ */ new Set([
|
|
34765
|
+
...switches.ownerDm ? signedManifestTools(manifest, "owner_dm", searchProvidersCapable) : [],
|
|
34766
|
+
...switches.room ? signedManifestTools(manifest, "team_room", searchProvidersCapable) : []
|
|
34767
|
+
])].sort(),
|
|
34768
|
+
secret_path_mode: "enforce"
|
|
34769
|
+
};
|
|
34770
|
+
}
|
|
34771
|
+
function bindRuntimeConfig(manifest, payload, prior, switches = { ownerDm: true, room: true }, searchProvidersCapable = false) {
|
|
34736
34772
|
const mismatches = /* @__PURE__ */ new Set();
|
|
34737
34773
|
const cfg = manifest.config;
|
|
34738
34774
|
const out = {};
|
|
@@ -34761,6 +34797,7 @@ function bindRuntimeConfig(manifest, payload, prior, switches = { ownerDm: true,
|
|
|
34761
34797
|
for (const name of Object.keys(files)) {
|
|
34762
34798
|
if (name !== LLM_SOURCE && !PERSONA_FILES.includes(name)) mismatches.add("files");
|
|
34763
34799
|
}
|
|
34800
|
+
const ownerDmTools = signedManifestTools(manifest, "owner_dm", searchProvidersCapable);
|
|
34764
34801
|
const sj = isObj2(payload.skills_json) ? payload.skills_json : void 0;
|
|
34765
34802
|
const eps = bindTargets(sj?.endpoints, manifest.endpoints, endpointEquals);
|
|
34766
34803
|
const dbs = bindTargets(sj?.databases, manifest.databases, databaseEquals);
|
|
@@ -34778,14 +34815,20 @@ function bindRuntimeConfig(manifest, payload, prior, switches = { ownerDm: true,
|
|
|
34778
34815
|
appliedSearch = searchBlock;
|
|
34779
34816
|
} else {
|
|
34780
34817
|
mismatches.add("search");
|
|
34781
|
-
|
|
34818
|
+
const usablePrior = cfg.search !== void 0 && prior.search?.provider === cfg.search.provider ? prior.search : void 0;
|
|
34819
|
+
searchBlock = cfg.search === void 0 ? void 0 : usablePrior ?? Object.freeze({ provider: cfg.search.provider });
|
|
34782
34820
|
}
|
|
34783
34821
|
const skillsDoc = {
|
|
34784
34822
|
endpoints: eps.written,
|
|
34785
34823
|
databases: dbs.written,
|
|
34786
34824
|
flags: [...cfg.flags],
|
|
34787
34825
|
...deny !== void 0 || cfg.tools_allow !== void 0 ? { tools: { ...deny !== void 0 ? { deny } : {}, ...cfg.tools_allow !== void 0 ? { allow: [...cfg.tools_allow] } : {} } } : {},
|
|
34788
|
-
...searchBlock ? { search: searchBlock } : {}
|
|
34826
|
+
...searchBlock ? { search: searchBlock } : {},
|
|
34827
|
+
// D31: the marker the runtime keys D24 (iii) on; written under every manifest, true when config.search is signed.
|
|
34828
|
+
search_signed: cfg.search !== void 0,
|
|
34829
|
+
// D36, D39: the signed owner-DM cap, only when the owner-DM switch is on and the manifest grants an owner-DM tool;
|
|
34830
|
+
// otherwise none, so the runtime clamps a signed raise to the default. Never read from the payload.
|
|
34831
|
+
...cfg.search?.owner_dm_daily_cap !== void 0 && switches.ownerDm && ownerDmTools.length > 0 ? { owner_dm_search_cap: cfg.search.owner_dm_daily_cap } : {}
|
|
34789
34832
|
};
|
|
34790
34833
|
out["skills.json"] = JSON.stringify(skillsDoc, null, 2) + "\n";
|
|
34791
34834
|
const signedMemory = { mode: cfg.memory.mode, ...cfg.memory.recall_days !== void 0 ? { recall_days: cfg.memory.recall_days } : {} };
|
|
@@ -34816,12 +34859,7 @@ function bindRuntimeConfig(manifest, payload, prior, switches = { ownerDm: true,
|
|
|
34816
34859
|
mismatches.add("legacy_assign");
|
|
34817
34860
|
legacyDecisions = decisions.filter((d) => isObj2(d) && d.action === "delete");
|
|
34818
34861
|
}
|
|
34819
|
-
const
|
|
34820
|
-
const policy = {
|
|
34821
|
-
v: 1,
|
|
34822
|
-
manifest_tools: [.../* @__PURE__ */ new Set([...switches.ownerDm ? signedTools("owner_dm") : [], ...switches.room ? signedTools("team_room") : []])].sort(),
|
|
34823
|
-
secret_path_mode: "enforce"
|
|
34824
|
-
};
|
|
34862
|
+
const policy = manifestPolicyFor(manifest, switches, searchProvidersCapable);
|
|
34825
34863
|
return {
|
|
34826
34864
|
mismatches: CONFIG_BINDING_FIELDS.filter((f) => mismatches.has(f)).sort(),
|
|
34827
34865
|
targetMismatches: { endpoint: eps.mismatches, database: dbs.mismatches },
|
|
@@ -34960,6 +34998,8 @@ var TOOL_ROOM_CLASSES = Object.freeze({
|
|
|
34960
34998
|
current_time: Object.freeze(["room_safe_read"]),
|
|
34961
34999
|
echo: Object.freeze(["room_safe_read"]),
|
|
34962
35000
|
web_search: Object.freeze(["owner_credential"]),
|
|
35001
|
+
web_extract: Object.freeze(["owner_credential", "egress"]),
|
|
35002
|
+
// D5: spends the owner's key and sends URLs out
|
|
34963
35003
|
fetch_url: Object.freeze(["egress"]),
|
|
34964
35004
|
sql_query: Object.freeze(["owner_credential"]),
|
|
34965
35005
|
http_request: Object.freeze(["owner_credential", "egress", "side_effect"])
|
|
@@ -35381,6 +35421,8 @@ function hexToBytes4(h) {
|
|
|
35381
35421
|
return new Uint8Array(Buffer.from(h, "hex"));
|
|
35382
35422
|
}
|
|
35383
35423
|
var BOOT_DRAIN_WAIT_MS_DEFAULT = 6e4;
|
|
35424
|
+
var SEARCH_CAPABILITY_REFETCH_DEBOUNCE_MS = 1e3;
|
|
35425
|
+
var SEARCH_CAPABILITY_REFETCH_MAX_WAIT_MS = 1e4;
|
|
35384
35426
|
var BootDrainRotationRefusedError = class extends Error {
|
|
35385
35427
|
constructor() {
|
|
35386
35428
|
super("key-package rotation refused: the boot drain has not settled");
|
|
@@ -35576,6 +35618,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
35576
35618
|
this.actionClock = deps.actionThrottleNow ?? Date.now;
|
|
35577
35619
|
this.rng = deps.rng ?? Math.random;
|
|
35578
35620
|
this.bootDrainWaitMs = deps.bootDrainWaitMs ?? BOOT_DRAIN_WAIT_MS_DEFAULT;
|
|
35621
|
+
this.searchCapabilityRefetchDebounceMs = deps.searchCapabilityRefetchDebounceMs ?? SEARCH_CAPABILITY_REFETCH_DEBOUNCE_MS;
|
|
35622
|
+
this.searchCapabilityRefetchMaxWaitMs = deps.searchCapabilityRefetchMaxWaitMs ?? SEARCH_CAPABILITY_REFETCH_MAX_WAIT_MS;
|
|
35579
35623
|
this.replySleep = deps.replySleep ?? ((ms) => new Promise((resolve) => {
|
|
35580
35624
|
const t = setTimeout(resolve, ms);
|
|
35581
35625
|
t.unref?.();
|
|
@@ -36014,6 +36058,8 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36014
36058
|
bootSweepTimer;
|
|
36015
36059
|
rng;
|
|
36016
36060
|
bootDrainWaitMs;
|
|
36061
|
+
searchCapabilityRefetchDebounceMs;
|
|
36062
|
+
searchCapabilityRefetchMaxWaitMs;
|
|
36017
36063
|
/** Resolved (and cleared) each time an ingest cycle settles: see start(). */
|
|
36018
36064
|
ingestSettledWaiters = [];
|
|
36019
36065
|
/** The boot key-package publish, when start() had to defer it. Test seam. */
|
|
@@ -36459,9 +36505,14 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36459
36505
|
}
|
|
36460
36506
|
const skillsJson = payload.skills_json ?? {};
|
|
36461
36507
|
if (version && inForce === null) {
|
|
36508
|
+
let asDelivered = skillsJson;
|
|
36509
|
+
if (skillsJson !== null && typeof skillsJson === "object" && !Array.isArray(skillsJson)) {
|
|
36510
|
+
const { owner_dm_search_cap: _cap, search_signed: _signed, ...rest } = skillsJson;
|
|
36511
|
+
asDelivered = rest;
|
|
36512
|
+
}
|
|
36462
36513
|
this.grantWriters.writeManagedFiles(this.home, this.cfg.identityId, {
|
|
36463
36514
|
...payload.files ?? {},
|
|
36464
|
-
"skills.json": `${JSON.stringify(
|
|
36515
|
+
"skills.json": `${JSON.stringify(asDelivered, null, 2)}
|
|
36465
36516
|
`
|
|
36466
36517
|
});
|
|
36467
36518
|
}
|
|
@@ -36516,7 +36567,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36516
36567
|
}
|
|
36517
36568
|
let bound;
|
|
36518
36569
|
try {
|
|
36519
|
-
bound = this.grantWriters.bindRuntimeConfig(manifest, payload, { search: prior }, { ownerDm: this.ownerDmManifestGrants, room: this.roomManifestGrants });
|
|
36570
|
+
bound = this.grantWriters.bindRuntimeConfig(manifest, payload, { search: prior }, { ownerDm: this.ownerDmManifestGrants, room: this.roomManifestGrants }, this.searchProvidersCapable());
|
|
36520
36571
|
} catch (e) {
|
|
36521
36572
|
bound = null;
|
|
36522
36573
|
return this.bindFailed(manifest, payload.memory, e);
|
|
@@ -36535,9 +36586,9 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36535
36586
|
return true;
|
|
36536
36587
|
}
|
|
36537
36588
|
try {
|
|
36538
|
-
this.grantWriters.writeManifestPolicy(this.home, this.cfg.identityId, bound.policy);
|
|
36539
36589
|
this.grantWriters.writeManagedFiles(this.home, this.cfg.identityId, bound.files);
|
|
36540
36590
|
if (manifest.config.llm === void 0) this.grantWriters.removeManagedLlmSource(this.home, this.cfg.identityId);
|
|
36591
|
+
this.grantWriters.writeManifestPolicy(this.home, this.cfg.identityId, manifestPolicyFor(manifest, { ownerDm: this.ownerDmManifestGrants, room: this.roomManifestGrants }, this.searchProvidersCapable()));
|
|
36541
36592
|
} catch (e) {
|
|
36542
36593
|
this.manifestBinding = null;
|
|
36543
36594
|
throw e;
|
|
@@ -36590,6 +36641,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36590
36641
|
/** The report half of refetchRuntimeConfigOnce (split out for the plan 4 Task 11 bind_failed path). */
|
|
36591
36642
|
async postRuntimeConfigReport(base, version, skillsJson) {
|
|
36592
36643
|
const local = readLocalReport(this.home, this.cfg.identityId);
|
|
36644
|
+
const reportedSearchProviders = this.searchProvidersCapable();
|
|
36593
36645
|
const report = {
|
|
36594
36646
|
applied_version: version,
|
|
36595
36647
|
paths: checkConfiguredPaths(skillsJson),
|
|
@@ -36604,6 +36656,13 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36604
36656
|
// to the backend. The backend needs it to generate a truthful tool
|
|
36605
36657
|
// paragraph, so it rides along here.
|
|
36606
36658
|
has_search_key: local?.has_search_key ?? false,
|
|
36659
|
+
// L-2 (spec §7.9): only the runtime knows whether an extract-capable provider key is readable; never derived
|
|
36660
|
+
// from has_search_key. An older runtime omits it: false.
|
|
36661
|
+
// Final review I-2 (controller ruling): AND the current search_providers_v2, so the web never shows extract while
|
|
36662
|
+
// the bridge withholds web_extract from the runtime.
|
|
36663
|
+
has_extract_provider: (local?.has_extract_provider ?? false) && reportedSearchProviders,
|
|
36664
|
+
// D39: the owner-DM daily cap the runtime enforces, passed through when it reported a valid one; never the signed value.
|
|
36665
|
+
...local?.owner_dm_search_cap_effective !== void 0 ? { owner_dm_search_cap_effective: local.owner_dm_search_cap_effective } : {},
|
|
36607
36666
|
// Same idea, for the BYOK LLM key file: only the runtime can see
|
|
36608
36667
|
// whether one is configured in its own environment.
|
|
36609
36668
|
has_llm_key: local?.has_llm_key ?? false,
|
|
@@ -36643,7 +36702,11 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36643
36702
|
// Plan 4 Task 15 (OQ-5): sorted, unique enums; capability is the latest hello's class.
|
|
36644
36703
|
config_binding_mismatches: [...this.configBindingMismatches],
|
|
36645
36704
|
target_mismatches: ["database", "endpoint"].filter((k) => this.targetMismatches[k] > 0),
|
|
36646
|
-
capability: this.lastConsumerCapability
|
|
36705
|
+
capability: this.lastConsumerCapability,
|
|
36706
|
+
// D28 (§11.1 step 3): a separate key, never a value in the capability enum. False with no runtime attached.
|
|
36707
|
+
search_providers_v2: reportedSearchProviders,
|
|
36708
|
+
// D19 item 4: content-free; the signed provider is one the attached runtime(s) cannot honour.
|
|
36709
|
+
search_provider_unsupported: this.searchProviderUnsupported(reportedSearchProviders)
|
|
36647
36710
|
}
|
|
36648
36711
|
};
|
|
36649
36712
|
const reportedVerifyFailures = this.ownerVerifyFailures;
|
|
@@ -36664,6 +36727,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36664
36727
|
return;
|
|
36665
36728
|
}
|
|
36666
36729
|
this.ownerVerifyFailures = Math.max(0, this.ownerVerifyFailures - reportedVerifyFailures);
|
|
36730
|
+
this.lastReportedSearchProviders = reportedSearchProviders;
|
|
36667
36731
|
if (version) writeServiceLog2("info", "runtime_config.applied", { version });
|
|
36668
36732
|
else writeServiceLog2("info", "runtime_config.legacy_memory_reported");
|
|
36669
36733
|
}
|
|
@@ -36733,6 +36797,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
36733
36797
|
this.startedAt = Date.now();
|
|
36734
36798
|
await this.restoreRuntimeInbox();
|
|
36735
36799
|
await this.loadToolGrantState();
|
|
36800
|
+
this.recomputeManifestPolicy(false);
|
|
36736
36801
|
try {
|
|
36737
36802
|
this.store.pruneRoomSearchCap(utcDayOf(Date.now()));
|
|
36738
36803
|
} catch (e) {
|
|
@@ -42524,6 +42589,115 @@ ${channelId}`;
|
|
|
42524
42589
|
manifestConsumerAttached() {
|
|
42525
42590
|
return this.manifestConsumer !== null;
|
|
42526
42591
|
}
|
|
42592
|
+
/**
|
|
42593
|
+
* D28 (B-4): every socket that said hello, with whether its latest hello declared search_providers_v2 on the
|
|
42594
|
+
* manifest_grants_v1 predicate (ws-server.ts). The agent value is the AND over all of them, absent (false) with
|
|
42595
|
+
* none: unlike manifestConsumer, never newest wins. The per-delivery filter judges each socket by its own hello.
|
|
42596
|
+
*/
|
|
42597
|
+
searchProvidersSockets = /* @__PURE__ */ new Map();
|
|
42598
|
+
/**
|
|
42599
|
+
* The D28 value the files were last recomputed for; null until the first socket observation, so that observation
|
|
42600
|
+
* always recomputes (final review I-1), even when it is non-capable.
|
|
42601
|
+
*/
|
|
42602
|
+
searchProvidersState = null;
|
|
42603
|
+
searchCapabilityRefetchTimer = null;
|
|
42604
|
+
/** When the current burst's first change happened (valid while the timer is set). */
|
|
42605
|
+
searchCapabilityBurstStart = 0;
|
|
42606
|
+
/** The D28 value in the last report posted (null until one is); a change needs a refetch to reach the web. */
|
|
42607
|
+
lastReportedSearchProviders = null;
|
|
42608
|
+
noteSearchProvidersSocket(token, declared) {
|
|
42609
|
+
this.searchProvidersSockets.set(token, declared);
|
|
42610
|
+
this.onSearchProvidersMaybeChanged();
|
|
42611
|
+
}
|
|
42612
|
+
clearSearchProvidersSocket(token) {
|
|
42613
|
+
if (this.searchProvidersSockets.delete(token)) this.onSearchProvidersMaybeChanged();
|
|
42614
|
+
}
|
|
42615
|
+
/** D28: every attached runtime socket declared search_providers_v2; no socket attached means absent (false). */
|
|
42616
|
+
searchProvidersCapable() {
|
|
42617
|
+
if (this.searchProvidersSockets.size === 0) return false;
|
|
42618
|
+
for (const v of this.searchProvidersSockets.values()) if (!v) return false;
|
|
42619
|
+
return true;
|
|
42620
|
+
}
|
|
42621
|
+
/**
|
|
42622
|
+
* D19 item 3 (B-6): the search provider signed in the stored verified manifest in force (a pin loaded and a good
|
|
42623
|
+
* manifest), never the server payload; undefined with no pin, no manifest, or no signed search block. The
|
|
42624
|
+
* per-delivery filter (consumer-grants.ts withSearchCapability) and the S-28 report reason both read this.
|
|
42625
|
+
*/
|
|
42626
|
+
signedSearchProvider() {
|
|
42627
|
+
const pin = this.cfg.ownerGrantPin?.pin ?? null;
|
|
42628
|
+
return pin !== null ? this.toolGrantManifest?.config.search?.provider : void 0;
|
|
42629
|
+
}
|
|
42630
|
+
/** D19 item 4, D34: the in-force signed provider is not brave, and the attached runtime(s) lack the capability. */
|
|
42631
|
+
searchProviderUnsupported(capable) {
|
|
42632
|
+
if (capable) return false;
|
|
42633
|
+
const provider = this.signedSearchProvider();
|
|
42634
|
+
return provider !== void 0 && provider !== "brave";
|
|
42635
|
+
}
|
|
42636
|
+
/**
|
|
42637
|
+
* D35 (P-4): on a capability change, rewrite manifest-policy.json at once from the stored manifest, the kill
|
|
42638
|
+
* switches and the new capability, then a debounced refetch (one per burst) only when needed.
|
|
42639
|
+
*/
|
|
42640
|
+
onSearchProvidersMaybeChanged() {
|
|
42641
|
+
const capable = this.searchProvidersCapable();
|
|
42642
|
+
const prev = this.searchProvidersState;
|
|
42643
|
+
if (capable === prev) return;
|
|
42644
|
+
this.searchProvidersState = capable;
|
|
42645
|
+
this.recomputeManifestPolicy(capable);
|
|
42646
|
+
if (this.closingDown) return;
|
|
42647
|
+
if (prev === null && !capable) return;
|
|
42648
|
+
const now = Date.now();
|
|
42649
|
+
if (this.searchCapabilityRefetchTimer !== null) clearTimeout(this.searchCapabilityRefetchTimer);
|
|
42650
|
+
else this.searchCapabilityBurstStart = now;
|
|
42651
|
+
const delay = Math.max(0, Math.min(this.searchCapabilityRefetchDebounceMs, this.searchCapabilityBurstStart + this.searchCapabilityRefetchMaxWaitMs - now));
|
|
42652
|
+
this.searchCapabilityRefetchTimer = setTimeout(() => {
|
|
42653
|
+
this.searchCapabilityRefetchTimer = null;
|
|
42654
|
+
if (this.searchCapabilityRefetchNeeded()) void this.refetchRuntimeConfig();
|
|
42655
|
+
}, delay);
|
|
42656
|
+
this.searchCapabilityRefetchTimer.unref?.();
|
|
42657
|
+
}
|
|
42658
|
+
/**
|
|
42659
|
+
* A refetch re-renders skills.json (owner_dm_search_cap counts web_extract only with the capability, D39) and posts
|
|
42660
|
+
* the report. Needed under a manifest in force, or when the reported value is stale. Never before the boot fetch.
|
|
42661
|
+
*/
|
|
42662
|
+
searchCapabilityRefetchNeeded() {
|
|
42663
|
+
if (!this.bootConfigSettled || this.closingDown) return false;
|
|
42664
|
+
const inForce = (this.cfg.ownerGrantPin?.pin ?? null) !== null && this.toolGrantManifest !== null;
|
|
42665
|
+
return inForce || this.lastReportedSearchProviders !== this.searchProvidersCapable();
|
|
42666
|
+
}
|
|
42667
|
+
/**
|
|
42668
|
+
* D35: the local recompute. Widening (web_extract back in) only while the binding for the in-force manifest stands,
|
|
42669
|
+
* so a policy is never written ahead of its skills.json. Narrowing never waits: with no binding in force, a file that
|
|
42670
|
+
* lists web_extract loses it at once, so an older runtime never reads it as unreadable (which drops every grant).
|
|
42671
|
+
* Content-free; a write failure is logged and the debounced refetch tries again.
|
|
42672
|
+
*/
|
|
42673
|
+
recomputeManifestPolicy(capable) {
|
|
42674
|
+
const pin = this.cfg.ownerGrantPin?.pin ?? null;
|
|
42675
|
+
const manifest = pin !== null ? this.toolGrantManifest : null;
|
|
42676
|
+
try {
|
|
42677
|
+
if (manifest !== null && this.manifestBinding !== null && this.manifestBinding.counter === manifest.counter) {
|
|
42678
|
+
this.grantWriters.writeManifestPolicy(this.home, this.cfg.identityId, manifestPolicyFor(manifest, { ownerDm: this.ownerDmManifestGrants, room: this.roomManifestGrants }, capable));
|
|
42679
|
+
return;
|
|
42680
|
+
}
|
|
42681
|
+
if (capable) return;
|
|
42682
|
+
const path = join17(runtimeConfigDir(this.home, this.cfg.identityId), MANIFEST_POLICY_FILE);
|
|
42683
|
+
if (!existsSync5(path)) return;
|
|
42684
|
+
let doc;
|
|
42685
|
+
try {
|
|
42686
|
+
doc = JSON.parse(readFileSync10(path, "utf8"));
|
|
42687
|
+
} catch {
|
|
42688
|
+
return;
|
|
42689
|
+
}
|
|
42690
|
+
const tools = doc?.manifest_tools;
|
|
42691
|
+
if (!Array.isArray(tools) || !tools.includes("web_extract")) return;
|
|
42692
|
+
this.grantWriters.writeManifestPolicy(this.home, this.cfg.identityId, {
|
|
42693
|
+
v: 1,
|
|
42694
|
+
manifest_tools: tools.filter((t) => typeof t === "string" && t !== "web_extract"),
|
|
42695
|
+
secret_path_mode: "enforce"
|
|
42696
|
+
});
|
|
42697
|
+
} catch (e) {
|
|
42698
|
+
writeServiceLog2("warn", "authz.search_capability", { state: ServiceLogEnum.of("policy_write_failed", ["policy_write_failed"]) }, e);
|
|
42699
|
+
}
|
|
42700
|
+
}
|
|
42527
42701
|
noteConsumerCapability(state) {
|
|
42528
42702
|
this.lastConsumerCapability = state;
|
|
42529
42703
|
if (state !== "ok") {
|
|
@@ -42715,6 +42889,8 @@ ${channelId}`;
|
|
|
42715
42889
|
this.supersedeRecheckTimers.clear();
|
|
42716
42890
|
if (this.redeliveryTimer !== null) clearTimeout(this.redeliveryTimer);
|
|
42717
42891
|
this.redeliveryTimer = null;
|
|
42892
|
+
if (this.searchCapabilityRefetchTimer !== null) clearTimeout(this.searchCapabilityRefetchTimer);
|
|
42893
|
+
this.searchCapabilityRefetchTimer = null;
|
|
42718
42894
|
this.pingHandle?.close();
|
|
42719
42895
|
if (this.safetyTimer) clearInterval(this.safetyTimer);
|
|
42720
42896
|
if (this.runtimeConfigTimer) clearInterval(this.runtimeConfigTimer);
|
|
@@ -42824,7 +43000,7 @@ ${channelId}`;
|
|
|
42824
43000
|
};
|
|
42825
43001
|
|
|
42826
43002
|
// src/config.ts
|
|
42827
|
-
import { readFileSync as
|
|
43003
|
+
import { readFileSync as readFileSync11, statSync as statSync5 } from "node:fs";
|
|
42828
43004
|
import { dirname as dirname6, join as join18 } from "node:path";
|
|
42829
43005
|
var REQUIRED = ["api_key", "identity_id", "device_id", "signing_private_key", "enclave_id", "api_base"];
|
|
42830
43006
|
var CONFIG_ERROR_MESSAGE = {
|
|
@@ -42846,7 +43022,7 @@ var ConfigError = class extends Error {
|
|
|
42846
43022
|
function loadConfig(opts) {
|
|
42847
43023
|
let raw;
|
|
42848
43024
|
try {
|
|
42849
|
-
raw =
|
|
43025
|
+
raw = readFileSync11(opts.credentialPath, "utf8");
|
|
42850
43026
|
} catch (e) {
|
|
42851
43027
|
const code = e?.code;
|
|
42852
43028
|
throw new ConfigError(code === "ENOENT" ? "credential_not_found" : "credential_unreadable");
|
|
@@ -42976,11 +43152,11 @@ import { z as z3 } from "zod";
|
|
|
42976
43152
|
|
|
42977
43153
|
// src/auth.ts
|
|
42978
43154
|
import { randomBytes as randomBytes11, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
42979
|
-
import { existsSync as existsSync6, readFileSync as
|
|
43155
|
+
import { existsSync as existsSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync2 } from "node:fs";
|
|
42980
43156
|
function resolveToken(opts) {
|
|
42981
43157
|
if (opts.token) return opts.token;
|
|
42982
43158
|
const f = opts.tokenFile;
|
|
42983
|
-
if (f && existsSync6(f)) return
|
|
43159
|
+
if (f && existsSync6(f)) return readFileSync12(f, "utf8").trim();
|
|
42984
43160
|
const tok = randomBytes11(32).toString("base64url");
|
|
42985
43161
|
if (f) writeFileSync2(f, tok, { mode: 384 });
|
|
42986
43162
|
return tok;
|
|
@@ -43076,6 +43252,7 @@ function withConsumerGrants(m, perToolConsumer) {
|
|
|
43076
43252
|
return { ...m, authorization: Object.freeze({ ...rest, ...stripGrants ? { tool_grants: NO_GRANTS } : {} }) };
|
|
43077
43253
|
}
|
|
43078
43254
|
var MANIFEST_GRANTS_FEATURE = "manifest_grants_v1";
|
|
43255
|
+
var SEARCH_PROVIDERS_FEATURE = "search_providers_v2";
|
|
43079
43256
|
var CONSTANT_TOOL_NAMES = Object.freeze({
|
|
43080
43257
|
owner_dm: Object.freeze(["current_time", "web_search"]),
|
|
43081
43258
|
team_room: Object.freeze(["current_time"])
|
|
@@ -43098,6 +43275,15 @@ function withHarnessGrants(m, withheld) {
|
|
|
43098
43275
|
if (!a || a.tool_grants.length === 0) return m;
|
|
43099
43276
|
return { ...m, authorization: Object.freeze({ ...a, tool_grants: NO_GRANTS }) };
|
|
43100
43277
|
}
|
|
43278
|
+
function withSearchCapability(m, consumerHasFeature, signedProvider) {
|
|
43279
|
+
if (consumerHasFeature) return m;
|
|
43280
|
+
const a = m.authorization;
|
|
43281
|
+
if (!a) return m;
|
|
43282
|
+
const stripSearch = signedProvider !== void 0 && signedProvider !== "brave";
|
|
43283
|
+
const kept = a.tool_grants.filter((g) => g.tool !== "web_extract" && !(stripSearch && g.tool === "web_search"));
|
|
43284
|
+
if (kept.length === a.tool_grants.length) return m;
|
|
43285
|
+
return { ...m, authorization: Object.freeze({ ...a, tool_grants: kept.length === 0 ? NO_GRANTS : Object.freeze(kept) }) };
|
|
43286
|
+
}
|
|
43101
43287
|
|
|
43102
43288
|
// src/http-server.ts
|
|
43103
43289
|
function writeSse(res, msgs, onWriteError) {
|
|
@@ -43120,7 +43306,8 @@ data: ${JSON.stringify(msgs[i])}
|
|
|
43120
43306
|
}
|
|
43121
43307
|
function deliverSseBatch(res, batch, agent, onWriteError) {
|
|
43122
43308
|
const withheld = agent.harnessGrantsWithheld();
|
|
43123
|
-
const
|
|
43309
|
+
const provider = agent.signedSearchProvider();
|
|
43310
|
+
const left3 = writeSse(res, batch.map((m) => withSearchCapability(withManifestCapability(withHarnessGrants(withConsumerGrants(m, false), withheld), false), false, provider)), onWriteError);
|
|
43124
43311
|
const leftIds = new Set(left3.map((message) => message.message_id));
|
|
43125
43312
|
agent.ackRuntimeMessages(batch.filter((message) => !leftIds.has(message.message_id)).map((message) => message.message_id));
|
|
43126
43313
|
if (left3.length) agent.requeueFront(batch.filter((message) => leftIds.has(message.message_id)));
|
|
@@ -43391,7 +43578,10 @@ function createHttpServer(agent, opts) {
|
|
|
43391
43578
|
if (method === "GET" && path === "/v1/messages") {
|
|
43392
43579
|
const channel = url.searchParams.get("channel_id") ?? void 0;
|
|
43393
43580
|
const wait = Math.max(0, Math.min(50, Number(url.searchParams.get("wait") ?? 0)));
|
|
43394
|
-
|
|
43581
|
+
const drained = await agent.drain(channel, wait);
|
|
43582
|
+
const withheld = agent.harnessGrantsWithheld();
|
|
43583
|
+
const provider = agent.signedSearchProvider();
|
|
43584
|
+
return json(res, 200, drained.map((m) => withSearchCapability(withManifestCapability(withHarnessGrants(withConsumerGrants(m, false), withheld), false), false, provider)));
|
|
43395
43585
|
}
|
|
43396
43586
|
if (method === "GET" && path === "/v1/events") {
|
|
43397
43587
|
const channel = url.searchParams.get("channel_id") ?? void 0;
|
|
@@ -43565,6 +43755,7 @@ function attachAgentWebSocket(server, agent, opts) {
|
|
|
43565
43755
|
consumer: "unknown",
|
|
43566
43756
|
perToolGrants: false,
|
|
43567
43757
|
manifestGrants: false,
|
|
43758
|
+
searchProviders: false,
|
|
43568
43759
|
// helloWaitMs 0 keeps today's synchronous initial flush exactly (the opt-out the existing tests use).
|
|
43569
43760
|
deliveryOpen: helloWaitMs <= 0,
|
|
43570
43761
|
deliveryToken: /* @__PURE__ */ new Map(),
|
|
@@ -43618,9 +43809,13 @@ function attachAgentWebSocket(server, agent, opts) {
|
|
|
43618
43809
|
continue;
|
|
43619
43810
|
}
|
|
43620
43811
|
const out = withConsumerNotes(
|
|
43621
|
-
|
|
43622
|
-
|
|
43623
|
-
|
|
43812
|
+
withSearchCapability(
|
|
43813
|
+
withManifestCapability(
|
|
43814
|
+
withHarnessGrants(withConsumerGrants(batch[i], session.perToolGrants), !session.perToolGrants && agent.harnessGrantsWithheld()),
|
|
43815
|
+
session.manifestGrants
|
|
43816
|
+
),
|
|
43817
|
+
session.searchProviders,
|
|
43818
|
+
agent.signedSearchProvider()
|
|
43624
43819
|
),
|
|
43625
43820
|
batch[i],
|
|
43626
43821
|
session,
|
|
@@ -43656,6 +43851,7 @@ function attachAgentWebSocket(server, agent, opts) {
|
|
|
43656
43851
|
const cleanup = () => {
|
|
43657
43852
|
if (cleaned) return;
|
|
43658
43853
|
cleaned = true;
|
|
43854
|
+
session.closed = true;
|
|
43659
43855
|
if (helloTimer) clearTimeout(helloTimer);
|
|
43660
43856
|
agent.noteSessionClose();
|
|
43661
43857
|
unsubscribe();
|
|
@@ -43676,6 +43872,7 @@ function attachAgentWebSocket(server, agent, opts) {
|
|
|
43676
43872
|
if (session.harness) agent.clearHarnessReport(session.harness);
|
|
43677
43873
|
if (session.perToolGrants) agent.clearPerToolConsumer(session);
|
|
43678
43874
|
if (session.manifestGrants) agent.clearManifestConsumer(session);
|
|
43875
|
+
agent.clearSearchProvidersSocket(session);
|
|
43679
43876
|
};
|
|
43680
43877
|
current = { sock, cleanup, sweepStale };
|
|
43681
43878
|
sock.on("close", cleanup);
|
|
@@ -43751,6 +43948,7 @@ async function handleInbound(sock, agent, data, inflight, flush, session) {
|
|
|
43751
43948
|
return;
|
|
43752
43949
|
}
|
|
43753
43950
|
if (m.type === "hello") {
|
|
43951
|
+
if (session.closed) return;
|
|
43754
43952
|
const report = m.harness === void 0 ? null : parseHarnessReport(m.harness);
|
|
43755
43953
|
if (report) {
|
|
43756
43954
|
session.harness = report;
|
|
@@ -43774,6 +43972,8 @@ async function handleInbound(sock, agent, data, inflight, flush, session) {
|
|
|
43774
43972
|
agent.clearManifestConsumer(session);
|
|
43775
43973
|
}
|
|
43776
43974
|
agent.noteConsumerCapability(manifest ? "ok" : session.harnessClaimed ? "harness_consumer" : "missing");
|
|
43975
|
+
session.searchProviders = session.consumer === "runtime" && session.harnessClaimed !== true && perTool && Array.isArray(m.features) && m.features.includes(SEARCH_PROVIDERS_FEATURE);
|
|
43976
|
+
agent.noteSearchProvidersSocket(session, session.searchProviders);
|
|
43777
43977
|
if (session.consumer === "runtime" && session.harnessClaimed !== true && Array.isArray(m.features) && m.features.includes(MEMORY_NOTES_FEATURE)) {
|
|
43778
43978
|
agent.noteNotesConsumer();
|
|
43779
43979
|
}
|
|
@@ -43781,7 +43981,7 @@ async function handleInbound(sock, agent, data, inflight, flush, session) {
|
|
|
43781
43981
|
if (session.recallConsumer) agent.noteRecallConsumer();
|
|
43782
43982
|
session.unifiedConsumer = agent.unifiedFeatureOn() && session.consumer === "runtime" && session.harnessClaimed !== true && Array.isArray(m.features) && m.features.includes(MEMORY_UNIFIED_FEATURE);
|
|
43783
43983
|
if (session.unifiedConsumer) agent.noteUnifiedConsumer();
|
|
43784
|
-
sendJson(sock, { type: "hello", features: ["nack", MEMORY_NOTES_FEATURE, MEMORY_RECALL_FEATURE, ...agent.unifiedFeatureOn() ? [MEMORY_UNIFIED_FEATURE] : [], MANIFEST_GRANTS_FEATURE] });
|
|
43984
|
+
sendJson(sock, { type: "hello", features: ["nack", MEMORY_NOTES_FEATURE, MEMORY_RECALL_FEATURE, ...agent.unifiedFeatureOn() ? [MEMORY_UNIFIED_FEATURE] : [], MANIFEST_GRANTS_FEATURE, SEARCH_PROVIDERS_FEATURE] });
|
|
43785
43985
|
if (!session.deliveryOpen) {
|
|
43786
43986
|
session.deliveryOpen = true;
|
|
43787
43987
|
flush();
|
package/dist/preflight.cjs
CHANGED
|
@@ -33,6 +33,7 @@ var init_resolve_node = __esm({
|
|
|
33
33
|
// src/preflight-upgrade.cts
|
|
34
34
|
var preflight_upgrade_exports = {};
|
|
35
35
|
__export(preflight_upgrade_exports, {
|
|
36
|
+
DIST_BASE: () => DIST_BASE,
|
|
36
37
|
guidedUpgrade: () => guidedUpgrade,
|
|
37
38
|
parseShasums: () => parseShasums,
|
|
38
39
|
promptYesNo: () => promptYesNo,
|
|
@@ -49,6 +49,10 @@ export interface LocalReport {
|
|
|
49
49
|
persona: Record<string, string>;
|
|
50
50
|
has_search_key: boolean;
|
|
51
51
|
has_llm_key: boolean;
|
|
52
|
+
/** L-2 (spec §7.9): a readable key for an extract-capable provider. Only a real true counts; absent is false. */
|
|
53
|
+
has_extract_provider: boolean;
|
|
54
|
+
/** D39: the owner-DM daily search cap the runtime enforces; an integer in [0, 2000], otherwise undefined. */
|
|
55
|
+
owner_dm_search_cap_effective?: number;
|
|
52
56
|
legacy_memory: LegacyMemoryReport[];
|
|
53
57
|
/** §B.4 counts; undefined when the runtime reported none or the block was malformed. */
|
|
54
58
|
secret_paths?: SecretPathsReport;
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const BRIDGE_VERSION = "0.30.
|
|
1
|
+
export declare const BRIDGE_VERSION = "0.30.3";
|
package/dist/ws-server.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export declare const MEMORY_UNIFIED_FEATURE = "memory_unified";
|
|
|
51
51
|
* fake against this list at run time (7b gate fix: a member added here without
|
|
52
52
|
* a fake made three tests throw "is not a function").
|
|
53
53
|
*/
|
|
54
|
-
export declare const WS_AGENT_MEMBERS: readonly ["ackRuntimeMessages", "action", "admitRuntimeDelivery", "applyMemoryNote", "clearHarnessReport", "clearManifestConsumer", "clearPerToolConsumer", "harnessGrantsWithheld", "isRuntimeMessageRetired", "noteConsumerCapability", "noteManifestConsumer", "noteRoomCapReached", "noteNotesConsumer", "notePerToolConsumer", "noteRecallConsumer", "noteUnifiedConsumer", "noteSessionClose", "noteSessionEvicted", "noteSessionOpen", "notesAppliedGeneration", "notesForDelivery", "recallForDelivery", "unifiedFeatureOn", "unifiedForDelivery", "recordHarnessReport", "recordRuntimeReply", "reportStatusGated", "requeueFailedRuntimeDeliveries", "requeueFront", "runtimeReplyReceipt", "send", "sendA2A", "subscribe", "takeDeliverable", "wakeListeners"];
|
|
54
|
+
export declare const WS_AGENT_MEMBERS: readonly ["ackRuntimeMessages", "action", "admitRuntimeDelivery", "applyMemoryNote", "clearHarnessReport", "clearManifestConsumer", "clearPerToolConsumer", "clearSearchProvidersSocket", "harnessGrantsWithheld", "signedSearchProvider", "isRuntimeMessageRetired", "noteConsumerCapability", "noteManifestConsumer", "noteRoomCapReached", "noteSearchProvidersSocket", "noteNotesConsumer", "notePerToolConsumer", "noteRecallConsumer", "noteUnifiedConsumer", "noteSessionClose", "noteSessionEvicted", "noteSessionOpen", "notesAppliedGeneration", "notesForDelivery", "recallForDelivery", "unifiedFeatureOn", "unifiedForDelivery", "recordHarnessReport", "recordRuntimeReply", "reportStatusGated", "requeueFailedRuntimeDeliveries", "requeueFront", "runtimeReplyReceipt", "send", "sendA2A", "subscribe", "takeDeliverable", "wakeListeners"];
|
|
55
55
|
export type WsAgent = Pick<BridgeAgent, (typeof WS_AGENT_MEMBERS)[number]>;
|
|
56
56
|
export declare function attachAgentWebSocket(server: HttpServer, agent: WsAgent, opts: AgentWebSocketOptions): {
|
|
57
57
|
close(): void;
|