@securecoms/agent-bridge 0.26.6 → 0.28.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 (54) hide show
  1. package/README.md +119 -0
  2. package/dist/bin.d.ts +24 -1
  3. package/dist/bin.js +4490 -375
  4. package/dist/bridge-agent.d.ts +405 -0
  5. package/dist/config.d.ts +7 -0
  6. package/dist/consumer-grants.d.ts +2 -1
  7. package/dist/embedder-manifest.json +52 -0
  8. package/dist/embedder-worker.js +2898 -0
  9. package/dist/embedder.js +1574 -0
  10. package/dist/index.js +4282 -297
  11. package/dist/memory/consumer-notes.d.ts +34 -0
  12. package/dist/memory/consumer-recall.d.ts +21 -0
  13. package/dist/memory/embedder/client.d.ts +103 -0
  14. package/dist/memory/embedder/download.d.ts +28 -0
  15. package/dist/memory/embedder/embedder-main.d.ts +1 -0
  16. package/dist/memory/embedder/embedder-paths.d.ts +14 -0
  17. package/dist/memory/embedder/engine.d.ts +21 -0
  18. package/dist/memory/embedder/gc.d.ts +2 -0
  19. package/dist/memory/embedder/handshake.d.ts +7 -0
  20. package/dist/memory/embedder/heartbeats.d.ts +4 -0
  21. package/dist/memory/embedder/import-bundle.d.ts +6 -0
  22. package/dist/memory/embedder/inner-source.d.ts +2 -0
  23. package/dist/memory/embedder/layout.d.ts +36 -0
  24. package/dist/memory/embedder/locks.d.ts +17 -0
  25. package/dist/memory/embedder/manifest.d.ts +30 -0
  26. package/dist/memory/embedder/no-dial.d.ts +3 -0
  27. package/dist/memory/embedder/protocol.d.ts +55 -0
  28. package/dist/memory/embedder/run-dir.d.ts +11 -0
  29. package/dist/memory/embedder/scheduler.d.ts +24 -0
  30. package/dist/memory/embedder/server.d.ts +46 -0
  31. package/dist/memory/embedder/session.d.ts +17 -0
  32. package/dist/memory/embedder/spawn.d.ts +40 -0
  33. package/dist/memory/embedder/tokenizer.d.ts +41 -0
  34. package/dist/memory/embedder/types.d.ts +32 -0
  35. package/dist/memory/embedder/windows.d.ts +46 -0
  36. package/dist/memory/embedder/worker-body.d.ts +44 -0
  37. package/dist/memory/embedder/worker.d.ts +1 -0
  38. package/dist/memory/memory-config.d.ts +23 -0
  39. package/dist/memory/note-frames.d.ts +47 -0
  40. package/dist/memory/note-provenance.d.ts +40 -0
  41. package/dist/memory/notes-book.d.ts +187 -0
  42. package/dist/memory/provider.d.ts +2 -0
  43. package/dist/memory/recall/call-heartbeat.d.ts +35 -0
  44. package/dist/memory/recall/membership-purge.d.ts +82 -0
  45. package/dist/memory/recall/rank.d.ts +15 -0
  46. package/dist/memory/recall/recall-book.d.ts +140 -0
  47. package/dist/memory/recall/reembed.d.ts +35 -0
  48. package/dist/memory/secret-scan.d.ts +5 -0
  49. package/dist/memory-taps.d.ts +3 -2
  50. package/dist/ort-wasm-simd-threaded.wasm +0 -0
  51. package/dist/version.d.ts +1 -1
  52. package/dist/voice/fast-prompt.d.ts +5 -0
  53. package/dist/ws-server.d.ts +28 -1
  54. package/package.json +2 -1
@@ -33,6 +33,9 @@ import { type OwnerAuthorityStore, type OwnerPin } from "@agentvault/connect-cli
33
33
  import type { BridgeConfig } from "./config.js";
34
34
  import { LatencyRecorder } from "./latency.js";
35
35
  import type { AttachmentMeta, VaultRefEntry } from "./attachments.js";
36
+ import { type Embedder } from "./memory/embedder/client.js";
37
+ import { type RecallItem, type RecallTapCapture } from "./memory/recall/recall-book.js";
38
+ import { type MemoryNoteResultFrame } from "./memory/note-frames.js";
36
39
  import { type ChatMessage } from "./voice/fast-prompt.js";
37
40
  import { VOICE_CALL_START_MARKER } from "./voice/prompt-compiler.js";
38
41
  import { type VaultListNode, type VaultReadExpectations } from "./vault.js";
