@securecoms/agent-bridge 0.28.2 → 0.29.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.
@@ -1,6 +1,6 @@
1
1
  import { MlsGroupKeyProvider } from "@agentvault/connect-sdk-ts";
2
2
  import type { HarnessReport } from "./harness-report.js";
3
- import type { MemoryContextResult } from "@agentvault/connect-mls-store";
3
+ import type { ItemCapture, MemoryContextResult } from "@agentvault/connect-mls-store";
4
4
  /**
5
5
  * M6 (ADR-0013 Decision H): what the sweep observed about one channel.
6
6
  *
@@ -35,6 +35,9 @@ import { LatencyRecorder } from "./latency.js";
35
35
  import type { AttachmentMeta, VaultRefEntry } from "./attachments.js";
36
36
  import { type Embedder } from "./memory/embedder/client.js";
37
37
  import { type RecallItem, type RecallTapCapture } from "./memory/recall/recall-book.js";
38
+ import { type RankingTransport } from "./memory/unified/ranking-client.js";
39
+ import type { WorkerInit } from "./memory/unified/worker-protocol.js";
40
+ import { type UnifiedFrameItem } from "./memory/unified/read-book.js";
38
41
  import { type MemoryNoteResultFrame } from "./memory/note-frames.js";
39
42
  import { type ChatMessage } from "./voice/fast-prompt.js";
40
43
  import { VOICE_CALL_START_MARKER } from "./voice/prompt-compiler.js";
@@ -162,6 +165,8 @@ export interface MemoryNoteContext {
162
165
  * when the frame ARRIVED, never the token read after an await.
163
166
  */
164
167
  shownRecall(messageId: string, token: number | undefined): ReadonlySet<number> | undefined;
168
+ /** Plan Task 27: the unified item ids shown with THIS socket's delivery of `messageId` that carries `token`, else undefined. */
169
+ shownUnified(messageId: string, token: number | undefined): ReadonlySet<number> | undefined;
165
170
  }
166
171
  /** Review m-3: pending memory_note requests per socket; a frame beyond this refuses `invalid` with no echo. */
167
172
  export declare const MAX_PENDING_NOTE_REQUESTS = 8;
@@ -312,6 +317,16 @@ export declare const ROOM_VOICE_GUIDANCE_WITH_RESPONDER = "You are in a group vo
312
317
  */
313
318
  export declare function selectVoiceGuidance(isDm: boolean | undefined, voiceDefaultResponder: boolean | undefined): string;
