@securecoms/agent-bridge 0.30.2 → 0.31.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.
@@ -41,11 +41,13 @@ import { type UnifiedFrameItem } from "./memory/unified/read-book.js";
41
41
  import { type MemoryNoteResultFrame } from "./memory/note-frames.js";
42
42
  import { type ChatMessage } from "./voice/fast-prompt.js";
43
43
  import { VOICE_CALL_START_MARKER } from "./voice/prompt-compiler.js";
44
+ import { type CallBufferTurn } from "./voice/call-buffer.js";
44
45
  import { type VaultListNode, type VaultReadExpectations } from "./vault.js";
45
46
  import type { AgentActionNodeType, AgentActionNodeStatus, AgentActionNodeDetail } from "@agentvault/connect-core";
46
47
  import { removeManagedLlmSource, writeManagedFiles, writeManifestPolicy } from "./service/runtime-config-dir.js";
47
48
  import { bindRuntimeConfig } from "./grants/binding.js";
48
49
  import type { RuntimeReplyReceipt } from "./reply-receipts.js";
50
+ import type { SearchProvider } from "./grants/search-providers.js";
49
51
  export type AgentAudience = "owner_dm" | "team_room" | "partner_room" | "other_human_dm" | "agent_to_agent" | "unknown";
50
52
  export type BridgeToolGrant =
51
53
  /** `source: "manifest"` (owner grants plan 4): issued from an owner-signed manifest; enforced only by a manifest_grants_v1 runtime. */
@@ -68,10 +70,12 @@ export type BridgeToolGrant =
68
70
  * the envelope's authenticated sender is the channel's server-marked relay.
69
71
  *
70
72
  * Tool grants (architect ruling, 2026-10-02): until channel-grant persistence
71
- * lands, the ONLY non-empty grant list is OWNER_DM_DEFAULT_READ_GRANTS
72
- * (`current_time` + `web_search`, read mode), and only on a DM whose sender is
73
- * that DM's pinned human counterpart AND server-marked `is_agent_owner` (or a
74
- * relayed voice turn whose speaker is). The pin is per channel, so the
73
+ * lands, the owner DM's constant grant lists are OWNER_DM_DEFAULT_READ_GRANTS
74
+ * (`current_time` + `web_search`, read mode) on a typed turn whose sender is
75
+ * that DM's pinned human counterpart AND server-marked `is_agent_owner`, and
76
+ * RELAYED_VOICE_GRANTS (equal today, never manifest-eligible) on a relayed
77
+ * voice turn whose speaker is, or on any voice-scoped owner turn (P-5, G-1).
78
+ * The pin is per channel, so the
75
79
  * owner distinction rests on the server's flag: see authorizationFor's LIMIT
76
80
  * note. Every
77
81
  * other audience, an unpinned or anomalous owner, and the kill switch
@@ -100,9 +104,17 @@ export declare const OWNER_DM_DEFAULT_READ_GRANTS: readonly BridgeToolGrant[];
100
104
  */
101
105
  export declare const ROOM_DEFAULT_READ_GRANTS: readonly BridgeToolGrant[];
102
106
  /**
103
- * Kill switch for OWNER_DM_DEFAULT_READ_GRANTS, read once at BridgeAgent
104
- * construction (daemon startup). Unset or exactly `on` keeps the grants;
105
- * any other value (`off`, `0`, `false`, empty) sends `[]` on every turn.
107
+ * Voice fast-path privacy, P-5 (spec §9). The grants of a relayed owner voice turn (and, from Task 4, of every
108
+ * voice-scoped owner turn). A constant of its own, frozen and written out, so a later change to
109
+ * OWNER_DM_DEFAULT_READ_GRANTS cannot widen a spoken turn. It still passes through effectiveToolGrants in
110
+ * authorizationFor, so the owner's signed web_search off-switch holds. Equal to the owner-DM default today.
111
+ */
112
+ export declare const RELAYED_VOICE_GRANTS: readonly BridgeToolGrant[];
113
+ /**
114
+ * Kill switch for OWNER_DM_DEFAULT_READ_GRANTS and RELAYED_VOICE_GRANTS (the
115
+ * relayed owner voice turn and every voice-scoped owner turn), read once at
116
+ * BridgeAgent construction (daemon startup). Unset or exactly `on` keeps the
117
+ * grants; any other value (`off`, `0`, `false`, empty) sends `[]` on every turn.
106
118
  */
