@securecoms/agent-bridge 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -305,6 +305,65 @@ Pick one receive mode — WebSocket, SSE, or long-poll — and stick with it. Th
305
305
 
306
306
  ---
307
307
 
308
+ ## Bring your own agent
309
+
310
+ The daemon has no approved-framework list. It checks a bearer token and nothing else, so there is exactly one requirement:
311
+
312
+ > **You must be able to add code to a process that stays running.**
313
+
314
+ That is the whole test. If your agent is a program you control, it can hold a SecureComs channel in about fifty lines — see [`examples/ws-agent-client.mjs`](./examples/ws-agent-client.mjs) and [`ws-agent-client.py`](./examples/ws-agent-client.py), which are runnable as-is.
315
+
316
+ To check your own connector against every rule in this section, run [`examples/byo-conformance.mjs`](./examples/byo-conformance.mjs) — a zero-dependency agent that follows the section literally and prints a pass/fail line per rule:
317
+
318
+ ```sh
319
+ node examples/byo-conformance.mjs <your agent's identity_id>
320
+ ```
321
+
322
+ **This fits.** Anything you build yourself, in any language: LangChain, LangGraph, CrewAI, AutoGen, Pydantic AI, the OpenAI or Anthropic agent SDKs, LlamaIndex — or a plain script calling an LLM API with no framework at all. You already own the process; you add a socket to it.
323
+
324
+ **This does not fit.** Consumer chat products (claude.ai, ChatGPT, the Gemini app) have no extension point. Session-based coding CLIs have no always-on loop to attach to. A fully hosted agent you cannot run code beside cannot reach the daemon.
325
+
326
+ **A platform you run but did not write** (Dify, Flowise, n8n, and the like) fits only if it lets you run persistent custom code. If it does, what you are writing is a plugin for that platform — the same job the bundled `openclaw` and `hermes` connectors do.
327
+
328
+ Install with `--harness universal` to skip connector wiring and print the details your agent needs. (`--harness custom` is the old name for the same thing and still works.) Running `install` with no `--harness` at all does the same thing when neither OpenClaw nor Hermes is present.
329
+
330
+ ### Where your agent runs
331
+
332
+ Same machine by default — the daemon binds `127.0.0.1`. Another machine works too, over TLS (see **Cross-host use** under Security). One caveat: a delivered attachment gives you a **local file path** on the daemon's machine, so an off-host agent can receive text, status, vault and A2A traffic but cannot read attachment bytes.
333
+
334
+ ### The five rules
335
+
336
+ 1. **Ack everything.** Delivery is at-least-once. Send `{type:"ack", message_id}` once you have durably handled a message, and dedup by `message_id`.
337
+ 2. **One consumer per daemon.** A second `/v1/ws` closes the first. Two clients will evict each other in a loop.
338
+ 3. **Answer the pings.** The daemon pings every 30s and reaps a socket that misses a pong. Standard WebSocket clients do this for you.
339
+ 4. **Read the token from its file, never from an environment value.** An env *value* is inherited by child processes and visible in `ps eww` and crash dumps. A path is not.
340
+ 5. **Check the identity gate before you attach.** Details below.
341
+
342
+ ### The identity gate
343
+
344
+ Skip this and a single-agent host still works. The failure appears later, when a second agent is installed on the same machine, and it is silent.
345
+
346
+ The daemon rewrites `~/.securecoms/connections/<identity_id>.json` on every start, giving you its current port and token-file path — so a moved port is picked up automatically. But if your connector reads the *wrong* agent's file, everything looks consistent: it authenticates successfully to that agent's daemon, drives that agent's identity, and evicts that agent's real connector.
347
+
348
+ The bearer token does not catch this. It rejects a caller with *no* valid token; it does nothing against a caller holding a *valid but wrong* pair.
349
+
350
+ So, before opening the WebSocket:
351
+
352
+ 1. Know which agent you are meant to serve — a fixed `identity_id`, from your own config. **Never** infer it by scanning the connections directory: on a multi-agent host a slow-starting daemon can leave exactly one file present at the moment you look, and you will bind to the wrong agent.
353
+ 2. Read that agent's connection file for `bridge_url` and `token_file`.
354
+ 3. `GET /healthz` (no token needed) and require **both**: the file's `identity_id` and your own fixed `identity_id` each equal `healthz.identity_id`.
355
+ 4. Any mismatch: do not open the socket. Back off with jitter and retry from step 2 — the port may have moved.
356
+
357
+ On reconnect, re-read the connection file and the token, but **not** your identity. Re-deriving it on every reconnect lets a file appearing or disappearing silently re-bind a running connector to a different agent.
358
+
359
+ The bundled `openclaw` and `hermes` connectors implement all of this; read either one as a worked example.
360
+
361
+ ### Separate compartments need separate OS uids
362
+
363
+ The gate prevents honest mistakes. It is not a tenant boundary. The hop from daemon to agent carries plaintext, and the token and sealed store are readable by anyone sharing the uid — so any agent under the same uid can already drive another's daemon directly. **Agents in different orgs or enclaves that must not cross MUST run under different OS uids, or on different hosts.**
364
+
365
+ ---
366
+
308
367
  ## Run as a service
