@securecoms/agent-bridge 0.11.3 → 0.16.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.
Files changed (45) hide show
  1. package/README.md +76 -0
  2. package/connectors/hermes/adapter.py +11 -1
  3. package/connectors/hermes/tests/test_adapter_wiring.py +33 -0
  4. package/dist/bin.js +2668 -96
  5. package/dist/bridge-agent.d.ts +331 -0
  6. package/dist/config.d.ts +3 -0
  7. package/dist/errors.d.ts +1 -1
  8. package/dist/http-server.d.ts +4 -4
  9. package/dist/index.js +2641 -109
  10. package/dist/memory-taps.d.ts +6 -2
  11. package/dist/rate-limit.d.ts +10 -0
  12. package/dist/service/harness-wire.d.ts +4 -0
  13. package/dist/service/install-prep.d.ts +1 -0
  14. package/dist/service/wire.d.ts +30 -0
  15. package/dist/summarize.d.ts +54 -0
  16. package/dist/voice/fast-client.d.ts +13 -0
  17. package/dist/voice/fast-prompt.d.ts +53 -0
  18. package/dist/voice/persona.d.ts +23 -0
  19. package/dist/voice/prompt-compiler.d.ts +4 -0
  20. package/dist/voice/settings.d.ts +31 -0
  21. package/package.json +6 -1
  22. package/dist/attachments.js +0 -49
  23. package/dist/auth.js +0 -56
  24. package/dist/bin.js.pre-c1-f803007 +0 -5795
  25. package/dist/bin.js.pre-c3-096a9a3 +0 -5832
  26. package/dist/bin.js.pre-ffrebuild-b162d4a +0 -5639
  27. package/dist/bin.js.pre-kpheal-20260708-150924 +0 -5531
  28. package/dist/bin.js.pre-kppersist-20260708-152341 +0 -5543
  29. package/dist/bin.js.pre-p0-20260708-102415 +0 -5196
  30. package/dist/bin.js.pre-p1b-02f47e1 +0 -5639
  31. package/dist/bin.js.pre-warmfix-20260708-153705 +0 -5613
  32. package/dist/bin.js.pretrace-113205 +0 -5395
  33. package/dist/bridge-agent.js +0 -831
  34. package/dist/config.js +0 -74
  35. package/dist/coordination.js +0 -63
  36. package/dist/errors.js +0 -25
  37. package/dist/http-server.js +0 -220
  38. package/dist/index.js.pre-p0-20260708-102415 +0 -4879
  39. package/dist/latency.js +0 -87
  40. package/dist/rate-limit.js +0 -56
  41. package/dist/service/config.js +0 -26
  42. package/dist/service/install.js +0 -60
  43. package/dist/service/launchd.js +0 -30
  44. package/dist/service/systemd.js +0 -25
  45. package/dist/ws-server.js +0 -254
package/README.md CHANGED
@@ -359,6 +359,7 @@ pm2 start "npx @agentvault/connect-agent-bridge --credential ./agent.json" \
359
359
  - Install **verifies the daemon actually came up** and fails loudly with the log path if it didn't — no silent crash-loops.
360
360
  - 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
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).
362
+ - **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
363
 
363
364
  ---
364
365
 
@@ -404,6 +405,81 @@ attachments: the cryptography lives in the daemon, not the agent.
404
405
 
405
406
  ---
406
407
 