314
319
  export interface BridgeAgentDeps {
320
+ /**
321
+ * Unified memory part 1 (plan Task 8): TEST SEAM ONLY. Production (bin.ts) never passes it, so the compiled
322
+ * UNIFIED_MEMORY_PART1 decides (switch.arch.test.ts pins both).
323
+ */
324
+ unifiedMemoryPart1?: boolean;
325
+ /**
326
+ * Unified memory: TEST SEAM. Production builds the real worker from dist/memory-worker.js (Task 27 pins that bin.ts
327
+ * never passes it).
328
+ */
329
+ rankingTransport?: (init: WorkerInit) => RankingTransport;
315
330
  transport?: any;
316
331
  backend?: any;
317
332
  fetchImpl?: typeof fetch;
@@ -448,6 +463,22 @@ export declare const VOICE_COALESCE_WINDOW_MS = 0;
448
463
  export declare const VOICE_COALESCE_WINDOW_MAX_MS = 5000;
449
464
  /** `SECURECOMS_VOICE_COALESCE_MS` as integer ms, clamped to VOICE_COALESCE_WINDOW_MAX_MS; anything else is 0 (disabled). */
450
465
  export declare function voiceCoalesceWindowFromEnv(env?: NodeJS.ProcessEnv): number;
466
+ /**
467
+ * The shape of a turn the voice coalescing window takes, minus the window size: a server-set voice-mode turn whose relay
468
+ * speaker was verified against the roster, transcribed, with voice guidance, and not the call-start marker. The window
469
+ * (`coalescible`) and the owner-voice writer on the non-window path (R-U7 as amended by Eric 2026-10-11, "Fix voice")
470
+ * both use this one predicate, so the two cannot drift. It says nothing about WHO spoke: tapUnifiedVoice keeps every
471
+ * writer gate (relay_speaker === "owner" frozen at receipt, the stamp, the receipt purge epoch, the write-time owner scope).
472
+ */
473
+ export declare function voiceWindowEligible(t: {
474
+ voiceMode: boolean | undefined;
475
+ speaker: {
476
+ identityId: string;
477
+ } | undefined;
478
+ transcribed: unknown;
479
+ voiceGuidance: string | undefined;
480
+ isCallStart: boolean;
481
+ }): boolean;
451
482
  /** Default inbound-buffer cap (messages) when deps.maxBuffer is unset. */
452
483
  export declare const DEFAULT_MAX_BUFFER = 1000;
453
484
  /**
@@ -1163,6 +1194,20 @@ export declare class BridgeAgent {
1163
1194
  private readonly embedder;
1164
1195
  /** 7b Task 16: the recall index (the single writer, I8: reached only from tapRecall). */
1165
1196
  private readonly recall;
1197
+ private readonly unifiedOn;
1198
+ /** Unified memory (plan Task 21, spec section 11.5): the token-MAC index key, derived from the identity seed. */
1199
+ private readonly indexKey;
1200
+ /** Unified memory (plan Task 21, U15): the ranking worker's client. Lazy: no worker starts until the first request. */
1201
+ private readonly ranking;
1202
+ /** Unified memory (plan Task 23, U9): the single writer. Every item write is a named call site of `this.items.commit`. */
1203
+ private readonly items;
1204
+ /** Unified memory (plan Task 26, C14): background embeds of pending items. Built in the constructor, after `this.ranking` exists. */
1205
+ private readonly itemReembed;
1206
+ private itemReembedHot;
1207
+ /** Unified memory (plan Task 27, spec section 8.1): the per-turn owner-DM read. Built only with the part 1 switch on. */
1208
+ private unifiedReads;
1209
+ /** Plan Task 27: a runtime socket with no harness claim declared memory_unified. */
1210
+ private unifiedConsumerSeen;
1166
1211
  /** 7b Task 19 (spec §8, H1, ruling P-1): this identity's call heartbeat, and every identity's for the pause. */
1167
1212
  private readonly heartbeat;
1168
1213
  /** 7b Task 19 (spec §8): lazy re-embed of pending rows (never an insert; I8 untouched). */
@@ -1192,6 +1237,12 @@ export declare class BridgeAgent {
1192
1237
  private readonly recallStageEpochs;
1193
1238
  /** 7b Task 16: in-flight fire-and-forget index taps; close() waits for them (bounded). */
1194
1239
  private readonly recallTapWork;
1240
+ /** Unified memory (plan Task 23): in-flight fire-and-forget item writes; close() and tests wait for them (bounded). */
1241
+ private readonly unifiedWork;
1242
+ /** Unified memory (plan Task 30, OQ-P1): conversations whose migration this process has started (or finished). */
1243
+ private readonly migrationStarted;
1244
+ /** Unified memory (plan Task 23, the 7b M1/H-1 rule): turns restored at boot or drained before the boot config settled. */
1245
+ private readonly deferredUnifiedIds;
1195
1246
  /**
1196
1247
  * §13.5 live check (2026-10-09, cold start): set when an apply turns recall on (the boot apply included), cleared
1197
1248
  * once the maintenance tick has started the DM prewarm, or when recall is off at a tick.
@@ -1211,13 +1262,26 @@ export declare class BridgeAgent {
1211
1262
  * conversation per process, not once per inbound message. Bounded: cleared when it reaches 4096 entries.
1212
1263
  */
1213
1264
  private readonly recallAudienceLogged;
1265
+ /** Plan Task 23 fix round 1: the unified tap's `audience` skip, logged once per conversation per process (the recallAudienceLogged rule). */
1266
+ private readonly unifiedAudienceLogged;
1214
1267
  /** Off until a payload arrives (spec §5.3: before the first payload of a process, recall neither indexes nor reads nor purges). */
1215
1268
  private recallConfig;
1269
+ /** Unified memory (U14, R-U20): the parsed payload's switch; off until a payload arrives. */
1270
+ private unifiedConfig;
1271
+ /** Unified memory (U14): the store's unified_enabled as last committed by applyUnifiedPayload; false on any failure. */
1272
+ private unifiedEnabledMirror;
1216
1273
  /** The membership sweep now running, if any; close() waits for it (bounded). */
1217
1274
  private membershipSweepWork;
1218
1275
  /** Task 13 fix round LOW 3: when each channel last delivered an inbound row to this process. */
1219
1276
  private readonly lastInboundAt;
1220
1277
  private readonly membershipSweeper;
1278
+ /**
1279
+ * Unified memory (plan Task 22, spec section 8.2; H-4, H-A, CR-1): freeze and purge of an owner-DM scope on owner change.
1280
+ * Roster facts only (OQ-P3); the persisted is_dm, never a stale in-memory one (controller ruling C8). The absence record
1281
+ * reuses agent_memory_conversation keyed by the scope id: it holds a NUL, a channel id never does, so the keys cannot
1282
+ * collide, and memoryConversationIds reads the content tables only, so the membership sweep never sees it.
1283
+ */
1284
+ private readonly ownerScopes;
1221
1285
  private readonly listeners;
1222
1286
  private startedAt;
1223
1287
  private readonly store;
@@ -1791,6 +1855,15 @@ export declare class BridgeAgent {
1791
1855
  private enforceBodyRateFloor;
1792
1856
  /** Bookkeeping after a CONFIRMED publish of `body` under `id`. */
1793
1857
  private afterPublish;
1858
+ /**
1859
+ * Final review bridge I-1 (ledger T25): the agent reply's U11 capture, taken BEFORE the publish await. The clear generation
1860
+ * is the reply turn's ingest stamp (the in-reply-to message's), so a Clear all applied while the model generated the
1861
+ * reply refuses it `cleared`; the enable epoch, room-clear generation and purge epoch are read here, before the publish.
1862
+ * No in-reply-to, or no stamp held for it: null, and the reply is not remembered (fail closed). Switch off: null, no read.
1863
+ */
1864
+ private unifiedReplyCapture;
1865
+ /** Section 7.1 agent reply, section 24.2: the owner-DM predicate for the channel at write (no sender), else nothing. Reference trust always (U18). OQ-P2: bridge-authored Pinned:/Forgot: lines are filtered by the caller. */
1866
+ private spawnUnifiedReply;
1794
1867
  private isIdempotentSend;
1795
1868
  /**
1796
1869
  * SEC-06 #18: publish a reply part idempotently.
@@ -1917,6 +1990,23 @@ export declare class BridgeAgent {
1917
1990
  * `deliverable` refuse even before this drop runs (M-A3, M-A4).
1918
1991
  */
1919
1992
  private onConversationPurged;
1993
+ /**
1994
+ * The hourly sweeps (spec section 5.4; plan Task 22): the membership sweep, then, with unified memory compiled on, the
1995
+ * owner-DM scope sweep. Each failure is logged content-free; neither escapes. Controller ruling (Task 22 fix round 1):
1996
+ * the owner sweep is NOT gated on memory being on: "off does not purge" protects the owner's own memory from the switch;
1997
+ * an owner change is a security event and purges whatever the mode.
1998
+ */
1999
+ private runMembershipSweeps;
2000
+ /** Plan Task 22: one frozen owner-DM scope, pins included, with its read entries; the ranking worker reloads. */
2001
+ private purgeOwnerScope;
2002
+ /** Final review bridge I-2(c): a hot-set notification after a committed store change; a throw is logged, never escapes. */
2003
+ private rankingNote;
2004
+ /** H-4 facts for one channel, from the caches only (no network). */
2005
+ private ownerFacts;
2006
+ /** The owner-DM scope id now, or null (unknown counts as null: fail closed for reads and writes). */
2007
+ private ownerScopeNow;
2008
+ /** Section 24.2: the agent-reply writer's predicate: the scope now, and the roster fresh at the held MLS position or DM cycle-fresh. */
2009
+ private ownerScopeForWrite;
1920
2010
  /** M6 (Task 11): idempotent get-or-create resolve of the enclave's hidden
1921
2011
  * vault channel id — used as the fallback when a caller omits
1922
2012
  * vaultChannelId. Mirrors connect-web's filesafe-api.ts resolveVaultChannel
@@ -2012,6 +2102,23 @@ export declare class BridgeAgent {
2012
2102
  * this point keeps the last applied state.
2013
2103
  */
2014
2104
  private applyPayloadMemory;
2105
+ /**
2106
+ * Spec section 13 (R-U20, M-7, U14). Deletions first, whatever the mode; then the switch. Never throws.
2107
+ * Fix round 1 (controller ruling, U14 fail closed, 7a M-2 precedent): a clear that throws while the payload is above
2108
+ * the applied generation (or a room clear above that room's) holds unified memory OFF until a later refetch applies it.
2109
+ */
2110
+ private applyUnifiedPayload;
2111
+ /** Fix round 1: after a clear threw, is the payload still above what the store applied? An unreadable store is pending (fail closed). */
2112
+ private unifiedClearPending;
2113
+ /** section 14.3 (M-3): the database file plus its WAL (free pages are inside the file). A number, content-free. */
2114
+ private unifiedDbBytes;
2115
+ /**
2116
+ * Plan Task 31 (Task 33 follow-up): the unified report keys, only while the compiled part 1 switch is on. A store read
2117
+ * that throws omits these keys alone, so the rest of the machine report still posts; the log carries the error class only.
2118
+ */
2119
+ private reportedUnified;
2120
+ /** U14, R-U20: the switch, provider securecoms, a payload confirmed, the payload on, the local key on, the store enabled. No SQL. */
2121
+ private unifiedProviderOn;
2015
2122
  /** Spec §5.3 (H3): mode state lives in the store; level-triggered after every refetch. Never throws. */
2016
2123
  private applyRecallModeFromPayload;
2017
2124
  /**
@@ -2079,6 +2186,54 @@ export declare class BridgeAgent {
2079
2186
  private spawnRecallTap;
2080
2187
  /** @internal test seam: resolves once every in-flight ingest tap has settled. */
2081
2188
  settleRecallTaps(): Promise<void>;
2189
+ /** @internal test seam (plan Task 23): resolves once every in-flight unified item write has settled. */
2190
+ settleUnifiedWrites(): Promise<void>;
2191
+ /**
2192
+ * U11: synchronous, the first statement of every unified write. The purge epoch is the receipt one (7b fix H-2). With
2193
+ * the ingest cycle's receipt snapshot (final review bridge M-1), the enable epoch and room-clear generation are the
2194
+ * receipt values too, exactly as for the voice writer; the boot restore re-run has none and reads them now.
2195
+ */
2196
+ captureUnifiedTap(messageId: string, channelId: string, receipt?: ItemCapture): {
2197
+ stamp: number | undefined;
2198
+ capture: ItemCapture;
2199
+ };
2200
+ /**
2201
+ * Section 7.1 owner typed, section 7.2 item 1: the frozen memory_write mark, typed (not transcribed, not voice), and the
2202
+ * owner scope from ownerScopeForWrite (H-4: the scope comes only from the owner predicate at write, never from the
2203
+ * audience alone). The capture was taken before this method's first await (U11). Logs are content-free (U17). Never throws.
2204
+ */
2205
+ private tapUnifiedTyped;
2206
+ /**
2207
+ * Section 7.2 item 2 (R-U7, E-1; amended by Eric 2026-10-11, "Fix voice"): exactly ownerRelayed (relay_speaker === "owner",
2208
+ * frozen at receipt) AND a window-eligible turn (voiceWindowEligible), whatever the window size. Two call sites: the voice
2209
+ * window flush, and the ingest tap block when the window did not take the turn (off, 0 by default). Like the typed tap, the write-time owner scope's pinned identity must be the speaking identity;
2210
+ * otherwise nothing is written (H-4). Logs are content-free (U17). Never throws.
2211
+ */
2212
+ private tapUnifiedVoice;
2213
+ /** Section 8.1 steps 1 to 7, before the turn is staged. H-4: the frozen owner predicate and the owner scope now. Never throws. */
2214
+ private prepareUnifiedForTurn;
2215
+ /**
2216
+ * Plan Task 30 (spec section 14.1, M-1, OQ-P1): migrate this one conversation in the background. The owner identity comes
2217
+ * from ownerScopeForWrite (the pinned owner, roster fresh at write; never the audience alone, H-4) and the classification
2218
+ * from the persisted isDm: non-DM is excluded (M-4); no persisted flag or no owner scope is unknown, and an unknown or
2219
+ * stopped run (U11) is retried by a later owner turn.
2220
+ */
2221
+ private startUnifiedMigration;
2222
+ /** ws flush (synchronous). The predicate and the owner scope again at delivery, on the ORIGINAL message (H-4, Review Focus 3). */
2223
+ unifiedForDelivery(m: BridgeMessage): {
2224
+ items: UnifiedFrameItem[];
2225
+ pinnedMore: number;
2226
+ } | undefined;
2227
+ /** ws-server: the hello echoes memory_unified only with the part 1 switch compiled on. */
2228
+ unifiedFeatureOn(): boolean;
2229
+ /** ws-server: a runtime socket with no harness claim declared memory_unified. */
2230
+ noteUnifiedConsumer(): void;
2231
+ /** One content-free unified-tap skip line (reason class only, U17). */
2232
+ private logUnifiedTapSkip;
2233
+ /** Fire-and-forget unified tap from the ingest cycle; tracked so close() and tests can wait for it. */
2234
+ private spawnUnifiedTap;
2235
+ /** The 7b M1/H-1 rule for the unified writer: turns staged before the boot config settled, tapped once it has. A turn no longer held is skipped. */
2236
+ private reindexUnifiedRestored;
2082
2237
  /**
2083
2238
  * Spec §4 M1: after the boot refetch settles, tap the deferred turns: those
2084
2239
  * restored from runtime_inbox (a crash between staging and the tap) and
@@ -2188,6 +2343,24 @@ export declare class BridgeAgent {
2188
2343
  * taken; nothing under the mutex awaits it.
2189
2344
  */
2190
2345
  applyMemoryNote(raw: Record<string, unknown>, ctx: MemoryNoteContext): Promise<MemoryNoteResultFrame>;
2346
+ /**
2347
+ * Spec section 7.4 (pins) and section 14.4 (forget, Task 29), under unified memory. Order: W1, well formed, W2, W3,
2348
+ * W4(a), W4(b), then for a pin: the C-1 allowlist (with R2-L-1), H-1 coverage on the stripped bytes, W5 (secret), the
2349
+ * 400-character cap, W7b (the ingest stamp), the commit under U11 with W1/W2 re-checked first inside the mutex (the
2350
+ * 200-pin cap is in the store transaction), then the bridge-authored `Pinned:` line. `replace` is refused (OQ-P4,
2351
+ * fail closed). Every reason sent is a key of the runtime's UNIFIED_REFUSAL.
2352
+ */
2353
+ private applyUnifiedNote;
2354
+ /**
2355
+ * Spec section 14.4 ("Forget that", M-5), U13, R-U21; plan Task 29. Reached from applyUnifiedNote after W1 to W4(b) and
2356
+ * the owner scope check (owner DM only, a typed owner turn; voice never forgets, R-U7). Only an `r<id>` of an item shown
2357
+ * with THIS delivery of THIS message on THIS socket; its delivered text must still be known (plan audit M-6) before the
2358
+ * message's one destructive operation (W9) is prepared. The delete (item group, matching 7a/7b row, 7b tombstone for a
2359
+ * message origin, W9 spend) is one store transaction under the memory mutex, with W1/W2 re-checked first; the read book
2360
+ * is invalidated in the same mutex hold, the item leaves the ranking hot set, then the bridge publishes `Forgot:`
2361
+ * (OQ-P2: afterPublish keeps the line out of history and the items). Every reason is a key of UNIFIED_REFUSAL.
2362
+ */
2363
+ private forgetUnified;
2191
2364
  memoryContext(conversationId: string, limit?: number): Promise<import("@agentvault/connect-mls-store").MemoryContextResult>;
2192
2365
  /**
2193
2366
  * Voice front desk, step 1 — decided SYNCHRONOUSLY inside the ingest cycle,
@@ -2913,6 +3086,16 @@ export declare class BridgeAgent {
2913
3086
  /** 7b Task 27 Step 1: bytes held by this bridge's warm recall vector caches. A number, content-free; the fleet
2914
3087
  * RAM script (scripts/ops/semantic-memory-ram-budget.mjs) sums it with the shared embedder's RSS. */
2915
3088
  recall_cache_bytes: number;
3089
+ /** Plan Task 27 (unified memory; present only with the part 1 switch compiled on): the ranking worker's hot-set bytes,
3090
+ * owner-DM turns whose read missed its deadline (U15; one per turn, counted by the read book whether the deadline was
3091
+ * spent in the embedder, the worker or a worker crash), owner-DM reads started, and this process id (fleet RAM script). */
3092
+ memory_hot_bytes?: number;
3093
+ memory_deadline_misses?: number;
3094
+ memory_reads?: number;
3095
+ /** Fix round 2: owner-DM reads not kept because a drop or a pin insert moved the read book's generation during them
3096
+ * (counted apart from memory_deadline_misses, which is deadline misses only). */
3097
+ memory_reads_discarded?: number;
3098
+ pid?: number;
2916
3099
  /** INV-CUSTODY-DURABLE §5.C.3: a decrypted, un-acked row is memory-only
2917
3100
  * (its durable write failed); a restart now would lose it. */
2918
3101
  custody_persist_failing: boolean;
package/dist/config.d.ts CHANGED
@@ -45,6 +45,8 @@ export interface BridgeConfig {
45
45
  * string "import_only" is "download".
46
46
  */
47
47
  modelSource: "download" | "import_only";
48
+ /** Spec section 13 (R-U20): credential `memory.enabled`. Absent or true is on; any other value is off (plan I-9). Local only: the backend cannot set it. */
49
+ memoryEnabled?: boolean;
48
50
  }
49
51
  export declare const REQUIRED: readonly ["api_key", "identity_id", "device_id", "signing_private_key", "enclave_id", "api_base"];
50
52
  export declare function loadConfig(opts: {