309
368
 
310
369
  For an autonomous agent running on a server, keep the daemon up with systemd so it restarts on failure and starts on boot. A sleeping or crashed daemon means its WebSocket/SSE connections drop and messages queue on the server until the daemon reconnects; they replay losslessly on wake. The daemon can't wake a sleeping machine.
@@ -358,7 +417,8 @@ pm2 start "npx @agentvault/connect-agent-bridge --credential ./agent.json" \
358
417
  - A **free port is auto-selected** from `7077-7276` (lowest free wins; sibling service definitions and live binds both count as taken). Pass `--port N` to override; the explicit port is honored as-is.
359
418
  - Install **verifies the daemon actually came up** and fails loudly with the log path if it didn't — no silent crash-loops.
360
419
  - Install echoes the credential's `device_id`, `issued_at`, and a redacted api-key fingerprint so you can confirm you installed the newest download. **Re-running install is the only way to update a live (e.g. rotated) credential** — replacing the downloaded file alone does nothing.
361
- - Optional `--harness openclaw|hermes|custom` prints a paste-ready config snippet with the chosen port and the token-file path (the token itself never appears in output).
420
+ - Optional `--harness openclaw|hermes|universal` prints a paste-ready config snippet with the chosen port and the token-file path (the token itself never appears in output). (`--harness custom` is an accepted alias for `universal`.)
421
+ - **One harness gateway serves one agent.** The connector registers a single platform and resolves a single bound identity for the life of the gateway process, so a connector directory (`~/.hermes/plugins/securecoms`, `~/.openclaw/local-plugins/securecoms`) holds exactly one agent. Install and `wire` therefore **refuse** to overwrite a binding that names a different agent — doing so would take the harness away from that agent, not add one alongside it. To run two agents on one host, give each its own harness profile with its own connector directory and binding. To deliberately move a harness to a different agent, pass `--rebind`.
362
422
 
363
423
  ---
364
424
 
@@ -132,7 +132,17 @@ class SecureComsAdapter(BasePlatformAdapter):
132
132
 
133
133
  # ── Connection lifecycle ──────────────────────────────────────────────
134
134
 
135
- async def connect(self) -> bool:
135
+ async def connect(self, *, is_reconnect: bool = False) -> bool:
136
+ """Connect to the local daemon.
137
+
138
+ ``is_reconnect`` mirrors BasePlatformAdapter.connect's signature —
139
+ gateway/run.py passes it as a keyword on EVERY call, cold boot
140
+ included, so an adapter that omits it raises TypeError and the
141
+ platform never attaches. SecureComs holds no server-side update
142
+ queue to preserve across an outage (the daemon is a local WS the
143
+ supervisor re-opens), so the flag carries no behaviour here.
144
+ """
145
+ del is_reconnect # accepted for contract parity; nothing to preserve
136
146
  self._stopped = False
137
147
 
138
148
  # BINDING-STABILITY fold: resolve bound_identity here, ONCE, for the
@@ -539,3 +539,36 @@ class TestCheckRequirementsWiring:
539
539
  monkeypatch.setenv("SECURECOMS_CONNECTION_FILE", str(tmp_path / "does-not-exist.json"))
540
540
 
541
541
  assert adapter.check_requirements() is False
