@securecoms/agent-bridge 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -767,6 +767,38 @@ including its key). Each proxied call is metered server-side
767
767
  `fast_voice_tokens_in`, `fast_voice_tokens_out`), capped at
768
768
  `VOICE_FAST_CALLS_PER_MINUTE` (default 60) per agent.
769
769
 
770
+ ### Calling your own provider directly (`route: "direct"`)
771
+
772
+ To keep the fast-path call off the SecureComs proxy entirely, add
773
+ `"route": "direct"` to the credential file's `fast_path` block:
774
+
775
+ ```json
776
+ "fast_path": { "enabled": true, "route": "direct", "base_url": "https://api.openai.com/v1",
777
+ "model": "gpt-4o-mini", "api_key_env": "SECURECOMS_VOICE_LLM_KEY" }
778
+ ```
779
+
780
+ - With `route: "direct"` the daemon calls `base_url` with the file's own
781
+ `api_key`/`api_key_env`, and never calls `POST /api/v1/voice/fast-complete`,
782
+ even when the settings fetch succeeds. The backend voice settings (persona,
783
+ dials, voice, pace) still apply; only the fast-path provider and key come from
784
+ the file. A later `voice_settings_changed` refetch keeps the direct route.
785
+ - Opt-in lives only in this file. There is no web or backend setting for it,
786
+ and only the exact value `"direct"` counts: a block without `route` (or with
787
+ any other value) behaves as described above, so an old `fast_path` block does
788
+ not switch to direct on upgrade.
789
+ - If the direct provider fails (timeout, HTTP error, bad response), nothing is
790
+ spoken by the fast path and your full agent answers the turn. The daemon never
791
+ retries through the SecureComs proxy.
792
+ - `route: "direct"` without a resolvable key is a configuration error: the
793
+ daemon logs `voice.fast_path_direct_no_key` once at start and runs with no
794
+ fast path (full agent only). It does not fall back to the proxy.
795
+ - The prompt sent to your provider is the same call-scoped prompt the proxy
796
+ would get: the call's own spoken turns only, no typed chat, notes or memory.
797
+ The `x-securecoms-bridge-caps` header is never sent to your provider.
798
+ - Call summaries use the same client: with `route: "direct"` they are also
799
+ generated by your provider, never the SecureComs proxy. With no usable key,
800
+ call summaries are unavailable (as is the fast path).
801
+
770
802
  ---
771
803
 
772
804
  ## Unified memory (0.29.0)
@@ -931,6 +963,17 @@ copied after the owner's next typed message.
931
963
 
932
964
  ---
933
965
 
966
+ ## Release notes: 0.32.0
967
+
968
+ <!-- [COPY, approved by Eric 2026-10-20 ("Notes ok")] -->
969
+
970
+ - Privacy: the agent no longer sends its memory file, persona file text or old memory text to the SecureComs server. It sends only the persona file names and sizes. The server already removed the text it had stored.
971
+ - Optional: an agent's quick spoken replies on calls can go straight from the agent's machine to the owner's own model provider, using a key in the agent's credential file (`voice.fast_path.route: "direct"`). It is off unless you turn it on; with it on, call summaries also use your provider, and without a usable key they are not available.
972
+
973
+ Upgrade: `npm install -g @securecoms/agent-bridge@0.32.0`, then restart the agent.
974
+
975
+ ---
976
+
934
977
  ## Release notes: 0.31.0
935
978
 
936
979
  <!-- [COPY, approved by Eric 2026-10-18 (voice-scope release notes)] -->
package/dist/bin.js CHANGED
@@ -7765,6 +7765,8 @@ var schema = z2.object({
7765
7765
  model: z2.string().min(1),
7766
7766
  api_key: z2.string().min(1).optional(),
7767
7767
  api_key_env: z2.string().min(1).optional(),
7768
+ // E.2: only the exact string "direct" opts in; any other value is ignored (today's proxy behavior), never an error.
7769
+ route: z2.unknown().optional(),
7768
7770
  timeout_ms: z2.number().int().min(500).max(1e4).default(2500),
7769
7771
  max_tokens: z2.number().int().min(16).max(400).default(80)
7770
7772
  })
