@securecoms/agent-bridge 0.26.5 → 0.27.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 +91 -0
- package/connectors/hermes/securecoms_authz.py +16 -4
- package/connectors/hermes/tests/test_securecoms_authz.py +31 -6
- package/connectors/openclaw/openclaw-entry.js +14 -6
- package/connectors/openclaw/openclaw-entry.toolgate.test.ts +20 -9
- package/dist/bin.d.ts +23 -0
- package/dist/bin.js +1433 -89
- package/dist/bridge-agent.d.ts +343 -0
- package/dist/consumer-grants.d.ts +23 -0
- package/dist/http-server.d.ts +10 -0
- package/dist/index.js +1419 -84
- package/dist/memory/consumer-notes.d.ts +34 -0
- package/dist/memory/memory-config.d.ts +10 -0
- package/dist/memory/note-frames.d.ts +44 -0
- package/dist/memory/note-provenance.d.ts +40 -0
- package/dist/memory/notes-book.d.ts +169 -0
- package/dist/memory/secret-scan.d.ts +5 -0
- package/dist/room-tools.d.ts +23 -0
- package/dist/version.d.ts +1 -1
- package/dist/voice/fast-prompt.d.ts +5 -0
- package/dist/ws-server.d.ts +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,6 +56,19 @@ The bundled connectors give tools only to the agent owner's turns. Each harness
|
|
|
56
56
|
|
|
57
57
|
On an older harness the connector reports its tool veto as inactive and refuses every turn that has no tool grant, so non-owner rooms go quiet instead of running tools unchecked. `/healthz` shows `harness_tool_veto_active` (true, false, or null when no connector is attached), the authenticated `GET /v1/status` shows the harness name and version, and the daemon logs `harness.tool_veto_inactive` once when an attached harness cannot veto. After upgrading the package, re-run `agent-bridge wire --harness openclaw` (or re-copy the Hermes connector files) so the harness loads the new connector, then restart the harness gateway.
|
|
58
58
|
|
|
59
|
+
### Room tool rule (0.26.6 and later)
|
|
60
|
+
|
|
61
|
+
In a room whose every member, including pending members and anyone who left but still holds an MLS leaf, is in the agent owner's org, a same-org human sender's turn may use `current_time` only. Cross-org and unknown rooms get no tools. The owner DM keeps `current_time` and `web_search`. Replies to room turns are re-classified when they are sent.
|
|
62
|
+
|
|
63
|
+
Operator steps:
|
|
64
|
+
|
|
65
|
+
- **Deploy order:** backend (already live), then this bridge, then `@securecoms/agent-runtime` 0.9.9. A new runtime on an old bridge offers no tools on the owner's relayed voice turns. Room grants need both the new bridge and the new runtime.
|
|
66
|
+
- **Re-wire connectors:** re-run `agent-bridge wire --harness ...` on every Hermes and OpenClaw host after upgrading. Connectors are copied at wire time and an upgrade does not refresh them. A bridge downgrade after re-wiring makes the new connectors refuse tools on the owner's voice turns.
|
|
67
|
+
- **Kill switch:** `SECURECOMS_ROOM_DEFAULT_GRANTS` (bridge service environment, read at startup). Unset or `on` keeps the room default; any other value sends no tools on every room turn. It does not affect the owner DM. `SECURECOMS_OWNER_DM_DEFAULT_GRANTS` is unchanged.
|
|
68
|
+
- **Owner-DM epoch freshness:** owner-DM tools now also require a roster read at the agent's current MLS group and epoch. A wedged or group-less DM shows no tools. Diagnose with the `authz.decision` log fields `epoch_fresh`, `room_class`, `per_tool_consumer` and `room_kill_switch`.
|
|
69
|
+
- **Restart residual:** after a bridge restart, a reply to a room turn granted before the restart is not re-classified. Only `current_time` output can be affected.
|
|
70
|
+
- **Post-deploy probe:** count `connect.roster_mirror_unresolved` warnings and rooms reading `unknown`. A high rate means roster mirror drift is hiding room tools.
|
|
71
|
+
|
|
59
72
|
---
|
|
60
73
|
|
|
61
74
|
## Quickstart
|
|
@@ -692,6 +705,84 @@ including its key). Each proxied call is metered server-side
|
|
|
692
705
|
|
|
693
706
|
---
|
|
694
707
|
|
|
708
|
+
## Markdown notes (0.27.0)
|
|
709
|
+
|
|
710
|
+
SecureComs-runtime agents keep short notes of what the owner asks them to
|
|
711
|
+
remember, in the owner's private chat only. The runtime saves a note with the
|
|
712
|
+
`memory_note` tool, and the bridge stores only a quote of the owner's own
|
|
713
|
+
current message. Each note is sealed per row in `bridge-state.db` (schema
|
|
714
|
+
v10) and is delivered only to the runtime's `/v1/ws` socket, never to a
|
|
715
|
+
harness, HTTP, SSE, a room, another chat or the voice fast path.
|
|
716
|
+
|
|
717
|
+
**Compatibility.**
|
|
718
|
+
|
|
719
|
+
- Install `@securecoms/agent-runtime` 0.10.0 together with the bridge and
|
|
720
|
+
restart both (`launchctl bootout` then `bootstrap`, never `kickstart`). The
|
|
721
|
+
bridge moves legacy owner-DM facts into notes, and reports
|
|
722
|
+
`memory_notes: true`, only after a runtime whose hello declares
|
|
723
|
+
`memory_notes` has connected in that bridge process. A 0.9.x runtime's
|
|
724
|
+
hello does not, so a 0.27 bridge with a 0.9.x runtime keeps today's facts
|
|
725
|
+
and recall, but it is not a supported pairing.
|
|
726
|
+
- Harness daemons (Hermes, OpenClaw) must name `memory.provider` in their
|
|
727
|
+
credential file. A daemon whose file omits it is treated as `securecoms`;
|
|
728
|
+
its harness socket still never receives notes and never turns on the import
|
|
729
|
+
or the report, so naming the provider is defence in depth. A global
|
|
730
|
+
`npm install -g --prefix ~/.local` reaches every daemon whose plist points
|
|
731
|
+
at the global `dist/bin.js`, at its next restart; check each credential file
|
|
732
|
+
(`jq .memory.provider <credential-file>`) before installing.
|
|
733
|
+
- A `/v1/ws` consumer that sends no hello waits about 2 seconds for its first
|
|
734
|
+
delivery.
|
|
735
|
+
- Notes are off in a freshly started bridge until a runtime-config refetch
|
|
736
|
+
succeeds. A bridge restarted while the backend is unreachable serves no
|
|
737
|
+
notes and accepts no writes until the backend answers.
|
|
738
|
+
|
|
739
|
+
**Known limitations.**
|
|
740
|
+
|
|
741
|
+
- Text the owner pastes counts as the owner's own words, up to the
|
|
742
|
+
600-unit short-message bound.
|
|
743
|
+
- A quote can drop a condition or a negation ("Never cc X" can be saved as
|
|
744
|
+
"cc X"). Ask the agent what it saved.
|
|
745
|
+
- Short notes in CJK or Thai (fewer than 2 space-separated tokens) are
|
|
746
|
+
refused.
|
|
747
|
+
- UUIDs and long letter-and-digit tokens are refused as possible secrets.
|
|
748
|
+
- Forget and clear are logical deletes: sealed bytes can stay in free
|
|
749
|
+
database pages until they are overwritten, and they still open under the
|
|
750
|
+
device key.
|
|
751
|
+
- "Clear all notes" does not delete chat history or legacy facts. Legacy facts
|
|
752
|
+
that did not fit before can become notes after a clear.
|
|
753
|
+
- The voice fast path still sends the last 10 DM messages through the
|
|
754
|
+
backend proxy.
|
|
755
|
+
|
|
756
|
+
**Off switches.** There are three. Use the narrowest that fits.
|
|
757
|
+
|
|
758
|
+
- `memory.provider: "none"` in the credential file, then a daemon restart.
|
|
759
|
+
This turns notes off, and it also drops the recent history.
|
|
760
|
+
- `SECURECOMS_OWNER_DM_DEFAULT_GRANTS=off` in the bridge service environment.
|
|
761
|
+
This also drops the owner-DM tools, not only `memory_note`.
|
|
762
|
+
- `SECURECOMS_TOOL_DENY=memory_note` removes only the note tool.
|
|
763
|
+
|
|
764
|
+
**Log events.** All are content-free: counts and classes only, never note text.
|
|
765
|
+
`memory.note_written` (`op`, `notes_count`, `doc_chars`),
|
|
766
|
+
`memory.note_refused` (`reason_class`), `memory.notes_cache_miss`,
|
|
767
|
+
`memory.note_open_failed`, `memory.import_partial` (`remaining`),
|
|
768
|
+
`memory.config_unrecognized`, `memory.clear_generation_regressed`,
|
|
769
|
+
`memory.clear_failed`, `memory.applied_generation_out_of_range`,
|
|
770
|
+
`memory.notes_fill_failed`, `memory.import_failed`,
|
|
771
|
+
`memory.note_write_failed` (error class only), and `ws.hello_timeout` (no
|
|
772
|
+
fields: a `/v1/ws` socket sent no hello and delivery opened after the wait).
|
|
773
|
+
|
|
774
|
+
**Upgrade order.** Upgrade and restart the bridge and the runtime together.
|
|
775
|
+
First check `memory.provider` in the credential file of every daemon that a
|
|
776
|
+
global install reaches (`jq .memory.provider <credential-file>`).
|
|
777
|
+
|
|
778
|
+
**Rollback.** A 0.26.x bridge leaves the notes sealed on disk and reads
|
|
779
|
+
neither the notes nor the legacy facts already moved into them, until it is
|
|
780
|
+
upgraded again; the same holds for a 0.9.x runtime. Roll back the runtime and
|
|
781
|
+
the bridge together, and restart both: the bridge decides once per process
|
|
782
|
+
that a notes-reading runtime is present.
|
|
783
|
+
|
|
784
|
+
---
|
|
785
|
+
|
|
695
786
|
## Flags and environment
|
|
696
787
|
|
|
697
788
|
| Flag | Env var | Default | Description |
|
|
@@ -9,11 +9,13 @@ The daemon attaches an ``authorization`` block to every inbound frame
|
|
|
9
9
|
from server-authenticated roster data, never from message plaintext):
|
|
10
10
|
|
|
11
11
|
{"version": 1, "audience": ..., "sender_membership": "active"|"unknown",
|
|
12
|
-
"tool_grants": [{"tool": ..., "mode": ...}, ...]
|
|
12
|
+
"tool_grants": [{"tool": ..., "mode": ...}, ...],
|
|
13
|
+
"relay_speaker": "owner"} # optional; only on an owner-spoken relay turn
|
|
13
14
|
|
|
14
15
|
``tool_grants`` is non-empty ONLY for a DM's single human counterpart whom the
|
|
15
16
|
server marks as the agent's owner (``is_agent_owner``), or a relayed voice
|
|
16
|
-
turn whose speaker is that person
|
|
17
|
+
turn whose speaker is that person (the daemon marks it ``relay_speaker:
|
|
18
|
+
"owner"``). The daemon pins that counterpart per
|
|
17
19
|
channel, so the owner distinction rests on the server's flag: a backend that
|
|
18
20
|
mis-sets it would grant a DM member tools (review H2; binding the owner
|
|
19
21
|
identity outside server control is an open follow-up). Every other sender (room
|
|
@@ -24,7 +26,11 @@ not positively recognise as a grant is "no tools".
|
|
|
24
26
|
|
|
25
27
|
Hermes cannot express the daemon's per-tool grants (current_time, web_search)
|
|
26
28
|
as its own toolsets, so a granted turn keeps the owner's configured Hermes
|
|
27
|
-
toolsets unchanged and an ungranted turn gets none.
|
|
29
|
+
toolsets unchanged and an ungranted turn gets none. Because of that, a grant
|
|
30
|
+
is honoured ONLY on an ``owner_dm`` block or a block the daemon marked
|
|
31
|
+
``relay_speaker: "owner"`` (audit C-B1, r2 R2-T1): the daemon's room grants
|
|
32
|
+
are per-tool and are delivered only to a per-tool consumer, never to this
|
|
33
|
+
connector.
|
|
28
34
|
"""
|
|
29
35
|
|
|
30
36
|
from collections import OrderedDict
|
|
@@ -91,10 +97,16 @@ def _is_grant(entry: Any) -> bool:
|
|
|
91
97
|
|
|
92
98
|
|
|
93
99
|
def tools_allowed(frame: Any) -> bool:
|
|
94
|
-
"""True only when the
|
|
100
|
+
"""True only when the turn is the owner's own (its owner_dm block, or the
|
|
101
|
+
daemon's relay_speaker="owner" mark on a relayed voice turn) AND the daemon
|
|
102
|
+
granted it at least one tool. Audit C-B1, r2 R2-T1 (2026-10-05): a daemon
|
|
103
|
+
grant names ONE tool, but a granted turn here keeps the owner's whole Hermes
|
|
104
|
+
toolset, so nothing else is ever honoured, whatever the daemon sends."""
|
|
95
105
|
block = _valid_block(frame)
|
|
96
106
|
if block is None:
|
|
97
107
|
return False
|
|
108
|
+
if block.get("audience") != "owner_dm" and block.get("relay_speaker") != "owner":
|
|
109
|
+
return False
|
|
98
110
|
grants = block.get("tool_grants")
|
|
99
111
|
if not isinstance(grants, list) or not grants:
|
|
100
112
|
return False
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
# Per-turn tool authorization for the Hermes connector. The daemon attaches
|
|
4
4
|
# an `authorization` block to every inbound frame (BridgeAgent.authorizationFor,
|
|
5
5
|
# derived from server-authenticated roster data). Its `tool_grants` list is
|
|
6
|
-
# non-empty ONLY for the agent's pinned owner in their DM
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
6
|
+
# non-empty ONLY for the agent's pinned owner in their DM, a relayed voice turn
|
|
7
|
+
# spoken by that owner (marked `relay_speaker: "owner"` by the daemon), or
|
|
8
|
+
# (from PR R on) a same-org human in an internal room. This connector honours
|
|
9
|
+
# a grant ONLY on an `owner_dm` block or a block marked
|
|
10
|
+
# `relay_speaker: "owner"` (audit C-B1, r2 R2-T1): every other turn runs with
|
|
11
|
+
# NO tools. Fail closed.
|
|
10
12
|
|
|
11
13
|
import securecoms_authz as authz
|
|
12
14
|
|
|
@@ -36,8 +38,31 @@ class TestToolsAllowed:
|
|
|
36
38
|
|
|
37
39
|
def test_relayed_owner_voice_turn_with_grants_is_allowed(self):
|
|
38
40
|
# The daemon grants a relay turn only when the relayed speaker is the
|
|
39
|
-
# pinned owner
|
|
40
|
-
|
|
41
|
+
# pinned owner, and marks it relay_speaker="owner" (audit r2 R2-T1);
|
|
42
|
+
# its audience is agent_to_agent (the relay is an agent).
|
|
43
|
+
assert authz.tools_allowed(frame({**auth("agent_to_agent"), "relay_speaker": "owner"})) is True
|
|
44
|
+
|
|
45
|
+
def test_agent_to_agent_grants_without_the_owner_relay_mark_are_refused(self):
|
|
46
|
+
# Audit C-B1 / R2-T1: a grant is honoured only on owner_dm or on the
|
|
47
|
+
# bridge's relay_speaker="owner" mark, whatever the daemon sends.
|
|
48
|
+
assert authz.tools_allowed(frame(auth("agent_to_agent"))) is False
|
|
49
|
+
|
|
50
|
+
def test_a_non_owner_relay_mark_with_grants_is_refused(self):
|
|
51
|
+
# Review R2a fix 1: only the exact string "owner" is the mark. A
|
|
52
|
+
# truthiness check would accept every one of these.
|
|
53
|
+
for value in ("Owner", "OWNER", "guest", "", True, 1, ["owner"], {"v": "owner"}, "\u043ewner", "owner\u200b"):
|
|
54
|
+
block = {**auth("agent_to_agent"), "relay_speaker": value}
|
|
55
|
+
assert authz.tools_allowed(frame(block)) is False, repr(value)
|
|
56
|
+
|
|
57
|
+
def test_the_owner_relay_mark_without_grants_is_refused(self):
|
|
58
|
+
# Audit r2 R2-T1: the no-grants agent_to_agent case. The mark alone grants nothing.
|
|
59
|
+
assert authz.tools_allowed(frame({**auth("agent_to_agent", grants=[]), "relay_speaker": "owner"})) is False
|
|
60
|
+
|
|
61
|
+
def test_a_granted_block_outside_the_owner_dm_is_refused(self):
|
|
62
|
+
# Audit C-B1: a room grant means ONE named tool to the daemon, but a
|
|
63
|
+
# granted turn here keeps the owner's whole Hermes toolset.
|
|
64
|
+
for audience in ("team_room", "partner_room", "other_human_dm", "unknown"):
|
|
65
|
+
assert authz.tools_allowed(frame(auth(audience))) is False, audience
|
|
41
66
|
|
|
42
67
|
def test_room_member_without_grants_is_refused(self):
|
|
43
68
|
assert authz.tools_allowed(frame(auth("team_room", grants=[]))) is False
|
|
@@ -148,9 +148,11 @@ export function isOwnerAuthorization(authorization) {
|
|
|
148
148
|
// OpenClaw runs every turn with the agent's configured tools, so without this
|
|
149
149
|
// a room member, a partner-org member or another agent reaches the same exec
|
|
150
150
|
// and file tools as the owner. The daemon's authorization block says which
|
|
151
|
-
// turn was granted tools
|
|
152
|
-
//
|
|
153
|
-
//
|
|
151
|
+
// turn was granted tools (BridgeAgent.authorizationFor). A daemon grant names
|
|
152
|
+
// ONE tool, but a granted turn here lifts the veto for every configured tool,
|
|
153
|
+
// so the connector honours a grant ONLY on the owner's own turn: an owner_dm
|
|
154
|
+
// block, or a relayed voice turn the daemon marked relay_speaker "owner"
|
|
155
|
+
// (audit C-B1, r2 R2-T1). OpenClaw has no per-message "no tools"
|
|
154
156
|
// switch the channel can set, so the connector registers a before_tool_call
|
|
155
157
|
// hook and BLOCKS every tool call in a SecureComs session whose turn was not
|
|
156
158
|
// granted tools. If the host gives the plugin no hook API, a turn without a
|
|
@@ -180,9 +182,11 @@ export const NO_TOOLS_TURN_NOTE =
|
|
|
180
182
|
"Tools are disabled for this message because its sender is not this agent's owner in a direct message. Answer in text only and do not try to call any tool.";
|
|
181
183
|
|
|
182
184
|
/**
|
|
183
|
-
* True only when the daemon granted
|
|
184
|
-
* an active sender,
|
|
185
|
-
*
|
|
185
|
+
* True only when the daemon granted the OWNER'S OWN turn at least one tool: a
|
|
186
|
+
* v1 block, an active sender, audience owner_dm or the daemon's
|
|
187
|
+
* relay_speaker "owner" mark (audit C-B1, r2 R2-T1: any other audience gets no
|
|
188
|
+
* tools even with grants), and a non-empty list of `{ tool: <name> }` grants.
|
|
189
|
+
* The audience alone never grants tools (an unpinned or anomalous owner, or the
|
|
186
190
|
* grants kill switch, arrives as owner_dm with no grants). The daemon grants
|
|
187
191
|
* a DM's single human counterpart whom the server marks `is_agent_owner`; the
|
|
188
192
|
* pin is per channel, so a backend that mis-sets that flag would grant a DM
|
|
@@ -192,6 +196,10 @@ export const NO_TOOLS_TURN_NOTE =
|
|
|
192
196
|
export function toolsAuthorized(authorization) {
|
|
193
197
|
if (authorization === null || typeof authorization !== "object") return false;
|
|
194
198
|
if (authorization.version !== 1 || authorization.sender_membership !== "active") return false;
|
|
199
|
+
// Audit C-B1, r2 R2-T1 (2026-10-05): a daemon grant names ONE tool, but a
|
|
200
|
+
// granted turn here lifts the veto for every configured tool. Only the
|
|
201
|
+
// owner's own turn is honoured: its DM, or the daemon's relay_speaker mark.
|
|
202
|
+
if (authorization.audience !== "owner_dm" && authorization.relay_speaker !== "owner") return false;
|
|
195
203
|
const grants = authorization.tool_grants;
|
|
196
204
|
if (!Array.isArray(grants) || grants.length === 0) return false;
|
|
197
205
|
return grants.every((g) => g !== null && typeof g === "object" && typeof g.tool === "string" && g.tool !== "");
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
// packages/connect-agent-bridge/connectors/openclaw/openclaw-entry.toolgate.test.ts
|
|
2
2
|
//
|
|
3
|
-
// Tool gate (security fix 2026-10-05):
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
3
|
+
// Tool gate (security fix 2026-10-05): the daemon's authorization block's
|
|
4
|
+
// tool_grants is non-empty ONLY for the agent's pinned owner in their DM, a
|
|
5
|
+
// relayed voice turn spoken by that owner (marked `relay_speaker: "owner"` by
|
|
6
|
+
// the daemon), or (from PR R on) a same-org human in an internal room. This
|
|
7
|
+
// connector honours a grant ONLY on an `owner_dm` block or a block marked
|
|
8
|
+
// `relay_speaker: "owner"` (audit C-B1, r2 R2-T1). Every other turn (room
|
|
9
|
+
// member, other-org member, another agent, a frame with no or a malformed
|
|
10
|
+
// block) runs with every tool call BLOCKED by the connector's
|
|
11
|
+
// before_tool_call hook. If the OpenClaw host gives the plugin no hook API,
|
|
12
|
+
// such a turn is refused outright (acked, never dispatched). Fail closed.
|
|
11
13
|
//
|
|
12
14
|
// Drives the REAL register() + plugin.gateway.startAccount path with a fake
|
|
13
15
|
// OpenClaw runtime and api, like openclaw-entry.inbound.test.ts.
|
|
@@ -234,7 +236,16 @@ describe("openclaw-entry — tool gate (non-owner senders get no tools)", () =>
|
|
|
234
236
|
it("is true only for a v1 block from an active sender with at least one tool grant", async () => {
|
|
235
237
|
const m = await import("./openclaw-entry.js");
|
|
236
238
|
expect(m.toolsAuthorized(OWNER_AUTH)).toBe(true);
|
|
237
|
-
expect(m.toolsAuthorized({ ...noGrant("agent_to_agent"), tool_grants: GRANTS })).toBe(true); // owner-spoken relay turn
|
|
239
|
+
expect(m.toolsAuthorized({ ...noGrant("agent_to_agent"), tool_grants: GRANTS, relay_speaker: "owner" })).toBe(true); // owner-spoken relay turn (R2-T1)
|
|
240
|
+
expect(m.toolsAuthorized({ ...noGrant("agent_to_agent"), tool_grants: GRANTS })).toBe(false); // no owner relay mark
|
|
241
|
+
expect(m.toolsAuthorized({ ...noGrant("agent_to_agent"), relay_speaker: "owner" })).toBe(false); // the mark alone grants nothing
|
|
242
|
+
// Review R2a fix 1: only the exact string "owner" is the mark; a truthiness check would accept these.
|
|
243
|
+
for (const v of ["Owner", "OWNER", "guest", "", true, 1, ["owner"], { v: "owner" }, "\u043ewner", "owner\u200b"]) {
|
|
244
|
+
expect(m.toolsAuthorized({ ...noGrant("agent_to_agent"), tool_grants: GRANTS, relay_speaker: v }), JSON.stringify(v)).toBe(false);
|
|
245
|
+
}
|
|
246
|
+
for (const aud of ["team_room", "partner_room", "other_human_dm", "unknown"]) {
|
|
247
|
+
expect(m.toolsAuthorized({ ...noGrant(aud), tool_grants: GRANTS }), aud).toBe(false); // audit C-B1
|
|
248
|
+
}
|
|
238
249
|
expect(m.toolsAuthorized(noGrant("owner_dm"))).toBe(false); // kill switch / unpinned owner / lying flag
|
|
239
250
|
expect(m.toolsAuthorized(noGrant("team_room"))).toBe(false);
|
|
240
251
|
expect(m.toolsAuthorized(noGrant("partner_room"))).toBe(false);
|
package/dist/bin.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { BridgeAgent } from "./bridge-agent.js";
|
|
3
|
+
import { createHttpServer } from "./http-server.js";
|
|
4
|
+
import { attachAgentWebSocket } from "./ws-server.js";
|
|
2
5
|
import { type ResolveNodeDeps } from "./service/resolve-node.js";
|
|
3
6
|
import { type Harness } from "./service/wire.js";
|
|
4
7
|
export declare function nodeMajorOk(version: string): boolean;
|
|
@@ -12,3 +15,23 @@ export declare function parseVerb(argv: string[]): "install" | "uninstall" | "st
|
|
|
12
15
|
export declare function slugFromCredentialPath(credentialPath: string): string;
|
|
13
16
|
export declare function chooseInstallExecPath(runningExecPath: string, override: string | undefined, deps: ResolveNodeDeps): string;
|
|
14
17
|
export declare function realCopyConnector(srcDir: string, dstDir: string, harness: Harness): void;
|
|
18
|
+
/**
|
|
19
|
+
* Markdown memory 7a, N-L5: start the agent, THEN create the HTTP server and
|
|
20
|
+
* attach /v1/ws. start() applies the runtime config (and any clear issued
|
|
21
|
+
* while the machine was offline) and warms the restored turns before it
|
|
22
|
+
* resolves, so no route or socket exists while a stale note could be served.
|
|
23
|
+
* main() can listen only on the server this returns. `readTls` runs after
|
|
24
|
+
* start(), as the inline read did.
|
|
25
|
+
*/
|
|
26
|
+
export declare function startThenServe(agent: BridgeAgent, opts: {
|
|
27
|
+
token: string;
|
|
28
|
+
allowOrigins: string[];
|
|
29
|
+
readTls: () => {
|
|
30
|
+
cert: string;
|
|
31
|
+
key: string;
|
|
32
|
+
} | undefined;
|
|
33
|
+
}): Promise<{
|
|
34
|
+
server: ReturnType<typeof createHttpServer>["server"];
|
|
35
|
+
closeAllSseStreams: () => void;
|
|
36
|
+
agentWs: ReturnType<typeof attachAgentWebSocket>;
|
|
37
|
+
}>;
|