@securecoms/agent-bridge 0.28.2 → 0.30.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 (43) hide show
  1. package/README.md +288 -81
  2. package/dist/bin.d.ts +1 -1
  3. package/dist/bin.js +5112 -508
  4. package/dist/bridge-agent.d.ts +393 -3
  5. package/dist/config.d.ts +9 -0
  6. package/dist/consumer-grants.d.ts +19 -0
  7. package/dist/grants/binding.d.ts +58 -0
  8. package/dist/grants/effective.d.ts +47 -0
  9. package/dist/grants/manifest.d.ts +68 -0
  10. package/dist/grants/owner-turn.d.ts +52 -0
  11. package/dist/grants/persona-render.d.ts +30 -0
  12. package/dist/grants/pin-command.d.ts +31 -0
  13. package/dist/grants/pin.d.ts +35 -0
  14. package/dist/grants/room-search-cap.d.ts +15 -0
  15. package/dist/grants/voice-fence.d.ts +53 -0
  16. package/dist/http-server.d.ts +1 -0
  17. package/dist/index.js +4658 -248
  18. package/dist/memory/note-frames.d.ts +1 -1
  19. package/dist/memory/unified/chunk.d.ts +12 -0
  20. package/dist/memory/unified/config.d.ts +7 -0
  21. package/dist/memory/unified/consumer-unified.d.ts +21 -0
  22. package/dist/memory/unified/escape.d.ts +23 -0
  23. package/dist/memory/unified/index-key.d.ts +8 -0
  24. package/dist/memory/unified/item-book.d.ts +73 -0
  25. package/dist/memory/unified/item-reembed.d.ts +20 -0
  26. package/dist/memory/unified/migrate.d.ts +79 -0
  27. package/dist/memory/unified/owner-scope.d.ts +48 -0
  28. package/dist/memory/unified/pin-rules.d.ts +18 -0
  29. package/dist/memory/unified/ranking-client.d.ts +48 -0
  30. package/dist/memory/unified/ranking-core-constants.d.ts +8 -0
  31. package/dist/memory/unified/ranking-core.d.ts +117 -0
  32. package/dist/memory/unified/ranking-inprocess.d.ts +3 -0
  33. package/dist/memory/unified/ranking-worker.d.ts +1 -0
  34. package/dist/memory/unified/read-book.d.ts +81 -0
  35. package/dist/memory/unified/switch.d.ts +8 -0
  36. package/dist/memory/unified/text.d.ts +15 -0
  37. package/dist/memory/unified/worker-protocol.d.ts +106 -0
  38. package/dist/memory-taps.d.ts +1 -1
  39. package/dist/memory-worker.js +598 -0
  40. package/dist/service/runtime-config-dir.d.ts +15 -0
  41. package/dist/version.d.ts +1 -1
  42. package/dist/ws-server.d.ts +3 -1
  43. package/package.json +1 -1