408
+ ## Voice fast path
409
+
410
+ On a live voice call, the daemon can send an instant, persona-voiced
411
+ one-sentence acknowledgement while your full agent harness is still thinking —
412
+ so the human hears *something* immediately instead of dead air.
413
+
414
+ **The credential file's `voice` block is now a fallback, not the primary
415
+ source.** As of `@securecoms/agent-bridge` 0.13.0, the daemon fetches this
416
+ agent's voice settings from the backend (`GET /api/v1/agents/{identity_id}/voice-settings`)
417
+ on start and again whenever it receives a `voice_settings_changed` ping, and
418
+ prefers that backend row whenever its `version` is ≥ 1 — an owner has saved
419
+ settings from the web app's "Voice settings" page (`/app/agents/:identityId/voice`).
420
+ A `version: 0` response (nothing saved yet) keeps the file's persona/voice/pace
421
+ as a default, but **the fast-path LLM call goes through the SecureComs
422
+ backend proxy** (`POST /api/v1/voice/fast-complete`) whenever the settings
423
+ fetch itself succeeds — `voice_settings_source` is `"backend"`, or `"file"`
424
+ with `version: 0` — and in that case the file's own
425
+ `fast_path.api_key`/`base_url`/`model` are ignored; only the file's `persona`
426
+ and `enabled` flag still matter, as the fallback source for those fields. The
427
+ daemon needs no API key of its own for the proxied path — the backend proxy
428
+ holds `OPENROUTER_API_KEY` and does the call. **If the settings fetch itself
429
+ fails at boot** (network error, backend down), the daemon has no proxy config
430
+ to fall back to and instead falls back to the file's own `fast_path` block
431
+ in full, including its `api_key`/`api_key_env`/`base_url`/`model` — the
432
+ daemon then calls your own model provider directly with that key until a
433
+ later fetch succeeds.
434
+
435
+ Check which source is active via the daemon's health endpoint:
436
+ ```bash
437
+ curl -H "Authorization: Bearer $(cat <credential-dir>/bridge-token)" \
438
+ http://127.0.0.1:7077/healthz | jq '.voice_settings_version, .voice_settings_source'
439
+ ```
440
+ `voice_settings_source` is `"backend"` (a saved row won), `"file"` (fell back
441
+ to the credential file's `voice` block), or `"none"` (neither is configured).
442
+
443
+ Add a `voice` block to `agent.json` (used only as the fallback described above):
444
+
445
+ ```json
446
+ "voice": {
447
+ "persona": { "display_name": "Cortina",
448
+ "persona": "You are Cortina, a crisp, warm operations lead who co-owns AgentVault with Eric and Chris.",
449
+ "quirks": ["opens with 'Right —' when sure", "asks one clarifying question instead of guessing"] },
450
+ "fast_path": { "enabled": true, "base_url": "https://api.openai.com/v1", "model": "gpt-4o-mini",
451
+ "api_key_env": "SECURECOMS_VOICE_LLM_KEY", "timeout_ms": 2500, "max_tokens": 80 }
452
+ }
453
+ ```
454
+
455
+ - `persona` describes how the fast-path model should speak; it is folded into
456
+ a compiled-in system prompt shared with the daemon's other voice guidance.
457
+ - `fast_path.api_key_env` names an environment variable holding your LLM
458
+ provider's API key (e.g. `SECURECOMS_VOICE_LLM_KEY`); `api_key` accepts one
459
+ inline instead, but the env-var form is preferred so the key never lives in
460
+ the credential file. Without a resolvable key, the fast path stays disabled
461
+ even if `enabled: true`.
462
+ - `fast_path.base_url` must be `https://` or a loopback (`127.0.0.1` /
463
+ `localhost` / `::1`) `http://` URL — the daemon refuses any other
464
+ non-loopback plaintext endpoint, the same rule it applies to itself.
465
+ - Only a genuine live voice turn from a human (or unknown) sender fires the
466
+ fast path; it never fires for the agent's own relayed turns, and a fast-path
467
+ failure (timeout, network, bad response) is fail-soft — it never blocks or
468
+ delays your full agent's reply.
469
+
470
+ **The fast-path call runs through the SecureComs backend proxy — your own
471
+ model key never pays for it — whenever the settings fetch succeeds** (the
472
+ `fast_path.api_key`/`api_key_env`/`base_url`/`model` fields above are
473
+ ignored whenever `voice_settings_source` is `"backend"`, or `"file"` with
474
+ `version: 0`; they only take effect again if the settings fetch fails at
475
+ boot, in which case the daemon falls back to the file's own `fast_path`
476
+ including its key). Each proxied call is metered server-side
477
+ (`voice_fast_completions` table; `usage_daily` metrics `fast_voice_calls`,
478
+ `fast_voice_tokens_in`, `fast_voice_tokens_out`), capped at
479
+ `VOICE_FAST_CALLS_PER_MINUTE` (default 60) per agent.
480
+
481
+ ---
482
+
407
483
  ## Flags and environment
408
484
 
409
485
  | Flag | Env var | Default | Description |
@@ -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