107
119
  /** Audit L-2: the owner replay prune runs at most once per this interval. */
108
120
  export declare const OWNER_REPLAY_PRUNE_INTERVAL_MS: number;
@@ -142,7 +154,8 @@ export interface BridgeAuthorization {
142
154
  * owner-DM kill switch is on. Present only when true. An OFFER HINT: the
143
155
  * bridge's W1 to W8 are the gate. Never set on the ownerRelayed branch
144
156
  * (OQ-C, Eric approved 2026-10-06): voice turns read notes, never write,
145
- * and never on any turn with a relay speaker claim (review I-1).
157
+ * and never on any turn with a relay speaker claim (review I-1), nor on a
158
+ * voice-scoped owner-direct turn (G-1, voice fast-path privacy spec §1).
146
159
  * NOT a tool grant: it is not, and must never be, in `tool_grants`.
147
160
  * Reaches only the per-tool runtime consumer: withConsumerGrants strips it
148
161
  * for a harness socket and the HTTP/SSE paths (review m-3).
@@ -277,6 +290,12 @@ export interface BridgeMessage {
277
290
  identityId: string;
278
291
  displayName: string;
279
292
  };
293
+ /**
294
+ * Voice fast-path privacy, Task 4 (spec §1): set when the ingest loop found the turn VOICE-SCOPED (isVoiceScopedTurn:
295
+ * VS-1 relay speaker, VS-2 `transcribed`, VS-3 relay sender, VS-4 call-start marker). Absent on every other turn,
296
+ * including a typed message sent during a live call. A narrowing mark only: it never widens anything.
297
+ */
298
+ voice_scoped?: true;
280
299
  }
281
300
  /**
282
301
  * Task 4 (voice-mode carrier): the one-sentence reply guidance compiled in
@@ -444,6 +463,14 @@ export interface BridgeAgentDeps {
444
463
  * see start(). Injected so tests do not wait a minute.
445
464
  */
446
465
  bootDrainWaitMs?: number;
466
+ /**
467
+ * B-4 (D35): the debounce before the runtime-config refetch that follows a search_providers_v2 capability change.
468
+ * The policy file is rewritten at once; this only bounds the refetch to one per burst. Test seam; production
469
+ * uses {@link SEARCH_CAPABILITY_REFETCH_DEBOUNCE_MS}.
470
+ */
471
+ searchCapabilityRefetchDebounceMs?: number;
472
+ /** B-4 fix round 1: the longest a burst may defer its refetch after its first change. Test seam. */
473
+ searchCapabilityRefetchMaxWaitMs?: number;
447
474
  /**
448
475
  * SEC-06 #18: the wait between in-process reply publish retries after a rate
449
476
  * limit. Defaults to an unref'd setTimeout. Injected so tests do not sleep.
@@ -651,6 +678,22 @@ export interface SendOptions {
651
678
  inReplyTo?: string;
652
679
  /** SEC-06 #18: caller idempotency key (WS path); names the reply part. */
653
680
  idempotencyKey?: string;
681
+ /**
682
+ * Voice fast-path privacy, Task 4b (spec §2.1 agent appends, §2.2 A-3): set ONLY by the ws send handler, computed at
683
+ * frame arrival, for a per-tool SecureComs runtime socket with no harness claim whose `in_reply_to` was in flight on
684
+ * THAT socket (same delivery), for the same channel, and not retired. Only such a reply to a voice-scoped turn may
685
+ * enter the call buffer. `POST /v1/send`, SSE and harness replies never set it.
686
+ */
687
+ callBufferReply?: true;
688
+ }
689
+ /**
690
+ * Task 4b (spec §2.2 A-6): a voice-scoped turn's history, read ONCE at ingest, the buffer seq it ends at and the
691
+ * buffer's reset generation then (final fix M-4: agent lines of a later call never join it).
692
+ */
693
+ export interface VoiceSnapshot {
694
+ recent: CallBufferTurn[];
695
+ endSeq: number;
696
+ generation: number;
654
697
  }
655
698
  /**
656
699
  * B7 (FileSafe vault): a file-backed {@link OwnerAuthorityStore} for the
@@ -785,6 +828,10 @@ export interface AgentActionAPI {
785
828
  export declare const BRIDGE_VERSION: string;
786
829
  /** start() waits this long for its boot drain before it stops blocking on it. */
787
830
  export declare const BOOT_DRAIN_WAIT_MS_DEFAULT = 60000;