@@ -7789,7 +7791,8 @@ function parseVoiceConfig(raw, env) {
7789
7791
  model: v.fast_path.model,
7790
7792
  apiKey,
7791
7793
  timeoutMs: v.fast_path.timeout_ms,
7792
- maxTokens: v.fast_path.max_tokens
7794
+ maxTokens: v.fast_path.max_tokens,
7795
+ ...v.fast_path.route === "direct" ? { route: "direct" } : {}
7793
7796
  }
7794
7797
  };
7795
7798
  }
@@ -34662,6 +34665,7 @@ function lineHash(line) {
34662
34665
  return createHash5("sha256").update(line.trim(), "utf8").digest("hex");
34663
34666
  }
34664
34667
  var LOCAL_REPORT_FILE = "local-config.json";
34668
+ var LOCAL_PERSONA_FILE_NAMES = ["IDENTITY.md", "SOUL.md", "AGENTS.md"];
34665
34669
  var SECRET_PATH_COUNT_MAX = 1e4;
34666
34670
  var isCount = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0 && v <= SECRET_PATH_COUNT_MAX;
34667
34671
  function parseSecretPathsReport(raw) {
@@ -34743,18 +34747,23 @@ function readLocalReport(home, identityId) {
34743
34747
  const hasSearchKey = doc.has_search_key === true;
34744
34748
  const hasLlmKey = doc.has_llm_key === true;
34745
34749
  const raw = doc.persona;
34746
- const persona = raw && typeof raw === "object" && !Array.isArray(raw) ? Object.fromEntries(
34747
- Object.entries(raw).filter(([, v]) => typeof v === "string")
34748
- ) : {};
34750
+ const persona = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
34751
+ const personaFiles = [];
34752
+ for (const name of LOCAL_PERSONA_FILE_NAMES) {
34753
+ const text = persona[name];
34754
+ if (typeof text === "string" && text.trim() !== "") {
34755
+ personaFiles.push({ name, bytes: Buffer.byteLength(text, "utf8") });
34756
+ }
34757
+ }
34749
34758
  const capRaw = doc.owner_dm_search_cap_effective;
34750
34759
  const ownerDmCap = typeof capRaw === "number" && Number.isInteger(capRaw) && capRaw >= 0 && capRaw <= OWNER_DM_SEARCH_DAILY_CAP_MAX ? capRaw : void 0;
34751
34760
  return {
34752
- persona,
34761
+ local_persona_files: personaFiles,
34753
34762
  has_search_key: hasSearchKey,
34754
34763
  has_llm_key: hasLlmKey,
34755
34764
  has_extract_provider: doc.has_extract_provider === true,
34756
34765
  ...ownerDmCap !== void 0 ? { owner_dm_search_cap_effective: ownerDmCap } : {},
34757
- legacy_memory: localLegacySources(doc).map(({ path: _path, ...source }) => source),
34766
+ legacy_memory: localLegacySources(doc).map(({ path: _path, content: _content, ...source }) => source),
34758
34767
  secret_paths: parseSecretPathsReport(doc.secret_paths)
34759
34768
  };
34760
34769
  }
@@ -34839,16 +34848,6 @@ async function applyLegacyMemoryFactDecisions(home, identityId, rawDecisions, re
34839
34848
  }
34840
34849
  return { sourcesDeleted, factsAssigned };
34841
34850
  }