@@ -0,0 +1,58 @@
1
+ import { type ToolGrantManifest } from "./manifest.js";
2
+ import type { ManifestPolicyDoc } from "../service/runtime-config-dir.js";
3
+ export declare const CONFIG_BINDING_FIELDS: readonly ["persona", "memory", "search", "tools_allow", "flags", "llm", "files", "legacy_assign"];
4
+ export type ConfigBindingField = (typeof CONFIG_BINDING_FIELDS)[number];
5
+ export interface BindPayload {
6
+ readonly version?: number;
7
+ readonly files?: Record<string, string>;
8
+ readonly skills_json?: Record<string, unknown>;
9
+ readonly memory?: unknown;
10
+ readonly persona?: unknown;
11
+ /** Raw, unfiltered saved tokens (runtime_config_service.py memory_prunes). Untrusted. */
12
+ readonly memory_prunes?: unknown;
13
+ readonly legacy_memory_fact_decisions?: unknown;
14
+ }
15
+ /** The search block last written under a good search match for this pinned key (Task 11's sealed record); undefined = never. */
16
+ export type AppliedSearch = {
17
+ readonly provider: "brave" | "tavily";
18
+ readonly keyFile?: string;
19
+ } | null;
20
+ export interface BindPrior {
21
+ readonly search: AppliedSearch | undefined;
22
+ }
23
+ export interface BindResult {
24
+ readonly mismatches: readonly ConfigBindingField[];
25
+ readonly targetMismatches: {
26
+ readonly endpoint: number;
27
+ readonly database: number;
28
+ };
29
+ readonly files: Record<string, string>;
30
+ readonly memory: unknown;
31
+ readonly signedTargets: {
32
+ readonly endpoints: number;
33
+ readonly databases: number;
34
+ };
35
+ readonly policy: ManifestPolicyDoc;
36
+ /** Set only when `search` matched: the block written, to be saved as the new last good one after the write succeeds. */
37
+ readonly appliedSearch: AppliedSearch | undefined;
38
+ /**
39
+ * P-5 (Task 14): the decisions to hand applyLegacyMemoryFactDecisions. On a legacy_assign match: the
40
+ * deletes plus each assign whose token, rebuilt from its own fields, is in the verified saved set. On a
41
+ * mismatch: the deletes only.
42
+ */
43
+ readonly legacyDecisions: unknown[];
44
+ }
45
+ /** P4 OQ-7: hex SHA-256 of JCS(sorted, deduplicated memory_prunes entries that begin "legacy-fact-assign:"). Non-strings are ignored. */
46
+ export declare function legacyAssignSha256(prunes: readonly unknown[]): string;
47
+ /** The backend's assign token (runtime_config_service.py _legacy_memory_fact_decision_token), rebuilt from the fields. */
48
+ export declare function legacyAssignToken(d: {
49
+ source_id: string;
50
+ fact_hash: string;
51
+ channel_id: string;
52
+ }): string;
53
+ export declare function bindRuntimeConfig(manifest: ToolGrantManifest, payload: BindPayload, prior: BindPrior,
54
+ /** Review H-1: the two manifest kill switches; an audience switched off contributes no tool to the policy file. */
55
+ switches?: {
56
+ readonly ownerDm: boolean;
57
+ readonly room: boolean;
58
+ }): BindResult;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The effective grant (owner-signed tool grants, spec §5.2, §5.4, §7.4; plan 4 Task 6).
3
+ * The one formula, pure, so every rule has its own test and the issuing code decides nothing.
4
+ *
5
+ * signed(a) = manifest.grants[a] ∩ TOOL_GRANT_CODE_CEILING[a]
6
+ * effective(a) = signed(a) ∖ disabled, minus http_request with no signed endpoint, minus sql_query
7
+ * with no signed database, minus team_room web_search when capped, ∪ {current_time}
8
+ * (only when every precondition holds: `eligible` and the manifest switch on)
9
+ * fallback(a) = (constants(a) ∩ signed(a)) ∪ {current_time} when a precondition fails
10
+ * = constants(a) by reference with no good manifest
11
+ * [] when today's predicate gives []
12
+ */
13
+ import { type ToolGrantManifest } from "./manifest.js";
14
+ /** Spec §5.4 code ceiling. The same frozen lists Plan 1 uses to drop unknown entries. */
15
+ export declare const TOOL_GRANT_CODE_CEILING: {
16
+ readonly owner_dm: readonly string[];
17
+ readonly team_room: readonly string[];
18
+ };
19
+ export type GrantAudience = "owner_dm" | "team_room";
20
+ export type GrantSource = "constant" | "manifest" | "fallback" | "none";
21
+ export interface ReadGrant {
22
+ readonly tool: string;
23
+ readonly mode: "read";
24
+ readonly source?: "manifest";
25
+ }
26
+ export interface EffectiveInput {
27
+ readonly audience: GrantAudience;
28
+ /** Today's list from the existing predicate: OWNER_DM_DEFAULT_READ_GRANTS, ROOM_DEFAULT_READ_GRANTS, or a frozen []. */
29
+ readonly base: readonly ReadGrant[];
30
+ /** The last good manifest, or null (no pin, or none verified). */
31
+ readonly manifest: ToolGrantManifest | null;
32
+ /** Every manifest-only precondition held (Task 13 computes it). */
33
+ readonly eligible: boolean;
34
+ readonly manifestSwitchOn: boolean;
35
+ readonly disabled: ReadonlySet<string>;
36
+ readonly signedTargets: {
37
+ readonly endpoints: number;
38
+ readonly databases: number;
39
+ };
40
+ readonly roomSearchCapped: boolean;
41
+ }
42
+ export interface EffectiveGrant {
43
+ readonly grants: readonly ReadGrant[];
44
+ readonly source: GrantSource;
45
+ readonly withheldBySearchCap: boolean;
46
+ }
47
+ export declare function effectiveToolGrants(i: EffectiveInput): EffectiveGrant;
@@ -0,0 +1,68 @@
1
+ import { type OwnerGrantPin } from "./pin.js";
2
+ export declare const TOOL_GRANTS_DOMAIN = "SECURECOMS-TOOL-GRANTS-V1:";
3
+ export declare const MANIFEST_STATES: readonly ["ok", "unchanged", "no_pin", "no_manifest", "malformed", "unsupported_algo", "bad_signature", "wrong_agent", "wrong_owner", "rollback", "unknown_entry", "kept_last_good"];
4
+ export type ManifestState = (typeof MANIFEST_STATES)[number];
5
+ /** Known names only for dropping unknown entries (§7.1 step 6). These are the §5.4 code-ceiling names; step 4 uses them as the ceiling. */
6
+ export declare const MANIFEST_KNOWN_TOOLS: {
7
+ readonly owner_dm: readonly string[];
8
+ readonly team_room: readonly string[];
9
+ };
10
+ export interface ToolGrantManifest {
11
+ readonly type: "securecoms.tool-grants";
12
+ readonly v: 1;
13
+ readonly agent_identity_id: string;
14
+ readonly owner_identity_id: string;
15
+ readonly owner_dm_channel_id?: string;
16
+ readonly owner_dm_relay_identity_id?: string;
17
+ readonly counter: number;
18
+ readonly grants: {
19
+ readonly owner_dm?: readonly string[];
20
+ readonly team_room?: readonly string[];
21
+ };
22
+ /** Plan 4 Task 7 (spec §8.1): signed targets. Absent in the wrapper = {}. A `headersFile: ""` is dropped (L-4). */
23
+ readonly endpoints: Readonly<Record<string, SignedEndpoint>>;
24
+ readonly databases: Readonly<Record<string, SignedDatabase>>;
25
+ /** Plan 4 Task 7 (spec §8.1, §7.10): the signed values the config binding compares. Required. */
26
+ readonly config: SignedConfig;
27
+ }
28
+ export interface SignedEndpoint {
29
+ readonly baseUrl: string;
30
+ readonly headersFile?: string;
31
+ }
32
+ export type SignedDatabase = {
33
+ readonly sqlite: string;
34
+ } | {
35
+ readonly postgresUrlFile: string;
36
+ };
37
+ export interface SignedConfig {
38
+ readonly persona_sha256: string;
39
+ readonly llm?: {
40
+ readonly provider: string;
41
+ readonly model: string;
42
+ };
43
+ readonly search?: {
44
+ readonly provider: "brave" | "tavily";
45
+ };
46
+ readonly tools_allow?: readonly string[];
47
+ readonly flags: readonly string[];
48
+ readonly memory: {
49
+ readonly mode: "markdown" | "semantic" | "off";
50
+ readonly recall_days?: 30 | 90 | 365;
51
+ };
52
+ readonly legacy_assign_sha256: string;
53
+ }
54
+ export type ManifestVerdict = {
55
+ state: "ok";
56
+ manifest: ToolGrantManifest;
57
+ droppedUnknown: boolean;
58
+ wrapper: unknown;
59
+ } | {
60
+ state: "unchanged" | "malformed" | "unsupported_algo" | "bad_signature" | "wrong_agent" | "wrong_owner" | "rollback";
61
+ };
62
+ /** utf8(TOOL_GRANTS_DOMAIN) || canonicalize(manifest). The domain keeps an envelope signature from ever verifying as a manifest. */
63
+ export declare function manifestSignedBytes(manifest: unknown): Uint8Array;
64
+ export declare function verifyToolGrantManifest(raw: unknown, ctx: {
65
+ pin: OwnerGrantPin;
66
+ agentIdentityId: string;
67
+ storedCounter: number | null;
68
+ }): ManifestVerdict;
@@ -0,0 +1,52 @@
1
+ import type { AgentVaultEnvelope } from "@agentvault/connect-core";
2
+ import { type OwnerGrantPin } from "./pin.js";
3
+ export declare const OWNER_VERIFY_STATES: readonly ["ok", "bad_signature", "malformed", "unknown_algo", "no_signature", "not_owner_dm_leaves", "wrong_dm_channel", "replay", "verify_error"];
4
+ export type OwnerVerifyFailure = Exclude<(typeof OWNER_VERIFY_STATES)[number], "ok">;
5
+ export type OwnerTurnVerdict = {
6
+ readonly kind: "unchecked";
7
+ } | {
8
+ readonly kind: "verified";
9
+ readonly replay: {
10
+ readonly channelId: string;
11
+ readonly groupId: string;
12
+ readonly epoch: number;
13
+ };
14
+ } | {
15
+ readonly kind: "constants_only";
16
+ readonly replay: {
17
+ readonly channelId: string;
18
+ readonly groupId: string;
19
+ readonly epoch: number;
20
+ };
21
+ } | {
22
+ readonly kind: "failed";
23
+ readonly reason: OwnerVerifyFailure;
24
+ };
25
+ export type OwnerSignatureResult = {
26
+ readonly state: "ok";
27
+ readonly envelope: AgentVaultEnvelope;
28
+ } | {
29
+ readonly state: "no_signature" | "bad_signature" | "malformed" | "unknown_algo";
30
+ };
31
+ export declare function verifyOwnerSignature(entry: {
32
+ readonly payloadHex?: string;
33
+ readonly identitySignature?: string;
34
+ }, pin: OwnerGrantPin): OwnerSignatureResult;
35
+ export type OwnerDmLeafClass = "ok" | "unsigned_relay" | "foreign";
36
+ /**
37
+ * §7.2 check 2, decision D-4. Any number of owner leaves passes (multi-device:
38
+ * leaf credentials encode identity, not device). Fails closed on an empty tree,
39
+ * a non-basic leaf, or no owner leaf. A signed relay is admitted by
40
+ * `relayIdentityId` alone; the roster flag only chooses between
41
+ * `unsigned_relay` (today's constants) and `foreign` (zero), so it can never
42
+ * widen past today's constants.
43
+ */
44
+ export declare function classifyOwnerDmLeaves(leaves: readonly {
45
+ basic: boolean;
46
+ identity: string | null;
47
+ }[], allowed: {
48
+ ownerIdentityId: string;
49
+ agentIdentityId: string;
50
+ relayIdentityId?: string;
51
+ isServerRelay: (identityId: string) => boolean;
52
+ }): OwnerDmLeafClass;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The bridge's persona renderer (owner-signed tool grants, spec §7.12; plan 4 Task 8).
3
+ *
4
+ * Under a manifest the bridge writes IDENTITY.md, SOUL.md and AGENTS.md from the
5
+ * raw persona whose hash the owner signed, never the server's text. It is a port
6
+ * of render_persona (backend/app/services/runtime_config_render.py) and is pinned
7
+ * to it byte for byte by persona-render.vectors.json, which the backend test
8
+ * test_persona_render_vectors.py re-renders with the Python function.
9
+ *
10
+ * Python str.strip() semantics, not JavaScript trim (trim also removes U+FEFF).
11
+ * `tone` is rendered in a fixed field order (TONE_FIELD_ORDER, the JSONB key
12
+ * order the backend reads), never the payload's key order (rulings item 8(a)).
13
+ */
14
+ export declare const PERSONA_MAX_BYTES = 8192;
15
+ export type PersonaRender = {
16
+ readonly ok: true;
17
+ readonly files: {
18
+ "IDENTITY.md": string;
19
+ "SOUL.md": string;
20
+ "AGENTS.md": string;
21
+ };
22
+ } | {
23
+ readonly ok: false;
24
+ readonly reason: "shape" | "over_cap";
25
+ };
26
+ /** Postgres JSONB returns object keys shorter-first, then bytewise; render_persona iterates that order. */
27
+ export declare const TONE_FIELD_ORDER: readonly ["humor", "length", "warmth", "formality"];
28
+ export declare function pyStrip(s: string): string;
29
+ /** A port of render_persona(persona, tool_names). Never throws. */
30
+ export declare function renderPersonaFiles(persona: unknown): PersonaRender;
@@ -0,0 +1,31 @@
1
+ export type PinCommandStep = "pin_tmp_written" | "cred_tmp_written" | "pin_renamed" | "cred_renamed";
2
+ export type PinCommandResult = {
3
+ ok: true;
4
+ changedKey: boolean;
5
+ } | {
6
+ ok: false;
7
+ reason: "pin_invalid" | "credential_invalid" | "symlink_refused" | "agent_mismatch" | "directory_mismatch" | "io_error";
8
+ };
9
+ export declare const PIN_COPY: {
10
+ readonly saved: string;
11
+ readonly pinInvalid: string;
12
+ readonly credentialInvalid: string;
13
+ readonly symlink: string;
14
+ readonly ioError: string;
15
+ readonly agentMismatch: string;
16
+ readonly directoryMismatch: string;
17
+ readonly announce: (agentIdentityId: string) => string;
18
+ readonly usage: string;
19
+ };
20
+ export declare function runPinCommand(opts: {
21
+ credentialPath: string;
22
+ pinSourcePath: string;
23
+ agentIdentityId: string;
24
+ /** Required. Called exactly once, after every check passes and before the first write, with the credential's identity_id. Never called on a refusal. */
25
+ announce: (agentIdentityId: string) => void;
26
+ afterStep?: (s: PinCommandStep) => void;
27
+ }): PinCommandResult;
28
+ export declare function runPinCli(argv: readonly string[], io: {
29
+ out: (line: string) => void;
30
+ err: (line: string) => void;
31
+ }): 0 | 1 | 2;
@@ -0,0 +1,35 @@
1
+ export declare const PIN_FILE_NAME = "owner-signing-key.json";
2
+ export declare const PIN_STATES: readonly ["ok", "no_pin", "pin_conflict", "malformed", "unsupported_algo", "symlink_refused", "unreadable", "server_owner_mismatch"];
3
+ export type PinState = (typeof PIN_STATES)[number];
4
+ export interface OwnerGrantPin {
5
+ readonly ownerIdentityId: string;
6
+ readonly keyAlgo: "ed25519";
7
+ readonly publicKey: Uint8Array;
8
+ readonly publicKeyB64: string;
9
+ }
10
+ export interface OwnerGrantPinResult {
11
+ readonly state: Exclude<PinState, "server_owner_mismatch">;
12
+ readonly pin: OwnerGrantPin | null;
13
+ }
14
+ /** Canonical base64 of exactly `len` bytes: decode, re-encode, compare. */
15
+ export declare function canonicalBase64Bytes(v: unknown, len: number): Uint8Array | null;
16
+ export declare function parsePinObject(v: unknown): {
17
+ ok: true;
18
+ pin: OwnerGrantPin;
19
+ } | {
20
+ ok: false;
21
+ state: "malformed" | "unsupported_algo";
22
+ };
23
+ export interface PinFs {
24
+ lstat(path: string): {
25
+ isSymbolicLink(): boolean;
26
+ mode: number;
27
+ };
28
+ readFile(path: string): string;
29
+ }
30
+ /** Spec §6.5. A symlinked pin file is `symlink_refused` whatever the credential holds. */
31
+ export declare function loadOwnerGrantPin(opts: {
32
+ credentialPath: string;
33
+ credentialJson: Record<string, unknown>;
34
+ fs?: PinFs;
35
+ }): OwnerGrantPinResult;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Owner grants plan 4, Task 12 (spec §7.5, OQ-2, audit r1 L-3): the bridge's copy of the runtime's
3
+ * operator-cap parser (connect-agent-runtime/src/room-search-cap.ts). The two packages share no
4
+ * dependency; bridge-agent.room-search-cap.test.ts pins them equal on every listed input.
5
+ */
6
+ export declare const ROOM_SEARCH_DAILY_CAP = 50;
7
+ export declare const ROOM_SEARCH_CAP_ENV = "SECURECOMS_ROOM_SEARCH_DAILY_CAP";
8
+ /** /^\d+$/ and <= 50 is used (0 = no room search); anything else is 50 with one warning. Never a widening. */
9
+ export declare function parseRoomSearchCap(v: string | undefined, warn?: () => void): number;
10
+ /** Review M-3 (ERIC APPROVALS 3): the per-agent total across rooms, the runtime's parser copied (parity-tested). */
11
+ export declare const AGENT_ROOM_SEARCH_DAILY_CAP = 200;
12
+ export declare const AGENT_ROOM_SEARCH_CAP_ENV = "SECURECOMS_AGENT_ROOM_SEARCH_DAILY_CAP";
13
+ export declare function parseAgentRoomSearchCap(v: string | undefined, warn?: () => void): number;
14
+ /** The store row that holds the per-agent total (never a channel id: channel ids are UUIDs). */
15
+ export declare const AGENT_TOTAL_KEY = "agent-total";
@@ -0,0 +1,53 @@
1
+ /** "untrusted" (review H-3): an agent reply whose turn saw a relay-class line, or that could not be attributed. */
2
+ export type LineOrigin = "fast_path" | "reply" | "untrusted";
3
+ /** 2026-09-07T00:00:00Z: before this the deterministic fast-reply id did not exist (`date -u -r 1788739200`). */
4
+ export declare const FAST_ID_EPOCH_MS = 1788739200000;
5
+ /**
6
+ * The unkeyed fastReplyId of e1369a7c (2026-09-06) to 60365953 (2026-09-25): SHA-256 of `fast:<messageId>`, first 16
7
+ * bytes, RFC 4122 version and variant nibbles, as a hyphenated UUID. Rows of that period carry this form.
8
+ */
9
+ export declare function legacyFastReplyId(messageId: string): string;
10
+ export interface AgentLine {
11
+ readonly origin?: LineOrigin;
12
+ readonly modality?: string;
13
+ readonly messageId?: string;
14
+ readonly ts?: number;
15
+ }
16
+ /** `fastIds`: the keyed AND the legacy fast-reply id of every peer row of the conversation (not the 50-row window). */
17
+ export interface FenceContext {
18
+ readonly fastIds: ReadonlySet<string>;
19
+ }
20
+ /** Rule 4, history. */
21
+ export declare function classifyAgentLine(line: AgentLine, ctx: FenceContext): LineOrigin;
22
+ export interface UnifiedItemFacts {
23
+ readonly channelId: string;
24
+ readonly source: string;
25
+ readonly originId: string;
26
+ readonly role?: "peer" | "agent";
27
+ readonly origin?: LineOrigin;
28
+ readonly sourceRef?: string;
29
+ }
30
+ export type TraceRow = ({
31
+ readonly role: "peer" | "agent";
32
+ readonly peerId?: string;
33
+ } & AgentLine) | null;
34
+ /**
35
+ * Review H-3 (ruling): a peer line is relay-class when its authenticated sender is not the pinned owner identity
36
+ * (the relay, incl. owner-speaker voice transcripts) or its modality is voice.
37
+ */
38
+ export declare function isRelayClassPeer(line: {
39
+ readonly peerId?: string;
40
+ readonly modality?: string;
41
+ }, ownerId: string): boolean;
42
+ export type Trace = (conversationId: string, at: {
43
+ seq?: number;
44
+ messageId?: string;
45
+ }) => TraceRow;
46
+ /**
47
+ * Rule 4, unified. `strict` (review H-3): a turn that holds a manifest grant, which also excludes owner-voice items
48
+ * and imported peer lines that trace to a relay-class row (or cannot be traced).
49
+ */
50
+ export declare function classifyUnifiedItem(item: UnifiedItemFacts, trace: Trace, ctx: FenceContext, strict?: {
51
+ readonly ownerId: string;
52
+ readonly turnChannelId?: string;
53
+ }, keepUntrusted?: boolean): "keep" | "exclude";
@@ -25,6 +25,7 @@ export declare function writeSse(res: {
25
25
  export declare function deliverSseBatch(res: Parameters<typeof writeSse>[0], batch: BridgeMessage[], agent: {
26
26
  ackRuntimeMessages(ids: string[]): void;
27
27
  requeueFront(msgs: BridgeMessage[]): void;
28
+ harnessGrantsWithheld(): boolean;
28
29
  }, onWriteError?: (error: unknown) => void): void;
29
30
  /**
30
31
  * Once-per-connection write-failure reporter (#335). A throwing `res.write`