831
+ /** B-4 (D35): the refetch after a search_providers_v2 change waits this long, so a reconnect burst (close, then hello) runs one. */
832
+ export declare const SEARCH_CAPABILITY_REFETCH_DEBOUNCE_MS = 1000;
833
+ /** B-4 fix round 1: a flapping socket still refetches no later than this after the first change of a burst. */
834
+ export declare const SEARCH_CAPABILITY_REFETCH_MAX_WAIT_MS = 10000;
788
835
  export declare class BridgeAgent {
789
836
  private readonly cfg;
790
837
  private readonly transport;
@@ -1174,6 +1221,8 @@ export declare class BridgeAgent {
1174
1221
  private bootSweepTimer?;
1175
1222
  private readonly rng;
1176
1223
  private readonly bootDrainWaitMs;
1224
+ private readonly searchCapabilityRefetchDebounceMs;
1225
+ private readonly searchCapabilityRefetchMaxWaitMs;
1177
1226
  /** Resolved (and cleared) each time an ingest cycle settles: see start(). */
1178
1227
  private ingestSettledWaiters;
1179
1228
  /** The boot key-package publish, when start() had to defer it. Test seam. */
@@ -1375,6 +1424,11 @@ export declare class BridgeAgent {
1375
1424
  private startedAt;
1376
1425
  private readonly store;
1377
1426
  private readonly memory;
1427
+ /**
1428
+ * Voice fast-path privacy, Task 4b (spec §2.1, OQ-1 ruled): the current calls' spoken turns, in process only. The
1429
+ * ONLY history a voice-scoped turn sees; read through voiceScopedHistory, once per turn, at ingest.
1430
+ */
1431
+ private readonly callBuffer;
1378
1432
  /**
1379
1433
  * Markdown memory 7a: the one owner of every note mutation and of the
1380
1434
  * delivery cache (spec §6.3, §7.2). One per store, so one per identity.
@@ -2204,6 +2258,18 @@ export declare class BridgeAgent {
2204
2258
  * its own text, never the prior-turn history snapshot, for every provider.
2205
2259
  */
2206
2260
  private mayReadHistoryForTurn;
2261
+ /**
2262
+ * Voice fast-path privacy (spec §2.1, OQ-1 ruled): the ONLY history source of a voice-scoped turn, the call buffer.
2263
+ * Never reads the memory provider, the store, the unified store or the 7b index. A-6: called once per turn, at ingest
2264
+ * (takeVoiceSnapshot), after the reset signals and before the turn's own append.
2265
+ */
2266
+ private voiceScopedHistory;
2267
+ /**
2268
+ * Task 4b (A-6): the turn's snapshot, taken BEFORE its own text is appended (audit finding 2). Final fix I-1: a turn
2269
+ * already admitted to this call (a redelivered row) is snapshotted up to just before its own entry, as it was the
2270
+ * first time, so its snapshot never contains itself.
2271
+ */
2272
+ private takeVoiceSnapshot;
2207
2273
  /** An inbound turn may receive OpenClaw memory only from an active owner DM. */
2208
2274
  private mayReadOpenClawMemoryForTurn;
2209
2275
  /**
@@ -2212,6 +2278,12 @@ export declare class BridgeAgent {
2212
2278
  * never falls back to the runtime's retired plaintext JSON files.
2213
2279
  */
2214
2280
  private memoryForTurn;
2281
+ /**
2282
+ * Task 6 (spec §2.2 A-1, N1): call-buffer entries as runtime-frame turns. Under the voice fence (a pin is loaded) the
2283
+ * fast model's own lines (origin "fast_path") are dropped, as fenceMemory drops them from store history; the fast
2284
+ * prompt keeps them. The internal origin is not carried into the frame; `seq` stays 0 (A-5).
2285
+ */
2286
+ private voiceFrameTurns;
2215
2287
  /**
2216
2288
  * Owner grants plan 4 Task 16 (T-30, audit r2 H-1): the voice fence is on whenever a pin is loaded, on EVERY runtime
2217
2289
  * turn (manifest, fallback, greeting, relayed, constants-only alike). It never depends on a turn's grants.
@@ -2321,6 +2393,12 @@ export declare class BridgeAgent {
2321
2393
  * but lack an epoch give no recall (fail closed). Never throws.
2322
2394
  */
2323
2395
  private prepareRecallForTurn;
2396
+ /**
2397
+ * Voice fast-path privacy, Task 7 (spec §4): the frame-keyed delivery check. A voice-scoped frame gets no notes, no
2398
+ * recall and no unified items, whatever its authorization says. Keys on the frame's own marks, NEVER on the call
2399
+ * buffer's id set (a reset evicts ids; the staged frame must stay scoped).
2400
+ */
2401
+ private frameIsVoiceScoped;
2324
2402
  /** ws flush (synchronous). Q1 to Q3 and Q6 again at delivery, on the ORIGINAL message; Q4 lives in withConsumerRecall. */
2325
2403
  recallForDelivery(m: BridgeMessage): readonly RecallItem[] | undefined;
2326
2404
  /** ws-server: a runtime socket with no harness claim declared memory_recall. */
@@ -2575,7 +2653,13 @@ export declare class BridgeAgent {
2575
2653
  private stageRuntimeMessageInOrder;
2576
2654
  /** Test seam: resolves once every queued fast reply has settled. */
2577
2655
  settleFastReplies(): Promise<void>;
2578
- /** Send a line the voice fast path speaks: its memory record is "voice" (H1). */
2656
+ /**
2657
+ * Send a line the voice fast path speaks: its memory record is "voice" (H1).
2658
+ * Task 4b (spec §2.1 agent appends (1), A-1, A-2): once published, the line enters the call buffer as "fast_path",
2659
+ * unless the turn it answers failed owner verification (its id, or any fragment of a coalesced turn). A line answering
2660
+ * a turn the ingest did not find voice-scoped is not appended either (defense in depth: the fast path fires only on
2661
+ * voice-scoped turns, Task 5).
2662
+ */
2579
2663
  private sendSpoken;
2580
2664
  /** Speak a fixed acknowledgement line; failures are counted, never thrown. */
2581
2665
  private sendFastLine;
@@ -2584,8 +2668,8 @@ export declare class BridgeAgent {
2584
2668
  * the fast model's reply — or, in `ack_only`, the greeting template — is
2585
2669
  * spoken, and the outcome decides whether the runtime answers next.
2586
2670
  *
2587
- * MUST NEVER throw: every failure path (memory lookup, the LLM call,
2588
- * normalization, or the send itself) is caught, counted in
2671
+ * MUST NEVER throw: every failure path (the LLM call, normalization,
2672
+ * or the send itself) is caught, counted in
2589
2673
  * `fastPathFailed`, and logged WITHOUT the turn text or the model's reply.
2590
2674
  * The `fastReplyId(...)` send id is deterministic, so a duplicate that
2591
2675
  * slipped past the in-memory dedupe (e.g. a restart) resolves to the same
@@ -2841,17 +2925,22 @@ export declare class BridgeAgent {
2841
2925
  * guess. The returned object and grant array are frozen before they cross the
2842
2926
  * daemon boundary so one consumer cannot widen a later execution check.
2843
2927
  *
2844
- * Grants (architect ruling 2026-10-02): OWNER_DM_DEFAULT_READ_GRANTS only when
2845
- * the kill switch is on, the channel is a DM, and EITHER
2928
+ * Grants (architect ruling 2026-10-02; P-5 and G-1, spec §9): an owner-DM
2929
+ * constant only when the kill switch is on, the channel is a DM, and EITHER
2846
2930
  * (a) audience is owner_dm AND the sender is the channel's pinned DM
2847
2931
  * counterpart (`channelOwner`, never "anomalous"). A server-marked
2848
2932
  * `is_agent_owner` on a member who is NOT the DM's single human
2849
2933
  * counterpart gets nothing; on the counterpart it is trusted (see
2850
- * LIMIT below); OR
2934
+ * LIMIT below). OWNER_DM_DEFAULT_READ_GRANTS on a typed turn;
2935
+ * RELAYED_VOICE_GRANTS and no memory_write when `opts.voiceScoped`
2936
+ * (G-1: the ingest predicate isVoiceScopedTurn, spec §1); OR
2851
2937
  * (b) the sender is this channel's server-marked relay (`is_relay`) and the
2852
2938
  * relayed `speaker_identity_id` (passed by the caller only for relay
2853
2939
  * turns) is that same pinned owner, whose roster row is the agent's
2854
- * owner. A speaker claim from any non-relay sender gets nothing.
2940
+ * owner: RELAYED_VOICE_GRANTS. A speaker claim from any non-relay
2941
+ * sender gets nothing.
2942
+ * RELAYED_VOICE_GRANTS is never manifest-eligible: effectiveToolGrants gives
2943
+ * the constant by reference, or the fallback when a good manifest is held.
2855
2944
  * Both need the roster fetched at `heldEpoch` (the agent's current MLS
2856
2945
  * epoch for the channel), or the DM marked epoch-fresh at this ingest
2857
2946
  * cycle's refresh (audit M-B4).
@@ -3299,6 +3388,51 @@ export declare class BridgeAgent {
3299
3388
  noteManifestConsumer(token: object): void;
3300
3389
  clearManifestConsumer(token: object): void;
3301
3390
  manifestConsumerAttached(): boolean;
3391
+ /**
3392
+ * D28 (B-4): every socket that said hello, with whether its latest hello declared search_providers_v2 on the
3393
+ * manifest_grants_v1 predicate (ws-server.ts). The agent value is the AND over all of them, absent (false) with
3394
+ * none: unlike manifestConsumer, never newest wins. The per-delivery filter judges each socket by its own hello.
3395
+ */
3396
+ private readonly searchProvidersSockets;
3397
+ /**
3398
+ * The D28 value the files were last recomputed for; null until the first socket observation, so that observation
3399
+ * always recomputes (final review I-1), even when it is non-capable.
3400
+ */
3401
+ private searchProvidersState;
3402
+ private searchCapabilityRefetchTimer;
3403
+ /** When the current burst's first change happened (valid while the timer is set). */
3404
+ private searchCapabilityBurstStart;
3405
+ /** The D28 value in the last report posted (null until one is); a change needs a refetch to reach the web. */
3406
+ private lastReportedSearchProviders;
3407
+ noteSearchProvidersSocket(token: object, declared: boolean): void;
3408
+ clearSearchProvidersSocket(token: object): void;
3409
+ /** D28: every attached runtime socket declared search_providers_v2; no socket attached means absent (false). */
3410
+ searchProvidersCapable(): boolean;
3411
+ /**
3412
+ * D19 item 3 (B-6): the search provider signed in the stored verified manifest in force (a pin loaded and a good
3413
+ * manifest), never the server payload; undefined with no pin, no manifest, or no signed search block. The
3414
+ * per-delivery filter (consumer-grants.ts withSearchCapability) and the S-28 report reason both read this.
3415
+ */
3416
+ signedSearchProvider(): SearchProvider | undefined;
3417
+ /** D19 item 4, D34: the in-force signed provider is not brave, and the attached runtime(s) lack the capability. */
3418
+ private searchProviderUnsupported;
3419
+ /**
3420
+ * D35 (P-4): on a capability change, rewrite manifest-policy.json at once from the stored manifest, the kill
3421
+ * switches and the new capability, then a debounced refetch (one per burst) only when needed.
3422
+ */
3423
+ private onSearchProvidersMaybeChanged;
3424
+ /**
3425
+ * A refetch re-renders skills.json (owner_dm_search_cap counts web_extract only with the capability, D39) and posts
3426
+ * the report. Needed under a manifest in force, or when the reported value is stale. Never before the boot fetch.
3427
+ */
3428
+ private searchCapabilityRefetchNeeded;
3429
+ /**
3430
+ * D35: the local recompute. Widening (web_extract back in) only while the binding for the in-force manifest stands,
3431
+ * so a policy is never written ahead of its skills.json. Narrowing never waits: with no binding in force, a file that
3432
+ * lists web_extract loses it at once, so an older runtime never reads it as unreadable (which drops every grant).
3433
+ * Content-free; a write failure is logged and the debounced refetch tries again.
3434
+ */
3435
+ private recomputeManifestPolicy;
3302
3436
  noteConsumerCapability(state: "ok" | "missing" | "harness_consumer"): void;
3303
3437
  /**
3304
3438
  * Harness re-rule (2026-10-12, audit r2 H-3): true exactly when this agent has a pin loaded and a good
@@ -1,4 +1,5 @@
1
1
  import type { BridgeMessage } from "./bridge-agent.js";
2
+ import type { SearchProvider } from "./grants/search-providers.js";
2
3
  /**
3
4
  * The /v1/ws hello feature a consumer sends to declare per-tool grant
4
5
  * enforcement. The runtime exports the same value (connect-agent-runtime
@@ -23,6 +24,8 @@ export declare const PER_TOOL_GRANTS_FEATURE = "per_tool_grants";
23
24
  export declare function withConsumerGrants(m: BridgeMessage, perToolConsumer: boolean): BridgeMessage;
24
25
  /** The /v1/ws hello feature of a runtime that enforces manifest grants (runtime 0.13.0+). Pinned to the runtime's by capability-parity.test.ts. */
25
26
  export declare const MANIFEST_GRANTS_FEATURE = "manifest_grants_v1";
27
+ /** The /v1/ws hello feature of a runtime that supports the five signed search providers and web_extract (spec D3, D28). Pinned to the runtime's by capability-parity.test.ts. */
28
+ export declare const SEARCH_PROVIDERS_FEATURE = "search_providers_v2";
26
29
  export declare const CONSTANT_TOOL_NAMES: Readonly<Record<"owner_dm" | "team_room", readonly string[]>>;
27
30
  /**
28
31
  * Spec §7.9: a consumer that did not declare manifest_grants_v1 (runtime 0.12.2, a harness,
@@ -40,3 +43,15 @@ export declare function withManifestCapability(m: BridgeMessage, manifestConsume
40
43
  * Never mutates `m`. Callers: the three harness delivery paths, after withConsumerGrants.
41
44
  */
42
45
  export declare function withHarnessGrants(m: BridgeMessage, withheld: boolean): BridgeMessage;
46
+ /**
47
+ * D19 item 3, D34, M-3, P-2 (B-6): the per-delivery search filter, applied on every delivery path AFTER
48
+ * withManifestCapability (and so after withHarnessGrants), judged by the consumer's OWN hello. A consumer that did not
49
+ * declare search_providers_v2 (an older runtime, a harness, every HTTP path) never receives:
50
+ * - a `web_extract` grant, whatever the signed provider (defence in depth beside the policy file, D19 item 1); and
51
+ * - a `web_search` grant, marked OR unmarked (the owner-DM constant and `constants ∩ signed` included), whenever the
52
+ * provider signed in the stored verified manifest is defined and not `brave`. `tavily` too (D34): an older runtime
53
+ * reads a readable install-time Brave key first, so only a signed `brave` cannot reach an unsigned vendor.
54
+ * `signedProvider` comes from the agent's stored verified manifest (BridgeAgent.signedSearchProvider), never the payload.
55
+ * Every other grant and field is unchanged. Returns `m` by reference when nothing is dropped; never mutates `m`.
56
+ */
57
+ export declare function withSearchCapability(m: BridgeMessage, consumerHasFeature: boolean, signedProvider: SearchProvider | undefined): BridgeMessage;
@@ -1,3 +1,17 @@
1
+ /**
2
+ * The config binding and signed-target composer (owner-signed tool grants, spec §7.8, §7.10, §7.12;
3
+ * plan 4 Task 10). One pure function decides every file the bridge writes under a manifest.
4
+ *
5
+ * Rule: under a manifest the bridge writes a widening input only from a signed value, or from the
6
+ * payload when it equals the signed value; it never writes a mismatched widening input. Two ruled
7
+ * exceptions: a mismatched search block is replaced by the last applied good one (rulings item 3), or with none
8
+ * usable by the signed provider alone with no key file (D24 (i)),
9
+ * and llm-source.json is always the bridge's own render of the signed provider and model (item 5).
10
+ *
11
+ * Never throws (audit r1 H-4): every read of server data goes through `guarded`, and a failure is a
12
+ * mismatch of that field with nothing written for it.
13
+ */
14
+ import type { SearchProvider } from "./search-providers.js";
1
15
  import { type ToolGrantManifest } from "./manifest.js";
2
16
  import type { ManifestPolicyDoc } from "../service/runtime-config-dir.js";
3
17
  export declare const CONFIG_BINDING_FIELDS: readonly ["persona", "memory", "search", "tools_allow", "flags", "llm", "files", "legacy_assign"];
@@ -14,7 +28,7 @@ export interface BindPayload {
14
28
  }
15
29
  /** The search block last written under a good search match for this pinned key (Task 11's sealed record); undefined = never. */
16
30
  export type AppliedSearch = {
17
- readonly provider: "brave" | "tavily";
31
+ readonly provider: SearchProvider;
18
32
  readonly keyFile?: string;
19
33
  } | null;
20
34
  export interface BindPrior {
@@ -50,9 +64,23 @@ export declare function legacyAssignToken(d: {
50
64
  fact_hash: string;
51
65
  channel_id: string;
52
66
  }): string;
67
+ /** The manifest's signed tools for one audience, inside the code ceiling; web_extract only with the capability (D19 item 1, D28). */
68
+ export declare function signedManifestTools(manifest: ToolGrantManifest, audience: "owner_dm" | "team_room", searchProvidersCapable: boolean): string[];
69
+ /**
70
+ * manifest-policy.json from the stored manifest, the kill switches and the capability alone (D35): the bridge
71
+ * recomputes it locally on a capability change, without a refetch or a cached BindResult. An audience switched off
72
+ * contributes no tool (review H-1).
73
+ */
74
+ export declare function manifestPolicyFor(manifest: ToolGrantManifest, switches: {
75
+ readonly ownerDm: boolean;
76
+ readonly room: boolean;
77
+ }, searchProvidersCapable: boolean): ManifestPolicyDoc;
53
78
  export declare function bindRuntimeConfig(manifest: ToolGrantManifest, payload: BindPayload, prior: BindPrior,
54
79
  /** Review H-1: the two manifest kill switches; an audience switched off contributes no tool to the policy file. */
55
80
  switches?: {
56
81
  readonly ownerDm: boolean;
57
82
  readonly room: boolean;
58
- }): BindResult;
83
+ },
84
+ /** D19 item 1, D28: every attached runtime socket declared search_providers_v2. Default false = fail closed:
85
+ * an older runtime reads an unknown tool in manifest-policy.json as an unreadable file and drops every grant. */
86
+ searchProvidersCapable?: boolean): BindResult;
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Owner-signed tool grants, step 1 (spec §7.1, §8.1): verify the owner-signed
3
+ * tool-grant manifest wrapper `{ manifest, algo, signature }`.
4
+ *
5
+ * A pure function: an untrusted payload value in, "verified" or one named
6
+ * refusal out. In step 1 a verified manifest is only RECORDED; no grant code
7
+ * reads it (Task 8 pins that).
8
+ */
9
+ import { type SearchProvider } from "./search-providers.js";
1
10
  import { type OwnerGrantPin } from "./pin.js";
2
11
  export declare const TOOL_GRANTS_DOMAIN = "SECURECOMS-TOOL-GRANTS-V1:";
3
12
  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"];
@@ -41,7 +50,8 @@ export interface SignedConfig {
41
50
  readonly model: string;
42
51
  };
43
52
  readonly search?: {
44
- readonly provider: "brave" | "tavily";
53
+ readonly provider: SearchProvider;
54
+ readonly owner_dm_daily_cap?: number;
45
55
  };
46
56
  readonly tools_allow?: readonly string[];
47
57
  readonly flags: readonly string[];
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Search providers the bridge accepts in a signed manifest (spec 2026-10-16 5.1, D2, D36).
3
+ * The ONE bridge copy of the union; a parity test pins it against the runtime's SEARCH_PROVIDERS (ST-38).
4
+ */
5
+ export type SearchProvider = "brave" | "tavily" | "parallel" | "exa" | "firecrawl";
6
+ export declare const SEARCH_PROVIDERS: ReadonlySet<SearchProvider>;
7
+ /** Upper bound of the signed owner-DM daily search cap (D36, approved OQ-7). */
8
+ export declare const OWNER_DM_SEARCH_DAILY_CAP_MAX = 2000;
9
+ export declare function isSearchProvider(value: unknown): value is SearchProvider;
@@ -1,6 +1,7 @@
1
1
  import { type Server } from "node:http";
2
2
  import { z } from "zod";
3
3
  import type { BridgeAgent, BridgeMessage } from "./bridge-agent.js";
4
+ import type { SearchProvider } from "./grants/search-providers.js";
4
5
  /**
5
6
  * #335 handling decision: the throwing-write swallow stays (a dying socket
6
7
  * must cost only that connection's remainder — the caller requeues it and
@@ -26,6 +27,7 @@ export declare function deliverSseBatch(res: Parameters<typeof writeSse>[0], bat
26
27
  ackRuntimeMessages(ids: string[]): void;
27
28
  requeueFront(msgs: BridgeMessage[]): void;
28
29
  harnessGrantsWithheld(): boolean;
30
+ signedSearchProvider(): SearchProvider | undefined;
29
31
  }, onWriteError?: (error: unknown) => void): void;
30
32
  /**
31
33
  * Once-per-connection write-failure reporter (#335). A throwing `res.write`