34842
- function readManagedMemory(home, identityId) {
34843
- const p = join18(runtimeConfigDir(home, identityId), MEMORY_FILE);
34844
- if (!existsSync4(p)) return null;
34845
- try {
34846
- const body = readFileSync10(p, "utf8").trim();
34847
- return body || null;
34848
- } catch {
34849
- return null;
34850
- }
34851
- }
34852
34851
  function pruneManagedMemory(home, identityId, hashes, deps = REAL_FS) {
34853
34852
  const dir = runtimeConfigDir(home, identityId);
34854
34853
  const path = join18(dir, MEMORY_FILE);
@@ -36057,6 +36056,9 @@ var BridgeAgent = class _BridgeAgent {
36057
36056
  this.injectedFastLlm = !!deps.fastLlm;
36058
36057
  this.fileVoiceConfig = cfg.voice;
36059
36058
  this.fastLlm = deps.fastLlm ?? (cfg.voice?.fastPath.enabled ? new FastLlmClient(cfg.voice.fastPath, this.fetchImpl) : void 0);
36059
+ if (this.fileVoiceConfig?.fastPath.route === "direct" && !this.fileVoiceConfig.fastPath.apiKey) {
36060
+ writeServiceLog("warn", "voice.fast_path_direct_no_key");
36061
+ }
36060
36062
  this.voiceSettingsSource = cfg.voice ? "file" : "none";
36061
36063
  this.latency = deps.latency ?? new LatencyRecorder();
36062
36064
  this.voiceCoalesceWindowMs = Math.max(0, deps.voiceCoalesceWindowMs ?? voiceCoalesceWindowFromEnv());
@@ -36856,6 +36858,7 @@ var BridgeAgent = class _BridgeAgent {
36856
36858
  const s = parseVoiceSettings(await res.json());
36857
36859
  if (s.identityDisplayName) this.lastOwnDisplayName = s.identityDisplayName;
36858
36860
  const proxy = settingsToVoiceConfig(s, { baseUrl: this.cfg.apiBase, apiKey: this.cfg.apiKey });
36861
+ const direct = this.fileVoiceConfig?.fastPath.route === "direct" ? this.fileVoiceConfig.fastPath : void 0;
36859
36862
  if (s.version === 0 && this.fileVoiceConfig) {
36860
36863
  const merged = {
36861
36864
  ...s,
@@ -36864,16 +36867,16 @@ var BridgeAgent = class _BridgeAgent {
36864
36867
  quirks: this.fileVoiceConfig.persona.quirks
36865
36868
  };
36866
36869
  this.voiceSettings = merged;
36867
- this.cfg.voice = { persona: this.fileVoiceConfig.persona, fastPath: proxy.fastPath, settings: merged };
36870
+ this.cfg.voice = { persona: this.fileVoiceConfig.persona, fastPath: direct ?? proxy.fastPath, settings: merged };
36868
36871
  this.voiceSettingsVersion = 0;
36869
36872
  this.voiceSettingsSource = "file";
36870
36873
  } else {
36871
36874
  this.voiceSettings = s;
36872
- this.cfg.voice = proxy;
36875
+ this.cfg.voice = direct ? { ...proxy, fastPath: direct } : proxy;
36873
36876
  this.voiceSettingsVersion = s.version;
36874
36877
  this.voiceSettingsSource = "backend";
36875
36878
  }
36876
- if (!this.injectedFastLlm) {
36879
+ if (!this.injectedFastLlm && !direct) {
36877
36880
  this.fastLlm = new FastLlmClient(this.cfg.voice.fastPath, this.fetchImpl);
36878
36881
  }
36879
36882
  writeServiceLog("info", "voice.settings_loaded", {
@@ -37102,12 +37105,10 @@ var BridgeAgent = class _BridgeAgent {
37102
37105
  const report = {
37103
37106
  applied_version: version,
37104
37107
  paths: checkConfiguredPaths(skillsJson),
37105
- // Read AFTER the prunes above, so the owner's next view of memory is
37106
- // the file as it now stands, not the one they just pruned from.
37107
- memory: readManagedMemory(this.home, this.cfg.identityId),
37108
- // E13: pass through what the runtime reported. The daemon does not
37109
- // interpret any of it.
37110
- local_persona: local?.persona ?? null,
37108
+ // F-0 (G-5): the report carries no content. No MEMORY.md text, no persona text, no legacy
37109
+ // text. E13 needs only to know which of the runtime's own persona files hold something, so
37110
+ // this is [{name, bytes}] for each non-blank file and nothing else.
37111
+ local_persona_files: local?.local_persona_files ?? [],
37111
37112
  // Only the runtime knows whether a search key file is configured — it
37112
37113
  // is an environment value on that process, invisible to the daemon and
37113
37114
  // to the backend. The backend needs it to generate a truthful tool
@@ -37140,9 +37141,9 @@ var BridgeAgent = class _BridgeAgent {
37140
37141
  ...this.reportedClearGeneration(),
37141
37142
  // 7b spec §9.2 (Task 19): capability versus state. Read-only: the report never starts the embedder.
37142
37143
  ...this.reportedRecall(),
37143
- // Inert legacy files only. `readLocalReport` revalidates the current
37144
- // regular-file bytes and strips every local path before this leaves the
37145
- // machine.
37144
+ // Inert legacy files only, metadata only. `readLocalReport` revalidates the current
37145
+ // regular-file bytes and strips every local path and every `content` before this
37146
+ // leaves the machine.
37146
37147
  legacy_memory: local?.legacy_memory ?? [],
37147
37148
  // §B.4: counts of server-named secret paths inside / allowlisted /
37148
37149
  // outside the secrets directory, and the runtime's mode. Numbers and
package/dist/index.js CHANGED
@@ -33659,6 +33659,8 @@ var schema = z2.object({
33659
33659
  model: z2.string().min(1),
33660
33660
  api_key: z2.string().min(1).optional(),
33661
33661
  api_key_env: z2.string().min(1).optional(),
33662
+ // E.2: only the exact string "direct" opts in; any other value is ignored (today's proxy behavior), never an error.
33663
+ route: z2.unknown().optional(),
33662
33664
  timeout_ms: z2.number().int().min(500).max(1e4).default(2500),
33663
33665
  max_tokens: z2.number().int().min(16).max(400).default(80)
33664
33666
  })
@@ -33683,7 +33685,8 @@ function parseVoiceConfig(raw, env) {
33683
33685
  model: v.fast_path.model,
33684
33686
  apiKey,
33685
33687
  timeoutMs: v.fast_path.timeout_ms,
33686
- maxTokens: v.fast_path.max_tokens
33688
+ maxTokens: v.fast_path.max_tokens,
33689
+ ...v.fast_path.route === "direct" ? { route: "direct" } : {}
33687
33690
  }
33688
33691
  };
33689
33692
  }
@@ -34297,6 +34300,7 @@ function lineHash(line) {
34297
34300
  return createHash5("sha256").update(line.trim(), "utf8").digest("hex");
34298
34301
  }
34299
34302
  var LOCAL_REPORT_FILE = "local-config.json";
34303
+ var LOCAL_PERSONA_FILE_NAMES = ["IDENTITY.md", "SOUL.md", "AGENTS.md"];
34300
34304
  var SECRET_PATH_COUNT_MAX = 1e4;
34301
34305
  var isCount = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0 && v <= SECRET_PATH_COUNT_MAX;
34302
34306
  function parseSecretPathsReport(raw) {
@@ -34378,18 +34382,23 @@ function readLocalReport(home, identityId) {
34378
34382
  const hasSearchKey = doc.has_search_key === true;
34379
34383
  const hasLlmKey = doc.has_llm_key === true;
34380
34384
  const raw = doc.persona;
34381
- const persona = raw && typeof raw === "object" && !Array.isArray(raw) ? Object.fromEntries(
34382
- Object.entries(raw).filter(([, v]) => typeof v === "string")
34383
- ) : {};
34385
+ const persona = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
34386
+ const personaFiles = [];
34387
+ for (const name of LOCAL_PERSONA_FILE_NAMES) {
34388
+ const text = persona[name];
34389
+ if (typeof text === "string" && text.trim() !== "") {
34390
+ personaFiles.push({ name, bytes: Buffer.byteLength(text, "utf8") });
34391
+ }
34392
+ }
34384
34393
  const capRaw = doc.owner_dm_search_cap_effective;
34385
34394
  const ownerDmCap = typeof capRaw === "number" && Number.isInteger(capRaw) && capRaw >= 0 && capRaw <= OWNER_DM_SEARCH_DAILY_CAP_MAX ? capRaw : void 0;
34386
34395
  return {
34387
- persona,
34396
+ local_persona_files: personaFiles,
34388
34397
  has_search_key: hasSearchKey,
34389
34398
  has_llm_key: hasLlmKey,
34390
34399
  has_extract_provider: doc.has_extract_provider === true,
34391
34400
  ...ownerDmCap !== void 0 ? { owner_dm_search_cap_effective: ownerDmCap } : {},
34392
- legacy_memory: localLegacySources(doc).map(({ path: _path, ...source }) => source),
34401
+ legacy_memory: localLegacySources(doc).map(({ path: _path, content: _content, ...source }) => source),
34393
34402
  secret_paths: parseSecretPathsReport(doc.secret_paths)
34394
34403
  };
34395
34404
  }
@@ -34474,16 +34483,6 @@ async function applyLegacyMemoryFactDecisions(home, identityId, rawDecisions, re
34474
34483
  }
34475
34484
  return { sourcesDeleted, factsAssigned };
34476
34485
  }
34477
- function readManagedMemory(home, identityId) {
34478
- const p = join15(runtimeConfigDir(home, identityId), MEMORY_FILE);
34479
- if (!existsSync4(p)) return null;
34480
- try {
34481
- const body = readFileSync8(p, "utf8").trim();
34482
- return body || null;
34483
- } catch {
34484
- return null;
34485
- }
34486
- }
34487
34486
  function pruneManagedMemory(home, identityId, hashes, deps = REAL_FS) {
34488
34487
  const dir = runtimeConfigDir(home, identityId);
34489
34488
  const path = join15(dir, MEMORY_FILE);
@@ -35773,6 +35772,9 @@ var BridgeAgent = class _BridgeAgent {
35773
35772
  this.injectedFastLlm = !!deps.fastLlm;
35774
35773
  this.fileVoiceConfig = cfg.voice;
35775
35774
  this.fastLlm = deps.fastLlm ?? (cfg.voice?.fastPath.enabled ? new FastLlmClient(cfg.voice.fastPath, this.fetchImpl) : void 0);
35775
+ if (this.fileVoiceConfig?.fastPath.route === "direct" && !this.fileVoiceConfig.fastPath.apiKey) {
35776
+ writeServiceLog2("warn", "voice.fast_path_direct_no_key");
35777
+ }
35776
35778
  this.voiceSettingsSource = cfg.voice ? "file" : "none";
35777
35779
  this.latency = deps.latency ?? new LatencyRecorder();
35778
35780
  this.voiceCoalesceWindowMs = Math.max(0, deps.voiceCoalesceWindowMs ?? voiceCoalesceWindowFromEnv());
@@ -36572,6 +36574,7 @@ var BridgeAgent = class _BridgeAgent {
36572
36574
  const s = parseVoiceSettings(await res.json());
36573
36575
  if (s.identityDisplayName) this.lastOwnDisplayName = s.identityDisplayName;
36574
36576
  const proxy = settingsToVoiceConfig(s, { baseUrl: this.cfg.apiBase, apiKey: this.cfg.apiKey });
36577
+ const direct = this.fileVoiceConfig?.fastPath.route === "direct" ? this.fileVoiceConfig.fastPath : void 0;
36575
36578
  if (s.version === 0 && this.fileVoiceConfig) {
36576
36579
  const merged = {
36577
36580
  ...s,
@@ -36580,16 +36583,16 @@ var BridgeAgent = class _BridgeAgent {
36580
36583
  quirks: this.fileVoiceConfig.persona.quirks
36581
36584
  };
36582
36585
  this.voiceSettings = merged;
36583
- this.cfg.voice = { persona: this.fileVoiceConfig.persona, fastPath: proxy.fastPath, settings: merged };
36586
+ this.cfg.voice = { persona: this.fileVoiceConfig.persona, fastPath: direct ?? proxy.fastPath, settings: merged };
36584
36587
  this.voiceSettingsVersion = 0;
36585
36588
  this.voiceSettingsSource = "file";
36586
36589
  } else {
36587
36590
  this.voiceSettings = s;
36588
- this.cfg.voice = proxy;
36591
+ this.cfg.voice = direct ? { ...proxy, fastPath: direct } : proxy;
36589
36592
  this.voiceSettingsVersion = s.version;
36590
36593
  this.voiceSettingsSource = "backend";
36591
36594
  }
36592
- if (!this.injectedFastLlm) {
36595
+ if (!this.injectedFastLlm && !direct) {
36593
36596
  this.fastLlm = new FastLlmClient(this.cfg.voice.fastPath, this.fetchImpl);
36594
36597
  }
36595
36598
  writeServiceLog2("info", "voice.settings_loaded", {
@@ -36818,12 +36821,10 @@ var BridgeAgent = class _BridgeAgent {
36818
36821
  const report = {
36819
36822
  applied_version: version,
36820
36823
  paths: checkConfiguredPaths(skillsJson),
36821
- // Read AFTER the prunes above, so the owner's next view of memory is
36822
- // the file as it now stands, not the one they just pruned from.
36823
- memory: readManagedMemory(this.home, this.cfg.identityId),
36824
- // E13: pass through what the runtime reported. The daemon does not
36825
- // interpret any of it.
36826
- local_persona: local?.persona ?? null,
36824
+ // F-0 (G-5): the report carries no content. No MEMORY.md text, no persona text, no legacy
36825
+ // text. E13 needs only to know which of the runtime's own persona files hold something, so
36826
+ // this is [{name, bytes}] for each non-blank file and nothing else.
36827
+ local_persona_files: local?.local_persona_files ?? [],
36827
36828
  // Only the runtime knows whether a search key file is configured — it
36828
36829
  // is an environment value on that process, invisible to the daemon and
36829
36830
  // to the backend. The backend needs it to generate a truthful tool
@@ -36856,9 +36857,9 @@ var BridgeAgent = class _BridgeAgent {
36856
36857
  ...this.reportedClearGeneration(),
36857
36858
  // 7b spec §9.2 (Task 19): capability versus state. Read-only: the report never starts the embedder.
36858
36859
  ...this.reportedRecall(),
36859
- // Inert legacy files only. `readLocalReport` revalidates the current
36860
- // regular-file bytes and strips every local path before this leaves the
36861
- // machine.
36860
+ // Inert legacy files only, metadata only. `readLocalReport` revalidates the current
36861
+ // regular-file bytes and strips every local path and every `content` before this
36862
+ // leaves the machine.
36862
36863
  legacy_memory: local?.legacy_memory ?? [],
36863
36864
  // §B.4: counts of server-named secret paths inside / allowlisted /
36864
36865
  // outside the secrets directory, and the runtime's mode. Numbers and
@@ -46,7 +46,8 @@ export declare const LOCAL_REPORT_FILE = "local-config.json";
46
46
  * and it never affects which file the runtime actually reads.
47
47
  */
48
48
  export interface LocalReport {
49
- persona: Record<string, string>;
49
+ /** F-0 (spec D-3): name and UTF-8 size of each non-blank persona file. The text never leaves readLocalReport. */
50
+ local_persona_files: LocalPersonaFileReport[];
50
51
  has_search_key: boolean;
51
52
  has_llm_key: boolean;
52
53
  /** L-2 (spec §7.9): a readable key for an extract-capable provider. Only a real true counts; absent is false. */
@@ -64,6 +65,11 @@ export interface LocalReport {
64
65
  * so no path (the owner's `allowed_secret_paths` sits in the same file) can
65
66
  * ride along to the backend.
66
67
  */
68
+ export declare const LOCAL_PERSONA_FILE_NAMES: readonly ["IDENTITY.md", "SOUL.md", "AGENTS.md"];
69
+ export interface LocalPersonaFileReport {
70
+ name: (typeof LOCAL_PERSONA_FILE_NAMES)[number];
71
+ bytes: number;
72
+ }
67
73
  export interface SecretPathsReport {
68
74
  mode: "report" | "enforce";
69
75
  inside: number;
@@ -80,11 +86,11 @@ export interface SecretPathsReport {
80
86
  export type LegacyMemoryKind = "global-memory" | "conversation-history" | "captured-memory";
81
87
  export declare const LEGACY_MEMORY_FACT_MAX_BYTES = 1024;
82
88
  export declare const LEGACY_MEMORY_FACT_DECISION_MAX = 500;
89
+ /** What leaves the machine for one legacy source: metadata only (F-0 G-5). Never `content`, never `path`. */
83
90
  export interface LegacyMemoryReport {
84
91
  id: string;
85
92
  kind: LegacyMemoryKind;
86
93
  channel_id?: string;
87
- content: string | null;
88
94
  bytes: number;
89
95
  exportable: boolean;
90
96
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const BRIDGE_VERSION = "0.31.0";
1
+ export declare const BRIDGE_VERSION = "0.32.0";
@@ -28,6 +28,10 @@ export interface FastPathConfig {
28
28
  maxTokens: number;
29
29
  /** Extra request headers. Set only by settingsToVoiceConfig (the SecureComs proxy); never by parseVoiceConfig. */
30
30
  headers?: Readonly<Record<string, string>>;
31
+ /** E.2 (spec §6, OQ-4): the credential file's explicit opt-in to call this provider directly from the agent's
32
+ * machine. Set only by parseVoiceConfig, only for `route: "direct"`; a backend settings fetch then never replaces
33
+ * this config with the SecureComs proxy's. */
34
+ route?: "direct";
31
35
  }
32
36
  export interface VoiceConfig {
33
37
  persona: VoicePersona;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@securecoms/agent-bridge",
3
- "version": "0.31.0",
3
+ "version": "0.32.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"