542
+
543
+
544
+ # ---------------------------------------------------------------------------
545
+ # (6) connect() signature contract with BasePlatformAdapter
546
+ # ---------------------------------------------------------------------------
547
+
548
+
549
+ class TestConnectSignatureContract:
550
+ """gateway/run.py ALWAYS calls `adapter.connect(is_reconnect=...)` — on the
551
+ cold first boot as well as on every reconnect. An adapter whose connect()
552
+ does not accept that keyword raises TypeError inside the gateway's
553
+ connect helper, which the reconnect watcher reports as a platform error
554
+ and retries forever: the daemon looks connected to the SecureComs server
555
+ while the harness side never attaches and the agent never answers.
556
+ """
557
+
558
+ def test_connect_accepts_is_reconnect_keyword(self):
559
+ import inspect
560
+
561
+ sig = inspect.signature(adapter.SecureComsAdapter.connect)
562
+ param = sig.parameters.get("is_reconnect")
563
+ assert param is not None, "connect() must accept the is_reconnect keyword"
564
+ assert param.kind is inspect.Parameter.KEYWORD_ONLY
565
+ assert param.default is False
566
+
567
+ @pytest.mark.asyncio
568
+ async def test_connect_is_reconnect_true_reaches_the_unwired_path(self, monkeypatch):
569
+ a, _ = make_adapter(monkeypatch, files={}) # no binding file -> unwired
570
+ ok = await a.connect(is_reconnect=True)
571
+ assert ok is False
572
+ code, _message, retryable = a.fatal_errors[-1]
573
+ assert code == "unwired"
574
+ assert retryable is False
package/dist/bin.js CHANGED
@@ -1409,9 +1409,7 @@ var SUITE2 = "xchacha20poly1305";
1409
1409
  var AAD = new TextEncoder().encode("kekset");
1410
1410
  var enc = new TextEncoder();
1411
1411
  var dec = new TextDecoder();