@@ -114,7 +117,54 @@ export interface BridgeAuthorization {
114
117
  * audience owner_dm or on this mark, nothing else.
115
118
  */
116
119
  readonly relay_speaker?: "owner";
120
+ /**
121
+ * Markdown memory 7a (spec §8.3, OQ-B): true ONLY on authorizationFor's
122
+ * ownerDirect branch with memoryProvider "securecoms": the channel's pinned
123
+ * owner (never "anomalous") typed this turn in their DM, the roster is
124
+ * fresh at the held epoch and group (or the DM is cycle-fresh), and the
125
+ * owner-DM kill switch is on. Present only when true. An OFFER HINT: the
126
+ * bridge's W1 to W8 are the gate. Never set on the ownerRelayed branch
127
+ * (OQ-C, Eric approved 2026-10-06): voice turns read notes, never write,
128
+ * and never on any turn with a relay speaker claim (review I-1).
129
+ * NOT a tool grant: it is not, and must never be, in `tool_grants`.
130
+ * Reaches only the per-tool runtime consumer: withConsumerGrants strips it
131
+ * for a harness socket and the HTTP/SSE paths (review m-3).
132
+ */
133
+ readonly memory_write?: true;
117
134
  }
135
+ /**
136
+ * Spec §8.2: what ws-server tells applyMemoryNote about the socket a
137
+ * memory_note frame arrived on. Read live: W2 is re-evaluated after awaits.
138
+ */
139
+ export interface MemoryNoteContext {
140
+ /**
141
+ * THIS socket's in-flight record for a message id (the original, never the
142
+ * consumer copy) and the token of that delivery. A nack, backoff and
143
+ * redelivery on the same socket reuses the record object but gets a new
144
+ * token (review m-1).
145
+ */
146
+ lookup(messageId: string): {
147
+ record: BridgeMessage;
148
+ token: number;
149
+ } | undefined;
150
+ /** The socket's own session facts (OQ-J): consumer and the sticky harness claim. */
151
+ readonly session: {
152
+ readonly consumer: "runtime" | "unknown";
153
+ readonly harnessClaimed?: boolean;
154
+ };
155
+ /** The delivery token of the frame's message id, captured when the frame arrived; undefined when not in flight. */
156
+ token: number | undefined;
157
+ /** Per-socket request ids still being decided (M-2). At most MAX_PENDING_NOTE_REQUESTS (review m-3). */
158
+ pendingRequests: Set<string>;
159
+ /**
160
+ * 7b spec §9.1 (H5): the recall ids shown with THIS socket's delivery of `messageId` that carries
161
+ * `token`, else undefined. Task 18 fix round 1 (M2): the caller passes `ctx.token`, the token captured
162
+ * when the frame ARRIVED, never the token read after an await.
163
+ */
164
+ shownRecall(messageId: string, token: number | undefined): ReadonlySet<number> | undefined;
165
+ }
166
+ /** Review m-3: pending memory_note requests per socket; a frame beyond this refuses `invalid` with no echo. */
167
+ export declare const MAX_PENDING_NOTE_REQUESTS = 8;
118
168
  export interface BridgeMessage {
119
169
  channel_id: string;
120
170
  message_id: string;
@@ -142,6 +192,27 @@ export interface BridgeMessage {
142
192
  * @mention filter reads (not recomputed differently).
143
193
  */
144
194
  sender_is_agent?: boolean;
195
+ /**
196
+ * Audit r2 N-L3 (PR-1): this turn is a voice transcript. W8 never quotes
197
+ * such a record. Present only when true. Set when either holds:
198
+ * - the turn is relay-attributed (`speaker` set). `speaker` is honoured
199
+ * only from a sender the server roster marks `is_relay`, so on this path
200
+ * the mark does not depend on the body. The room relay's `sendTurn`
201
+ * (connect-room-relay/src/turns.ts) also sets `transcribed: true`, and
202
+ * the hosted voice worker reaches the bridge only through that relay;
203
+ * - the E2E body says `transcribed: true`. The owner's own web client sets
204
+ * it on an on-device transcript (connect-web MessagingShell.tsx
205
+ * `sendConfirmedVoiceToChannel`, reached from CallPanel's
206
+ * `sendAgentMessage` via `confirmedSendBody`).
207
+ * LIMITATION (spec §15): on the owner-device path the mark is an
208
+ * UNAUTHENTICATED claim in the body. A modified or legacy client, or a
209
+ * device added under the owner's identity by a compromised server (the
210
+ * ADR-0010 gap), can send speech-derived text without it, and the bridge
211
+ * then treats it as typed. That adds no capability (the sender could type
212
+ * the same words), so the mark is fail-closed but not a cryptographic
213
+ * guarantee.
214
+ */
215
+ transcribed?: true;
145
216
  /**
146
217
  * Track 5: materialized attachment metadata for `artifact_share` messages.
147
218
  * Each entry corresponds to one downloaded+decrypted file saved to disk.
@@ -251,6 +322,26 @@ export interface BridgeAgentDeps {
251
322
  * it undefined and gets homedir().
252
323
  */
253
324
  home?: string;
325
+ /**
326
+ * 7b Task 15: the embedder. Tests inject a fake. Undefined in the production construction (`hostMachine`, no
327
+ * `home`) builds the real EmbedderClient; undefined anywhere else gets INERT_EMBEDDER, so no test reaches a real one
328
+ * (Task 19 fix M2: a test that injects neither no longer builds a real client on the developer's homedir()).
329
+ */
330
+ embedder?: Embedder;
331
+ /**
332
+ * 7b Task 19 fix M2: true only in the production construction (bin.ts). This agent owns machine-level state under
333
+ * homedir(): the call heartbeat (~/.securecoms/run/voice-call), the model marker and GC (~/.securecoms/models) and
334
+ * the one-second maintenance timer that drives them. A test that injects `home` gets the same, under its temp home.
335
+ * A construction with neither (most direct test constructions) touches none of those paths.
336
+ *
337
+ * Library callers: omit it and semantic recall stays inert (INERT_EMBEDDER: the report says `preparing` or
338
+ * `off`, no model is downloaded or loaded, no call heartbeat is written and other bridges' background work is
339
+ * never paused by this one). Pass `true` only for the one process per agent that should own those paths, as
340
+ * bin.ts does. host-machine.arch.test.ts pins bin.ts as the only production construction.
341
+ */
342
+ hostMachine?: boolean;
343
+ /** 7b Task 15 (E1 delta M5): injected platform, default process.platform. */
344
+ platform?: NodeJS.Platform;
254
345
  /** Injectable so tests can assert recorded stages; defaults to a fresh one. */
255
346
  latency?: LatencyRecorder;
256
347
  /**
@@ -1063,10 +1154,113 @@ export declare class BridgeAgent {
1063
1154
  /** The ONE follow-up cycle every trigger during the in-flight cycle shares. */
1064
1155
  private ingestFollowUp;
1065
1156
  private runtimeConfigTimer?;
1157
+ /** Semantic memory 7b (spec §5.4, audit r2 H-A): the hourly left-channel purge. */
1158
+ private membershipSweepTimer?;
1159
+ /** 7b Task 15 (spec §5.3): the hourly recall prune, modelled on the membership sweep. */
1160
+ private recallPruneTimer?;
1161
+ private recallPruneWork;
1162
+ private readonly platform;
1163
+ private readonly embedder;
1164
+ /** 7b Task 16: the recall index (the single writer, I8: reached only from tapRecall). */
1165
+ private readonly recall;
1166
+ /** 7b Task 19 (spec §8, H1, ruling P-1): this identity's call heartbeat, and every identity's for the pause. */
1167
+ private readonly heartbeat;
1168
+ /** 7b Task 19 (spec §8): lazy re-embed of pending rows (never an insert; I8 untouched). */
1169
+ private readonly reembed;
1170
+ /** 7b Task 19: the one-second maintenance timer (heartbeat refresh, re-embed, daily marker and GC). */
1171
+ private recallMaintenanceTimer?;
1172
+ private recallMaintenanceWork;
1173
+ private maintenanceTickN;
1174
+ /** The previous re-embed tick did work: poll again next second instead of waiting for the minute. */
1175
+ private reembedHot;
1176
+ private lastModelUpkeepAt;
1177
+ /** Fix L3: the marker is touched on the first tick with recall on, then daily, independent of the GC cadence. */
1178
+ private lastMarkerTouchAt;
1179
+ /** Fix M2: BridgeAgentDeps.hostMachine, or an injected home (see there). */
1180
+ private readonly machinePaths;
1181
+ /** Spec §19 LOW (Federal pre-flight notice): logged once per process. */
1182
+ private modelDownloadNoticeLogged;
1183
+ /**
1184
+ * 7b Task 16 (spec §4 M1, fix H-1): turns restored from runtime_inbox at
1185
+ * boot AND turns the boot drain ingested before the boot refetch settled;
1186
+ * tapped once by reindexRestoredTurns after it.
1187
+ */
1188
+ private readonly deferredRecallIds;
1189
+ /** 7b fix H-2: recall epochs captured at receipt in the ingest cycle, consumed by stageSealed (cleared at cycle end). */
1190
+ private readonly recallReceipt;
1191
+ /** 7b fix H-2: the persisted receipt epochs of each staged turn (like ingestGeneration; deleted at ack). */
1192
+ private readonly recallStageEpochs;
1193
+ /** 7b Task 16: in-flight fire-and-forget index taps; close() waits for them (bounded). */
1194
+ private readonly recallTapWork;
1195
+ /**
1196
+ * Final-review item 2: conversations whose `audience` skip was already logged in this process. A room (or any
1197
+ * conversation the owner-DM gate refuses) refuses every turn the same way, so the line is written once per
1198
+ * conversation per process, not once per inbound message. Bounded: cleared when it reaches 4096 entries.
1199
+ */
1200
+ private readonly recallAudienceLogged;
1201
+ /** Off until a payload arrives (spec §5.3: before the first payload of a process, recall neither indexes nor reads nor purges). */
1202
+ private recallConfig;
1203
+ /** The membership sweep now running, if any; close() waits for it (bounded). */
1204
+ private membershipSweepWork;
1205
+ /** Task 13 fix round LOW 3: when each channel last delivered an inbound row to this process. */
1206
+ private readonly lastInboundAt;
1207
+ private readonly membershipSweeper;
1066
1208
  private readonly listeners;
1067
1209
  private startedAt;
1068
1210
  private readonly store;
1069
1211
  private readonly memory;
1212
+ /**
1213
+ * Markdown memory 7a: the one owner of every note mutation and of the
1214
+ * delivery cache (spec §6.3, §7.2). One per store, so one per identity.
1215
+ */
1216
+ private readonly notes;
1217
+ /**
1218
+ * Fix round 1 (concern 1): the applied clear generation when each pending
1219
+ * runtime turn was FIRST staged (W7b compares it). Keyed by message id,
1220
+ * persisted inside the sealed runtime_inbox payload and restored with it;
1221
+ * never a BridgeMessage field. Removed when the turn is acked or shed.
1222
+ */
1223
+ private readonly ingestGeneration;
1224
+ /** Spec §11.2 (OQ-L): set by refetchRuntimeConfig (Task 8); notes are on until a payload says otherwise. */
1225
+ private memoryConfig;
1226
+ /**
1227
+ * Task 8 fix round 1 (review I-1, N-L5): set once the BOOT refetch has
1228
+ * returned, on success or failure. Until then prepareNotesForTurn only
1229
+ * fills and never runs the legacy import: the boot drain pulls turns before
1230
+ * the boot refetch, and an import there would move facts into notes that a
1231
+ * clear issued while the machine was offline then deletes for good.
1232
+ * warmRestoredNotes runs the import for the buffered (drained) turns after.
1233
+ */
1234
+ private bootConfigSettled;
1235
+ /**
1236
+ * Final review M-1: false until a config refetch has returned a payload in
1237
+ * THIS process. Until then notes are off for reads and writes
1238
+ * (notesProviderOn), whatever memoryConfig says: the last applied payload
1239
+ * may have turned notes off, and that state is not persisted. Distinct from
1240
+ * bootConfigSettled, which turns true when the boot refetch RETURNED, on
1241
+ * success or failure (settled is not notes-on).
1242
+ */
1243
+ private memoryConfigConfirmed;
1244
+ /**
1245
+ * Final review I-1: true once a /v1/ws consumer completed a hello that
1246
+ * declares it reads notes (MEMORY_NOTES_FEATURE) as the runtime, with no
1247
+ * harness claim on that socket (ws-server). A 0.9.x runtime's hello does not
1248
+ * declare it, a harness socket never qualifies, and a socket that sends no
1249
+ * hello never does. Until then no legacy fact is moved into notes (those
1250
+ * consumers recall facts, never notes) and the report says
1251
+ * memory_notes: false. Per process and never reset: a runtime restart
1252
+ * closes the socket but is not a downgrade.
1253
+ */
1254
+ private notesConsumerSeen;
1255
+ /** 7b spec §7.2 Q4: a runtime socket (no harness claim) declared memory_recall at least once in this process (Task 19 reports it). */
1256
+ private recallConsumerSeen;
1257
+ /** Review M-1: the refetch now running, and the ONE follow-up every caller during it shares. */
1258
+ private refetchInFlight;
1259
+ private refetchFollowUp;
1260
+ /** Review M-4: the (payload, applied) pair last logged as regressed. */
1261
+ private regressedLogged;
1262
+ /** Review M-5: an out-of-range applied generation is logged once. */
1263
+ private generationRangeLogged;
1070
1264
  /** Task 2: warn-once-per-channel guard for the enclave-map fallback (below). */
1071
1265
  private enclaveFallbackWarned;
1072
1266
  /** Task 2: dedupe guard so ingestOnce writes a channel's enclave at most once
@@ -1128,6 +1322,7 @@ export declare class BridgeAgent {
1128
1322
  * loose until the next save.
1129
1323
  */
1130
1324
  refetchRuntimeConfig(): Promise<void>;
1325
+ private refetchRuntimeConfigOnce;
1131
1326
  /**
1132
1327
  * Enforce the inbound-buffer cap with a DROP-OLDEST policy.
1133
1328
  *
@@ -1698,6 +1893,17 @@ export declare class BridgeAgent {
1698
1893
  * A timeout rejects with the SDK's `FetchDeadlineError`.
1699
1894
  */
1700
1895
  private fetchBounded;
1896
+ /** H-A rule 4 (audit r1 C2): GET /api/v1/channels/{id}; only 404 is "gone" (404, not 403, for non-members). */
1897
+ private probeChannelMembership;
1898
+ /** §5.4: one conversation's turns, facts, notes and recall, inside the memory mutex, with every in-memory copy. */
1899
+ private purgeConversation;
1900
+ /**
1901
+ * 7b Task 17: the purged conversation's recall cache and pending map
1902
+ * entries go too, inside the memory mutex. The purge already moved the
1903
+ * purge and content epochs inside its transaction, so `warm` and
1904
+ * `deliverable` refuse even before this drop runs (M-A3, M-A4).
1905
+ */
1906
+ private onConversationPurged;
1701
1907
  /** M6 (Task 11): idempotent get-or-create resolve of the enclave's hidden
1702
1908
  * vault channel id — used as the fallback when a caller omits
1703
1909
  * vaultChannelId. Mirrors connect-web's filesafe-api.ts resolveVaultChannel
@@ -1761,6 +1967,7 @@ export declare class BridgeAgent {
1761
1967
  recordMemory(input: import("@agentvault/connect-mls-store").MemoryRecordInput): Promise<{
1762
1968
  seq: number;
1763
1969
  deduped: boolean;
1970
+ dropped?: true;
1764
1971
  }>;
1765
1972
  /**
1766
1973
  * OpenClaw collapses text history and workspace facts into the owner's
@@ -1777,6 +1984,177 @@ export declare class BridgeAgent {
1777
1984
  * never falls back to the runtime's retired plaintext JSON files.
1778
1985
  */
1779
1986
  private memoryForTurn;
1987
+ /**
1988
+ * Spec §11.2 (OQ-L), §6.5: apply one parsed `payload.memory`. An unreadable
1989
+ * value turns notes off and deletes nothing. A readable one goes through
1990
+ * NotesBook.clear, which purges only on a HIGHER generation (under the note
1991
+ * mutex) and resyncs on equal or lower; a lower one logs
1992
+ * memory.clear_generation_regressed. Content-free logs only.
1993
+ *
1994
+ * While a purge is pending (the payload is above the applied generation)
1995
+ * notes are held OFF, and they come back on only after the purge committed.
1996
+ * A purge that throws therefore leaves notes off (fail closed); it is
1997
+ * caught here (review M-2) so the rest of the config still applies and
1998
+ * reports, and the next refetch retries it. A refetch that fails before
1999
+ * this point keeps the last applied state.
2000
+ */
2001
+ private applyPayloadMemory;
2002
+ /** Spec §5.3 (H3): mode state lives in the store; level-triggered after every refetch. Never throws. */
2003
+ private applyRecallModeFromPayload;
2004
+ /** A retention change or a prune removed rows: drop the vector caches. Called only inside notes.exclusive. */
2005
+ private onRecallRetentionChangedLocked;
2006
+ /** Markdown mode deleted every row: drop the caches and the per-message map. Called only inside notes.exclusive. */
2007
+ private onRecallPurgedAllLocked;
2008
+ /**
2009
+ * 7b spec §7.1, §7.2: Q1 (config half) and Q2 here, then the 150 ms bound
2010
+ * inside RecallBook.prepare. Called beside prepareNotesForTurn, before the
2011
+ * turn is staged. The receipt epochs (Task 16 fix H-2) narrow the capture:
2012
+ * the in-cycle receipt, else the persisted staging marks; marks that exist
2013
+ * but lack an epoch give no recall (fail closed). Never throws.
2014
+ */
2015
+ private prepareRecallForTurn;
2016
+ /** ws flush (synchronous). Q1 to Q3 and Q6 again at delivery, on the ORIGINAL message; Q4 lives in withConsumerRecall. */
2017
+ recallForDelivery(m: BridgeMessage): readonly RecallItem[] | undefined;
2018
+ /** ws-server: a runtime socket with no harness claim declared memory_recall. */
2019
+ noteRecallConsumer(): void;
2020
+ /** @internal test seam */
2021
+ recallWarm(c: string): Promise<void>;
2022
+ /** @internal test seam */
2023
+ recallCacheWarm(c: string): boolean;
2024
+ /** @internal test seam */
2025
+ recallDeliverable(id: string, c: string): readonly RecallItem[] | undefined;
2026
+ /** I1 and Q1's config half (spec §4, §7.2): provider, payload semantic, kill switch, store enabled. */
2027
+ private recallProviderOn;
2028
+ /**
2029
+ * Spec §4: the tap's capture, synchronous, taken before the tap's first
2030
+ * await (fact 5: the ingest stamp is deleted at ack). The purge and mode
2031
+ * epochs are the ones persisted at staging, captured at RECEIPT (Task 14,
2032
+ * audit r2 M-A; fix H-2), so a purge or a mode change after receipt, even
2033
+ * across a restart, refuses the insert. A missing value is -1, which no
2034
+ * real epoch equals: the tap skips it (`purged` / `epoch`), fail closed.
2035
+ */
2036
+ captureRecallTap(messageId: string, channelId: string): RecallTapCapture;
2037
+ /** One content-free skip line (reason class only). */
2038
+ private logRecallSkip;
2039
+ /**
2040
+ * The single writer (I8). Gates I1 to I5 here, in spec §4 table order; I6
2041
+ * (capture half) here, and I6, I7, M-A and I9 again inside the store
2042
+ * transaction. A skip logs only its reason class. Never throws.
2043
+ */
2044
+ private tapRecall;
2045
+ /** Fire-and-forget tap from the ingest cycle; tracked so close() and tests can wait for it. */
2046
+ private spawnRecallTap;
2047
+ /** @internal test seam: resolves once every in-flight ingest tap has settled. */
2048
+ settleRecallTaps(): Promise<void>;
2049
+ /**
2050
+ * Spec §4 M1: after the boot refetch settles, tap the deferred turns: those
2051
+ * restored from runtime_inbox (a crash between staging and the tap) and
2052
+ * those the boot drain ingested before the recall config was known (fix
2053
+ * H-1). I9 absorbs a first run that committed. The authorization is the
2054
+ * frozen mark; I1 is re-read here; the capture carries the persisted
2055
+ * receipt epochs (fix H-2). A turn no longer held logs `stamp_missing`.
2056
+ */
2057
+ private reindexRestoredTurns;
2058
+ /**
2059
+ * Spec §5.3 hourly prune; audit r1 H5: inside the memory mutex, and a prune
2060
+ * that deleted rows drops the vector caches. Never throws.
2061
+ * @internal test seam
2062
+ */
2063
+ runRecallPruneTick(): Promise<void>;
2064
+ /**
2065
+ * 7b Task 19, the one-second maintenance tick minus the heartbeat refresh (the timer does that). Never throws.
2066
+ * - Once a day (and on the first tick): touch this identity's model marker while recall is on and the files are
2067
+ * on disk, then GC model trees this manifest does not name and no marker has used for 30 days (spec §6.4 item 6).
2068
+ * - Re-embed (spec §8) while recall is on: every minute, and every second while the previous tick did work.
2069
+ * @internal test seam
2070
+ */
2071
+ runRecallMaintenanceTick(n: number): Promise<void>;
2072
+ /**
2073
+ * Spec §9.2 (Task 19): capability versus state, plus the H6 hold count and the newest completed sweep time
2074
+ * (Task 13 carry). Pure reads: never starts, connects or spawns the embedder (plan audit r1 LOW).
2075
+ */
2076
+ private reportedRecall;
2077
+ private reportedRecallState;
2078
+ /**
2079
+ * Review M-5: the report key only for a value the backend accepts
2080
+ * (0..MAX_CLEAR_GENERATION); otherwise the key is omitted, so one bad
2081
+ * mirror value cannot 422 the whole report, and the event logs once.
2082
+ */
2083
+ private reportedClearGeneration;
2084
+ /**
2085
+ * W1 and R1: the provider is securecoms, a config payload has arrived in
2086
+ * this process (final review M-1), and the parsed payload keeps notes on.
2087
+ */
2088
+ private notesProviderOn;
2089
+ /**
2090
+ * Final review I-1: ws-server calls this when a socket completes a hello as
2091
+ * the runtime (no harness claim) that declares MEMORY_NOTES_FEATURE. The
2092
+ * first one also refetches the runtime config, so the report says
2093
+ * memory_notes: true now rather than at the next 6-hour refetch.
2094
+ */
2095
+ noteNotesConsumer(): void;
2096
+ /**
2097
+ * R2 (spec §7.3): the frozen marks that only the full owner predicate in
2098
+ * authorizationFor sets. `memory_write` (the pinned owner typed this turn)
2099
+ * or `relay_speaker === "owner"` (the pinned owner spoke it through the
2100
+ * relay; read only, never write, OQ-C). Strict `=== true` / `=== "owner"`:
2101
+ * `audience === "owner_dm"` alone is not enough (§0.1 item 7).
2102
+ *
2103
+ * Review I-1 (spec §8.3): the owner-DM kill switch is re-checked HERE, at
2104
+ * read time, and so is the provider (R1). A turn restored from
2105
+ * runtime_inbox carries the marks frozen by the previous process; with
2106
+ * SECURECOMS_OWNER_DM_DEFAULT_GRANTS now off, or the provider no longer
2107
+ * securecoms, it gets no notes, and the boot warm runs no legacy import.
2108
+ */
2109
+ private notesReadable;
2110
+ /** The applied clear generation, for a socket that gets no notes (R5 decided first, review m-2). */
2111
+ notesAppliedGeneration(): number;
2112
+ /**
2113
+ * ws-server flush (synchronous). R1 to R3 here; R5 in withConsumerNotes; R4
2114
+ * holds because nothing else calls this. Reads only the generation-checked
2115
+ * cache (N-M1): a miss gives no notes. `generation` is the applied clear
2116
+ * generation the socket records for W7b.
2117
+ */
2118
+ notesForDelivery(m: BridgeMessage): {
2119
+ notes?: string;
2120
+ generation: number;
2121
+ };
2122
+ /**
2123
+ * §7.2 Warm and §10 import, BEFORE the turn is staged. Never throws.
2124
+ * The import runs only for a `memory_write` turn (M-6: owner-DM channels
2125
+ * only); the turn's own frozen authorization is the proof NotesBook checks.
2126
+ */
2127
+ private prepareNotesForTurn;
2128
+ /**
2129
+ * §7.2 + N-L5: after the boot refetch applied any clear, warm each channel
2130
+ * holding an R2-eligible restored turn, before start() returns (bin.ts
2131
+ * attaches /v1/ws only after start()). `this.buffer` also holds the turns
2132
+ * the boot drain pulled; their prepare ran fill-only (review I-1), so the
2133
+ * legacy import for them runs here, after the purge.
2134
+ */
2135
+ private warmRestoredNotes;
2136
+ /**
2137
+ * Spec §8.2: the write gate for one `memory_note` frame, fail closed, in
2138
+ * table order: W1, well formed (and a request_id not already pending on
2139
+ * this socket, M-2), W2, W3, W4(a), W4(b), W8, W5, then W2 and W1 once more
2140
+ * after the await, then under the note mutex W7, W2 and W1 again after the
2141
+ * seal and right before the synchronous commit, and W7b (the generation
2142
+ * stamped when the turn was first staged, re-checked inside the store
2143
+ * transaction; a turn with no stamp refuses `cleared`). Every refusal logs `memory.note_refused
2144
+ * { reason_class }` and nothing else (§8.6); no note text, frame body or id
2145
+ * is ever logged. Never rejects: an unexpected throw refuses with `store`.
2146
+ *
2147
+ * Everything that decides WHERE and WHETHER a note is written comes from
2148
+ * the bridge-held in-flight record that `ctx.lookup` returns for THIS
2149
+ * socket, never from the frame: the channel (W3), the frozen
2150
+ * authorization (W4(a)), the sender (W4(b)), and the source text and the
2151
+ * `transcribed` mark (W8). The note text that reaches NotesBook is the
2152
+ * owner's own span, produced only by `prepareStoredNote` (W8 then W5).
2153
+ *
2154
+ * LEAF LOCK: heldPosition (W4(b)) is awaited BEFORE the note mutex is
2155
+ * taken; nothing under the mutex awaits it.
2156
+ */
2157
+ applyMemoryNote(raw: Record<string, unknown>, ctx: MemoryNoteContext): Promise<MemoryNoteResultFrame>;
1780
2158
  memoryContext(conversationId: string, limit?: number): Promise<import("@agentvault/connect-mls-store").MemoryContextResult>;
1781
2159
  /**
1782
2160
  * Voice front desk, step 1 — decided SYNCHRONOUSLY inside the ingest cycle,
@@ -2107,6 +2485,8 @@ export declare class BridgeAgent {
2107
2485
  * room_kill_switch SECURECOMS_ROOM_DEFAULT_GRANTS as read at startup
2108
2486
  * per_tool_consumer a consumer that declared per-tool grant
2109
2487
  * enforcement is attached
2488
+ * memory_write the turn carries memory_write (Markdown memory
2489
+ * 7a, spec §8.3): a boolean, never content
2110
2490
  */
2111
2491
  private logAuthzDecision;
2112
2492
  /**
@@ -2264,7 +2644,12 @@ export declare class BridgeAgent {
2264
2644
  * which Date.parse would read as LOCAL time, so a bare timestamp is UTC.
2265
2645
  */
2266
2646
  private recordRecvLag;
2647
+ /**
2648
+ * 7b Task 16 carry: the cycle's receipt epochs are dropped in a `finally`,
2649
+ * so a cycle that throws leaves no stale entry for a later staging to read.
2650
+ */
2267
2651
  private ingestCycle;
2652
+ private ingestCycleBody;
2268
2653
  drain(channelId: string | undefined, waitSeconds: number): Promise<BridgeMessage[]>;
2269
2654
  /** Notify-only SSE signal: fired after a buffer push. Listener pulls via takeBuffered. */
2270
2655
  subscribe(listener: () => void): () => void;
@@ -2377,6 +2762,26 @@ export declare class BridgeAgent {
2377
2762
  isRuntimeMessageRetired(messageId: string): boolean;
2378
2763
  /** Complete the durable handoff only after the runtime explicitly acks. */
2379
2764
  ackRuntimeMessages(messageIds: string[]): void;
2765
+ /**
2766
+ * The sealed runtime_inbox payload: the message plus its ingest stamp and receipt recall epochs (never on the message object).
2767
+ * Task 18 fix round 1: any bridge-private key already on the message is stripped first (the same splitStagedKeys), so
2768
+ * only the bridge's own marks are persisted. A message whose W9 budget is durably spent (agent_memory_w9_spent: an ack
2769
+ * then a backend redelivery) is staged with W9_KEY = W9_MAX, so the budget stays spent.
2770
+ */
2771
+ private stagedPayload;
2772
+ /**
2773
+ * Seal `message` into runtime_inbox with the current ingest stamp and record
2774
+ * the stamp that is actually persisted. Task 7 n-1: a same-id re-stage is
2775
+ * compared on the MESSAGE only, never the stamp, so a clear between two
2776
+ * stagings of one message is not a payload conflict (which would fail the
2777
+ * stage and leave the backend row redelivered forever). The row is not
2778
+ * rewritten, so the FIRST staging's stamp stays authoritative: the owner's
2779
+ * clear still covers a turn first ingested before it. A prior row with no
2780
+ * valid stamp gets none, so its write refuses `cleared`.
2781
+ */
2782
+ private stageSealed;
2783
+ /** In-memory mirror of the persisted marks (staging and restore). */
2784
+ private applyStagedMarks;
2380
2785
  private restoreRuntimeInbox;
2381
2786
  /**
2382
2787
  * Seal a runtime frame before the backend queue id may be acknowledged.
package/dist/config.d.ts CHANGED
@@ -38,6 +38,13 @@ export interface BridgeConfig {
38
38
  * a backend voice-settings refetch replaces `voice` wholesale.
39
39
  */
40
40
  voiceFastPathMode?: FastPathMode;
41
+ /**
42
+ * Spec §6.6 (M6): credential `memory.model_source`. "import_only" stops every model
43
+ * network connection; files arrive only through `models import <dir>`. Local only:
44
+ * the backend cannot set it (it never writes the credential). Anything but the exact
45
+ * string "import_only" is "download".
46
+ */
47
+ modelSource: "download" | "import_only";
41
48
  }
42
49
  export declare const REQUIRED: readonly ["api_key", "identity_id", "device_id", "signing_private_key", "enclave_id", "api_base"];
43
50
  export declare function loadConfig(opts: {
@@ -17,6 +17,7 @@ export declare const PER_TOOL_GRANTS_FEATURE = "per_tool_grants";
17
17
  *
18
18
  * Never mutates `m`: the buffer and in-flight records keep the original, so a
19
19
  * later per-tool consumer still receives it. The stripped copy keeps every
20
- * other field (audience, relay_speaker) as is.
20
+ * other field (audience, relay_speaker) as is, except `memory_write`, which
21
+ * no non-per-tool consumer ever receives (Markdown memory 7a, review m-3).
21
22
  */
22
23
  export declare function withConsumerGrants(m: BridgeMessage, perToolConsumer: boolean): BridgeMessage;
@@ -0,0 +1,52 @@
1
+ {
2
+ "manifest": {
3
+ "modelId": "minilm-l6-v2-int8",
4
+ "dims": 384,
5
+ "maxTokens": 256,
6
+ "pooling": "mean",
7
+ "normalize": true,
8
+ "similarityThreshold": 0.19,
9
+ "onnxInputs": [
10
+ "input_ids",
11
+ "attention_mask",
12
+ "token_type_ids"
13
+ ],
14
+ "onnxOutput": "last_hidden_state",
15
+ "wasmSha256": "ec8580a9d7b9476ceee52e10a7f94124e4dc71a019d666ed6d4726697c109a4d",
16
+ "wasmSize": 13961845,
17
+ "files": [
18
+ {
19
+ "name": "model.onnx",
20
+ "urlPath": "/models/embeddings/minilm-l6-v2-int8/afdb6f1a0e45b715d0bb9b11772f032c399babd23bfc31fed1c170afc848bdb1/model.onnx",
21
+ "sha256": "afdb6f1a0e45b715d0bb9b11772f032c399babd23bfc31fed1c170afc848bdb1",
22
+ "size": 22972370
23
+ },
24
+ {
25
+ "name": "vocab.txt",
26
+ "urlPath": "/models/embeddings/minilm-l6-v2-int8/07eced375cec144d27c900241f3e339478dec958f92fddbc551f295c992038a3/vocab.txt",
27
+ "sha256": "07eced375cec144d27c900241f3e339478dec958f92fddbc551f295c992038a3",
28
+ "size": 231508
29
+ },
30
+ {
31
+ "name": "LICENSE",
32
+ "urlPath": "/models/embeddings/minilm-l6-v2-int8/d83f8f68a86e308e30d1ddf3d1b9984500f0c39ab8a1c7d274ab2799c21155b4/LICENSE",
33
+ "sha256": "d83f8f68a86e308e30d1ddf3d1b9984500f0c39ab8a1c7d274ab2799c21155b4",
34
+ "size": 9934
35
+ },
36
+ {
37
+ "name": "NOTICE",
38
+ "urlPath": "/models/embeddings/minilm-l6-v2-int8/99446995c7800bd9ce033f24c477b00daa50f06da85e441e903f4ce1e32c7507/NOTICE",
39
+ "sha256": "99446995c7800bd9ce033f24c477b00daa50f06da85e441e903f4ce1e32c7507",
40
+ "size": 320
41
+ }
42
+ ]
43
+ },
44
+ "tokenizer": {
45
+ "lowercase": true,
46
+ "stripAccents": true,
47
+ "unkToken": "[UNK]",
48
+ "unknownDropped": false,
49
+ "addSpecialTokens": true,
50
+ "maxInputCharsPerWord": 100
51
+ }
52
+ }