1412
- function unb64(s) {
1413
- return new Uint8Array(Buffer.from(s, "base64"));
1414
- }
1412
+ var unb64 = base64ToBytes;
1415
1413
  function headerObject(h) {
1416
1414
  return {
1417
1415
  enclaveId: h.enclaveId,
@@ -11000,6 +10998,16 @@ function connectorTargetDir(home, harness) {
11000
10998
  if (harness === "openclaw") return `${home}/.openclaw/local-plugins/securecoms`;
11001
10999
  return `${home}/.hermes/plugins/securecoms`;
11002
11000
  }
11001
+ function checkBindingConflict(prev, next, allowRebind) {
11002
+ if (!prev || prev === next) return { ok: true, rebindFrom: null };
11003
+ if (allowRebind) return { ok: true, rebindFrom: prev };
11004
+ return {
11005
+ ok: false,
11006
+ message: `refusing to rebind: this harness connector is already bound to ${prev}, not ${next}. A harness gateway serves ONE agent, so this would take the harness away from ${prev} rather than add ${next} alongside it.
11007
+ To run both agents, give each one its own harness profile (its own connector dir + binding).
11008
+ To deliberately move THIS harness to ${next}, re-run with --rebind.`
11009
+ };
11010
+ }
11003
11011
  function connectorsSrcDir(importMetaUrl, harness) {
11004
11012
  const pkgRoot = dirname5(dirname5(fileURLToPath(importMetaUrl)));
11005
11013
  return join9(pkgRoot, "connectors", harness);
@@ -11061,8 +11069,18 @@ function runWire(deps) {
11061
11069
  if (!resolved) return 1;
11062
11070
  const { entry, harness } = resolved;
11063
11071
  const targetDir = connectorTargetDir(deps.home, harness);
11072
+ const guard = checkBindingConflict(
11073
+ deps.readBinding(targetDir),
11074
+ entry.identityId,
11075
+ deps.argv.includes("--rebind")
11076
+ );
11077
+ if (!guard.ok) {
11078
+ deps.log(`wire: ${guard.message}`);
11079
+ return 1;
11080
+ }
11064
11081
  deps.copyConnector(deps.connectorsSrcDir(harness), targetDir, harness);
11065
11082
  deps.writeBinding(targetDir, entry.identityId);
11083
+ if (guard.rebindFrom) deps.log(`wire: rebind: ${guard.rebindFrom} -> ${entry.identityId}`);
11066
11084
  if (harness === "hermes") {
11067
11085
  const enabled = enableHermesConfig(deps.home, deps);
11068
11086
  deps.log(`[securecoms] ${enabled.message}`);
@@ -11107,15 +11125,30 @@ function runUnwire(deps) {
11107
11125
  function customFacts(input) {
11108
11126
  const ws = `ws://127.0.0.1:${input.port ?? "<PORT>"}/v1/ws`;
11109
11127
  return [
11110
- ` bridge WS: ${ws}`,
11111
- ` token file: ${input.tokenFilePath}`,
11128
+ ` bridge WS: ${ws}`,
11129
+ ` token file: ${input.tokenFilePath}`,
11130
+ ` agent id: ${input.identityId}`,
11131
+ ` discovery: ~/.securecoms/connections/${input.identityId}.json (rewritten on every daemon start)`,
11112
11132
  ` Bring your own connector: open the WS with the bearer token read from that file.`,
11133
+ ` Ack every message you receive; delivery is at-least-once, so dedup by message_id.`,
11134
+ ` Before you attach, check the identity gate: the daemon's /healthz identity_id MUST equal`,
11135
+ ` the agent id above. Skipping it means a second agent on this host can silently`,
11136
+ ` drive the wrong daemon. See "Bring your own agent" in the agent-bridge README.`,
11113
11137
  ` Protocol reference: the "WebSocket" section of the agent-bridge README.`
11114
11138
  ].join("\n");
11115
11139
  }
11116
11140
  function autoWireHarness(input, deps) {
11117
11141
  const h = input.harness;
11118
- if (!h || h === "none") return { echo: "", wired: false, error: false };
11142
+ if (h === "none") return { echo: "", wired: false, error: false };
11143
+ if (!h) {
11144
+ return {
11145
+ echo: `[securecoms] No agent harness detected (no ~/.openclaw, no ~/.hermes) \u2014 the bridge service is installed and running, but nothing is attached to it yet.
11146
+ Bring your own agent:
11147
+ ${customFacts(input)}`,
11148
+ wired: false,
11149
+ error: false
11150
+ };
11151
+ }
11119
11152
  if (h === "openclaw") {
11120
11153
  const retry = `agent-bridge wire --harness openclaw --identity ${input.slug}`;
11121
11154
  const parent2 = `${input.home}/.openclaw`;
@@ -11123,18 +11156,25 @@ function autoWireHarness(input, deps) {
11123
11156
  return {
11124
11157
  echo: `[securecoms] OpenClaw not found at ~/.openclaw \u2014 the bridge service is installed, but the harness was NOT wired.
11125
11158
  Install OpenClaw, then re-run install, or run: ${retry}
11126
- (or re-run install with --harness custom).`,
11159
+ (or re-run install with --harness universal).`,
11127
11160
  wired: false,
11128
11161
  error: true
11129
11162
  };
11130
11163
  }
11131
11164
  const targetDir = connectorTargetDir(input.home, "openclaw");
11165
+ const guard = checkBindingConflict(deps.readBinding(targetDir), input.identityId, input.allowRebind ?? false);
11166
+ if (!guard.ok) {
11167
+ return {
11168
+ echo: `[securecoms] the bridge service is installed, but the OpenClaw connector was NOT wired: ${guard.message}`,
11169
+ wired: false,
11170
+ error: true
11171
+ };
11172
+ }
11132
11173
  try {
11133
- const prev = deps.readBinding(targetDir);
11134
11174
  deps.copyConnector(input.connectorsSrcDir, targetDir, "openclaw");
11135
11175
  deps.writeBinding(targetDir, input.identityId);
11136
- const rebind = prev && prev !== input.identityId ? `
11137
- rebind: ${prev} \u2192 ${input.identityId}` : "";
11176
+ const rebind = guard.rebindFrom ? `
11177
+ rebind: ${guard.rebindFrom} \u2192 ${input.identityId}` : "";
11138
11178
  return {
11139
11179
  echo: `[securecoms] OpenClaw connector installed and bound to identity ${input.identityId}.${rebind}
11140
11180
  Now restart OpenClaw to pick up the connector.`,
@@ -11157,14 +11197,21 @@ function autoWireHarness(input, deps) {
11157
11197
  return {
11158
11198
  echo: `[securecoms] Hermes not found at ~/.hermes \u2014 the bridge service is installed, but the harness was NOT wired.
11159
11199
  Install Hermes, then re-run install, or run: ${retry}
11160
- (or re-run install with --harness custom).`,
11200
+ (or re-run install with --harness universal).`,
11161
11201
  wired: false,
11162
11202
  error: true
11163
11203
  };
11164
11204
  }
11165
11205
  const targetDir = connectorTargetDir(input.home, "hermes");
11206
+ const guard = checkBindingConflict(deps.readBinding(targetDir), input.identityId, input.allowRebind ?? false);
11207
+ if (!guard.ok) {
11208
+ return {
11209
+ echo: `[securecoms] the bridge service is installed, but the Hermes connector was NOT wired: ${guard.message}`,
11210
+ wired: false,
11211
+ error: true
11212
+ };
11213
+ }
11166
11214
  try {
11167
- const prev = deps.readBinding(targetDir);
11168
11215
  deps.copyConnector(input.connectorsSrcDir, targetDir, "hermes");
11169
11216
  deps.writeBinding(targetDir, input.identityId);
11170
11217
  const enabled = enableHermesConfig(input.home, deps);
@@ -11176,8 +11223,8 @@ function autoWireHarness(input, deps) {
11176
11223
  error: true
11177
11224
  };
11178
11225
  }
11179
- const rebind = prev && prev !== input.identityId ? `
11180
- rebind: ${prev} \u2192 ${input.identityId}` : "";
11226
+ const rebind = guard.rebindFrom ? `
11227
+ rebind: ${guard.rebindFrom} \u2192 ${input.identityId}` : "";
11181
11228
  return {
11182
11229
  echo: `[securecoms] Hermes connector installed and bound to identity ${input.identityId}.${rebind}
11183
11230
  ${enabled.message}
@@ -11194,7 +11241,7 @@ function autoWireHarness(input, deps) {
11194
11241
  };
11195
11242
  }
11196
11243
  }
11197
- return { echo: `[securecoms] Custom harness \u2014 bring your own connector:
11244
+ return { echo: `[securecoms] Universal \u2014 bring your own agent:
11198
11245
  ${customFacts(input)}`, wired: false, error: false };
11199
11246
  }
11200
11247
 
@@ -11342,6 +11389,7 @@ async function orchestrateInstall(ctx) {
11342
11389
  identityId: parsed.identityId,
11343
11390
  slug,
11344
11391
  port,
11392
+ allowRebind: ctx.allowRebind,
11345
11393
  home: ctx.home,
11346
11394
  tokenFilePath: result.tokenHint,
11347
11395
  connectorsSrcDir: ctx.connectorsSrcDir
@@ -11741,6 +11789,8 @@ function realWireDeps() {
11741
11789
  argv: realWireArgv(),
11742
11790
  readFleet: realReadFleet,
11743
11791
  copyConnector: realCopyConnector,
11792
+ readBinding: realReadBinding,
11793
+ // bin.ts:252 — feeds checkBindingConflict
11744
11794
  writeBinding: realWriteBinding,
11745
11795
  removeBinding: realRemoveBinding,
11746
11796
  getHealth: realGetHealth,
@@ -11762,7 +11812,7 @@ async function runServiceVerb(verb) {
11762
11812
  const label = argOrEnv("--label", "SECURECOMS_LABEL");
11763
11813
  if (verb === "install" ? !credentialPath : !credentialPath && !label) {
11764
11814
  const execPathUsage = verb === "install" ? " [--exec-path <path>]" : "";
11765
- process.stderr.write(`usage: agent-bridge ${verb} --credential <path> [--label <name>] [--store <path>] [--port N] [--harness openclaw|hermes|custom]${execPathUsage}
11815
+ process.stderr.write(`usage: agent-bridge ${verb} --credential <path> [--label <name>] [--store <path>] [--port N] [--harness openclaw|hermes|universal]${execPathUsage}
11766
11816
  `);
11767
11817
  process.exit(2);
11768
11818
  }
@@ -11786,6 +11836,7 @@ async function runServiceVerb(verb) {
11786
11836
  label,
11787
11837
  explicitPort: portStr ? Number(portStr) : void 0,
11788
11838
  harness: installHarness,
11839
+ allowRebind: process.argv.includes("--rebind"),
11789
11840
  home: homedir2(),
11790
11841
  connectorsSrcDir: connectorsSrcDir(import.meta.url, installHarness === "hermes" ? "hermes" : "openclaw"),
11791
11842
  autoWireDeps: {
package/dist/index.js CHANGED
@@ -1121,9 +1121,7 @@ var SUITE2 = "xchacha20poly1305";
1121
1121
  var AAD = new TextEncoder().encode("kekset");
1122
1122
  var enc = new TextEncoder();
1123
1123
  var dec = new TextDecoder();
1124
- function unb64(s) {
1125
- return new Uint8Array(Buffer.from(s, "base64"));
1126
- }
1124
+ var unb64 = base64ToBytes;
1127
1125
  function headerObject(h) {
1128
1126
  return {
1129
1127
  enclaveId: h.enclaveId,
@@ -7,6 +7,10 @@ export interface AutoWireInput {
7
7
  home: string;
8
8
  tokenFilePath: string;
9
9
  connectorsSrcDir: string;
10
+ /** Deliberate consent to move an already-bound harness to this identity
11
+ * (`install --rebind`). Absent/false, a conflicting binding REFUSES —
12
+ * see checkBindingConflict in wire.ts for why. */
13
+ allowRebind?: boolean;
10
14
  }
11
15
  export interface AutoWireDeps extends HermesConfigIoDeps {
12
16
  statDir: (p: string) => boolean;
@@ -43,6 +43,7 @@ export interface OrchestrateInstallCtx {
43
43
  label?: string;
44
44
  explicitPort?: number;
45
45
  harness?: string;
46
+ allowRebind?: boolean;
46
47
  home: string;
47
48
  launchAgentsDir?: string;
48
49
  systemdDir?: string;
@@ -38,6 +38,10 @@ export interface WireDeps extends HermesConfigIoDeps {
38
38
  argv: string[];
39
39
  readFleet: () => AgentFleetEntry[];
40
40
  copyConnector: (srcDir: string, dstDir: string, harness: Harness) => void;
41
+ /** The identity the target dir is ALREADY bound to, or null when nothing
42
+ * is bound / the file is missing or malformed. Read BEFORE any write so
43
+ * `checkBindingConflict` can refuse without having touched disk. */
44
+ readBinding: (dstDir: string) => string | null;
41
45
  writeBinding: (dstDir: string, identityId: string) => void;
42
46
  removeBinding: (dstDir: string) => void;
43
47
  getHealth: (port: number) => {
@@ -72,6 +76,32 @@ export declare function detectHarness(home: string, statDir: (p: string) => bool
72
76
  * placeholder value, never the real one).
73
77
  */
74
78
  export declare function connectorTargetDir(home: string, harness: Harness): string;
79
+ /**
80
+ * The binding guard, shared by `wire` and by `install`'s auto-wire.
81
+ *
82
+ * A harness connector registers ONE platform and resolves ONE bound identity
83
+ * for the life of the gateway process (connectors/hermes/adapter.py's
84
+ * `register` + `_bound_identity`), so a connector directory holds exactly one
85
+ * agent. Writing a different identity over an existing binding therefore does
86
+ * not ADD an agent — it silently takes the harness away from whichever agent
87
+ * was already using it, leaving that agent dark while two gateways race for
88
+ * one daemon socket. Observed in the field 2026-09-08: installing a second
89
+ * Hermes agent rebound `~/.hermes/plugins/securecoms` away from the agent the
90
+ * default-profile gateway was serving.
91
+ *
92
+ * So an overwrite must be a deliberate act (`--rebind`), exactly like the
93
+ * binding itself is a deliberate act (audit C1). A first wire and an
94
+ * idempotent same-identity re-wire are always allowed.
95
+ *
96
+ * Pure: callers do the reading and the refusing.
97
+ */
98
+ export declare function checkBindingConflict(prev: string | null, next: string, allowRebind: boolean): {
99
+ ok: true;
100
+ rebindFrom: string | null;
101
+ } | {
102
+ ok: false;
103
+ message: string;
104
+ };
75
105
  /**
76
106
  * Resolves a connector's SOURCE dir at runtime, keyed by harness (`connectors/
77
107
  * <harness>`) — `connectors/` is shipped SOURCE (not compiled — H2), so this
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@securecoms/agent-bridge",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",