@agenticmail/core 0.9.37 → 0.9.39

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.
@@ -575,8 +575,8 @@ function validateSkill(s) {
575
575
  if (typeof sk.contributed_by !== "string") errs.push({ path: "contributed_by", message: "must be a string" });
576
576
  return errs;
577
577
  }
578
- function summarize(s) {
579
- return {
578
+ function summarize(s, score) {
579
+ const out = {
580
580
  id: s.id,
581
581
  name: s.name,
582
582
  category: s.category,
@@ -584,8 +584,12 @@ function summarize(s) {
584
584
  description: s.description,
585
585
  version: s.version,
586
586
  disclaimer_required: !!s.disclaimer,
587
- estimated_call_duration_minutes: s.context.estimated_call_duration_minutes
587
+ estimated_call_duration_minutes: s.context.estimated_call_duration_minutes,
588
+ when_to_use: s.context.when_to_use,
589
+ first_principle: s.principles && s.principles.length > 0 ? s.principles[0] : ""
588
590
  };
591
+ if (score !== void 0) out.score = score;
592
+ return out;
589
593
  }
590
594
  function listSkills(opts = {}) {
591
595
  const all = Array.from(ensureLoaded().byId.values());
@@ -606,17 +610,17 @@ function searchSkills(query, limit = 20) {
606
610
  const fallback = [];
607
611
  for (const s of byId.values()) {
608
612
  if (s.id.toLowerCase().includes(qLow) || s.name.toLowerCase().includes(qLow) || s.tags.some((t) => t.toLowerCase().includes(qLow))) {
609
- fallback.push(summarize(s));
613
+ fallback.push(summarize(s, 0.1));
610
614
  if (fallback.length >= limit) break;
611
615
  }
612
616
  }
613
617
  return fallback;
614
618
  }
615
619
  const out = [];
616
- for (const { id } of ranked) {
620
+ for (const { id, score } of ranked) {
617
621
  const skill = byId.get(id);
618
622
  if (!skill) continue;
619
- out.push(summarize(skill));
623
+ out.push(summarize(skill, score));
620
624
  if (out.length >= limit) break;
621
625
  }
622
626
  return out;
package/dist/index.cjs CHANGED
@@ -1280,8 +1280,8 @@ function validateSkill(s) {
1280
1280
  if (typeof sk.contributed_by !== "string") errs.push({ path: "contributed_by", message: "must be a string" });
1281
1281
  return errs;
1282
1282
  }
1283
- function summarize(s) {
1284
- return {
1283
+ function summarize(s, score) {
1284
+ const out = {
1285
1285
  id: s.id,
1286
1286
  name: s.name,
1287
1287
  category: s.category,
@@ -1289,8 +1289,12 @@ function summarize(s) {
1289
1289
  description: s.description,
1290
1290
  version: s.version,
1291
1291
  disclaimer_required: !!s.disclaimer,
1292
- estimated_call_duration_minutes: s.context.estimated_call_duration_minutes
1292
+ estimated_call_duration_minutes: s.context.estimated_call_duration_minutes,
1293
+ when_to_use: s.context.when_to_use,
1294
+ first_principle: s.principles && s.principles.length > 0 ? s.principles[0] : ""
1293
1295
  };
1296
+ if (score !== void 0) out.score = score;
1297
+ return out;
1294
1298
  }
1295
1299
  function listSkills(opts = {}) {
1296
1300
  const all = Array.from(ensureLoaded().byId.values());
@@ -1311,17 +1315,17 @@ function searchSkills(query, limit = 20) {
1311
1315
  const fallback = [];
1312
1316
  for (const s of byId.values()) {
1313
1317
  if (s.id.toLowerCase().includes(qLow) || s.name.toLowerCase().includes(qLow) || s.tags.some((t) => t.toLowerCase().includes(qLow))) {
1314
- fallback.push(summarize(s));
1318
+ fallback.push(summarize(s, 0.1));
1315
1319
  if (fallback.length >= limit) break;
1316
1320
  }
1317
1321
  }
1318
1322
  return fallback;
1319
1323
  }
1320
1324
  const out = [];
1321
- for (const { id } of ranked) {
1325
+ for (const { id, score } of ranked) {
1322
1326
  const skill = byId.get(id);
1323
1327
  if (!skill) continue;
1324
- out.push(summarize(skill));
1328
+ out.push(summarize(skill, score));
1325
1329
  if (out.length >= limit) break;
1326
1330
  }
1327
1331
  return out;
@@ -1606,6 +1610,7 @@ __export(index_exports, {
1606
1610
  getTelegramMe: () => getTelegramMe,
1607
1611
  getTelegramUpdates: () => getTelegramUpdates,
1608
1612
  getTelegramWebhookInfo: () => getTelegramWebhookInfo,
1613
+ getVoiceProvider: () => getVoiceProvider,
1609
1614
  hostSessionStoragePath: () => hostSessionStoragePath,
1610
1615
  inferPhoneRegion: () => inferPhoneRegion,
1611
1616
  invalidateSkillCache: () => invalidateSkillCache,
@@ -1618,6 +1623,7 @@ __export(index_exports, {
1618
1623
  isTelegramStopCommand: () => isTelegramStopCommand,
1619
1624
  isValidPhoneNumber: () => isValidPhoneNumber,
1620
1625
  listSkills: () => listSkills,
1626
+ listVoiceProviders: () => listVoiceProviders,
1621
1627
  loadAgentPersona: () => loadAgentPersona,
1622
1628
  loadHostSession: () => loadHostSession,
1623
1629
  loadSkill: () => loadSkill,
@@ -1646,6 +1652,7 @@ __export(index_exports, {
1646
1652
  redactSecret: () => redactSecret,
1647
1653
  redactSmsConfig: () => redactSmsConfig,
1648
1654
  redactTelegramConfig: () => redactTelegramConfig,
1655
+ registerVoiceProvider: () => registerVoiceProvider,
1649
1656
  renderSkillAsPrompt: () => renderSkillAsPrompt,
1650
1657
  requireBinary: () => requireBinary,
1651
1658
  requireWhisperModel: () => requireWhisperModel,
@@ -1653,6 +1660,7 @@ __export(index_exports, {
1653
1660
  resolveConfig: () => resolveConfig,
1654
1661
  resolveExtensionPolicy: () => resolveExtensionPolicy,
1655
1662
  resolveTlsRejectUnauthorized: () => resolveTlsRejectUnauthorized,
1663
+ resolveVoiceRuntime: () => resolveVoiceRuntime,
1656
1664
  safeJoin: () => safeJoin,
1657
1665
  sanitizeEmail: () => sanitizeEmail,
1658
1666
  saveAgentPersona: () => saveAgentPersona,
@@ -2584,6 +2592,13 @@ function resolveConfig(overrides) {
2584
2592
  dataDir: env.AGENTICMAIL_DATA_DIR?.replace(/^~(?=\/|$)/, (0, import_node_os.homedir)()) ?? DEFAULT_CONFIG.dataDir
2585
2593
  };
2586
2594
  if (env.OPENAI_API_KEY) config.openaiApiKey = env.OPENAI_API_KEY;
2595
+ if (env.XAI_API_KEY) {
2596
+ config.voiceProviderKeys = config.voiceProviderKeys ?? {};
2597
+ config.voiceProviderKeys.grok = env.XAI_API_KEY;
2598
+ }
2599
+ if (env.AGENTICMAIL_VOICE_RUNTIME && env.AGENTICMAIL_VOICE_RUNTIME.trim()) {
2600
+ config.voiceRuntime = env.AGENTICMAIL_VOICE_RUNTIME.trim();
2601
+ }
2587
2602
  const configPath = (0, import_node_path.join)(config.dataDir, "config.json");
2588
2603
  if ((0, import_node_fs.existsSync)(configPath)) {
2589
2604
  try {
@@ -8678,6 +8693,73 @@ var PhoneManager = class {
8678
8693
  }]);
8679
8694
  return { mission: updated, query: answered, alreadyAnswered: false };
8680
8695
  }
8696
+ /**
8697
+ * v0.9.92 — auto-close stale operator queries.
8698
+ *
8699
+ * An operator query is "stale" when:
8700
+ * - it's unanswered, AND
8701
+ * - either (a) the mission is no longer live (status in
8702
+ * {completed, failed, cancelled}), OR (b) the query is older
8703
+ * than `maxAgeSeconds`.
8704
+ *
8705
+ * The sweeper marks each stale query as answered with a synthetic
8706
+ * "[auto-closed: ...]" answer + `answeredVia: 'auto-sweeper'`. This
8707
+ * (a) gets the query out of `listOpenOperatorQueries`, (b) clears
8708
+ * the bridge's "you have N open questions" hint to the operator,
8709
+ * and (c) leaves a permanent audit trail of WHY the query was
8710
+ * closed.
8711
+ *
8712
+ * Returns the count of queries closed + a per-mission breakdown
8713
+ * for logging.
8714
+ */
8715
+ sweepStaleOperatorQueries(opts = {}) {
8716
+ const maxAgeMs = (opts.maxAgeSeconds ?? 3600) * 1e3;
8717
+ const nowMs = opts.nowMs ?? Date.now();
8718
+ const TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
8719
+ const rows = this.db.prepare(
8720
+ `SELECT * FROM phone_missions WHERE metadata_json LIKE '%"operatorQueries"%'`
8721
+ ).all();
8722
+ const breakdown = [];
8723
+ let totalClosed = 0;
8724
+ let missionsTouched = 0;
8725
+ for (const row of rows) {
8726
+ const mission = rowToMission(row);
8727
+ const queries = readOperatorQueries(mission);
8728
+ const open = queries.filter((q) => !q.answer);
8729
+ if (open.length === 0) continue;
8730
+ const reason = TERMINAL.has(mission.status) ? "mission-terminal" : null;
8731
+ const closedAt = new Date(nowMs).toISOString();
8732
+ const nextQueries = queries.map((q) => {
8733
+ if (q.answer) return q;
8734
+ const askedAtMs = Date.parse(q.askedAt);
8735
+ const tooOld = Number.isFinite(askedAtMs) && nowMs - askedAtMs >= maxAgeMs;
8736
+ const queryReason = reason ?? (tooOld ? "age" : null);
8737
+ if (!queryReason) return q;
8738
+ return {
8739
+ ...q,
8740
+ answer: queryReason === "mission-terminal" ? `[auto-closed: mission ended (${mission.status}) before this question was answered]` : `[auto-closed: question went unanswered for over ${Math.round(maxAgeMs / 6e4)} minutes]`,
8741
+ answeredAt: closedAt,
8742
+ answeredVia: "auto-sweeper"
8743
+ };
8744
+ });
8745
+ const closedHere = nextQueries.filter(
8746
+ (q, i) => q.answer && !queries[i].answer && q.answeredVia === "auto-sweeper"
8747
+ ).length;
8748
+ if (closedHere === 0) continue;
8749
+ this.updateMissionStatus(mission.id, mission.status, {
8750
+ operatorQueries: nextQueries
8751
+ }, [{
8752
+ at: closedAt,
8753
+ source: "system",
8754
+ text: `Auto-closed ${closedHere} stale operator query(ies): ${reason ?? "aged out"}.`,
8755
+ metadata: { closedCount: closedHere, reason: reason ?? "age" }
8756
+ }]);
8757
+ totalClosed += closedHere;
8758
+ missionsTouched += 1;
8759
+ breakdown.push({ missionId: mission.id, closed: closedHere, reason: reason ?? "age" });
8760
+ }
8761
+ return { closed: totalClosed, missionsTouched, breakdown };
8762
+ }
8681
8763
  // ─── Callback on disconnect (plan §7) ─────────────────
8682
8764
  /**
8683
8765
  * Flag a mission for callback-on-disconnect: the call dropped while
@@ -10690,7 +10772,7 @@ var SEARCH_EMAIL_TOOL = {
10690
10772
  var SEARCH_SKILLS_TOOL = {
10691
10773
  type: "function",
10692
10774
  name: "search_skills",
10693
- description: "Search your skill library for a playbook that fits the situation you just hit on this call (billing dispute, debt collector tactics, reservation deadlock, etc). Returns ranked summaries \u2014 pick the best match and pass its id to load_skill. Fast.",
10775
+ description: 'Search your skill library for a playbook that fits the situation you just hit on this call (billing dispute, debt collector tactics, reservation deadlock, etc). Returns up to 5 ranked matches, each with: id, name, BM25 score, when_to_use (the specific situation the skill is for), first_principle (the playbook\'s strategic frame), and a `recommendation` field. If `recommendation` says LOAD IT NOW or the top score > 0.3, immediately call load_skill with the top id \u2014 do not deliberate. If scores are weak (< 0.15), re-search with a different phrasing instead of loading a poor match. The model that judges "is this the right skill" is YOU \u2014 but the recommendation field will tell you what the right move usually is.',
10694
10776
  parameters: {
10695
10777
  type: "object",
10696
10778
  properties: {
@@ -12132,6 +12214,94 @@ function withTimeout(promise, ms) {
12132
12214
  return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
12133
12215
  }
12134
12216
 
12217
+ // src/phone/voice-providers/registry.ts
12218
+ var PROVIDERS2 = /* @__PURE__ */ new Map();
12219
+ function registerVoiceProvider(provider) {
12220
+ if (PROVIDERS2.has(provider.id)) {
12221
+ throw new Error(`Voice provider "${provider.id}" registered twice \u2014 id collision.`);
12222
+ }
12223
+ PROVIDERS2.set(provider.id, provider);
12224
+ }
12225
+ function listVoiceProviders() {
12226
+ return Array.from(PROVIDERS2.values());
12227
+ }
12228
+ function getVoiceProvider(id) {
12229
+ return PROVIDERS2.get(id);
12230
+ }
12231
+ function resolveVoiceRuntime(providerId, config, options = {}) {
12232
+ const id = (providerId || "openai").trim() || "openai";
12233
+ const provider = PROVIDERS2.get(id);
12234
+ if (!provider) {
12235
+ const known = Array.from(PROVIDERS2.keys()).join(", ") || "(none registered)";
12236
+ throw new Error(
12237
+ `Unknown voice runtime "${id}". Known providers: ${known}. Add a new one by dropping a file into packages/core/src/phone/voice-providers/.`
12238
+ );
12239
+ }
12240
+ let apiKey = "";
12241
+ let apiKeySource = "";
12242
+ if (provider.apiKeyConfigField) {
12243
+ const legacy = config[provider.apiKeyConfigField];
12244
+ if (legacy && legacy.trim()) {
12245
+ apiKey = legacy.trim();
12246
+ apiKeySource = `config.${provider.apiKeyConfigField}`;
12247
+ }
12248
+ }
12249
+ if (!apiKey) {
12250
+ const fromMap = config.voiceProviderKeys?.[provider.id];
12251
+ if (fromMap && fromMap.trim()) {
12252
+ apiKey = fromMap.trim();
12253
+ apiKeySource = `config.voiceProviderKeys.${provider.id}`;
12254
+ }
12255
+ }
12256
+ if (!apiKey) {
12257
+ const fromEnv = process.env[provider.apiKeyEnvVar];
12258
+ if (fromEnv && fromEnv.trim()) {
12259
+ apiKey = fromEnv.trim();
12260
+ apiKeySource = `env ${provider.apiKeyEnvVar}`;
12261
+ }
12262
+ }
12263
+ if (!apiKey) {
12264
+ throw new Error(
12265
+ `Voice provider "${provider.id}" (${provider.displayName}) selected, but no API key is configured. Set ${provider.apiKeyEnvVar} in your environment or save it to ~/.agenticmail/config.json under voiceProviderKeys.${provider.id}.`
12266
+ );
12267
+ }
12268
+ const model = options.model && options.model.trim() || provider.defaultModel;
12269
+ const url = `${provider.websocketBaseUrl}?model=${encodeURIComponent(model)}`;
12270
+ return {
12271
+ providerId: provider.id,
12272
+ providerDisplayName: provider.displayName,
12273
+ url,
12274
+ model,
12275
+ apiKey,
12276
+ apiKeySource
12277
+ };
12278
+ }
12279
+
12280
+ // src/phone/voice-providers/openai.ts
12281
+ registerVoiceProvider({
12282
+ id: "openai",
12283
+ displayName: "OpenAI Realtime (gpt-realtime)",
12284
+ websocketBaseUrl: "wss://api.openai.com/v1/realtime",
12285
+ defaultModel: "gpt-realtime",
12286
+ apiKeyEnvVar: "OPENAI_API_KEY",
12287
+ // Legacy: the original config.json schema used a dedicated
12288
+ // `openaiApiKey` field for this key. The resolver checks that field
12289
+ // before the generic voiceProviderKeys map so existing installs
12290
+ // continue to work without migration.
12291
+ apiKeyConfigField: "openaiApiKey",
12292
+ description: "OpenAI Realtime (gpt-realtime). Default voice runtime; supports linear PCM @ 24 kHz (46elks) and G.711 \xB5-law @ 8 kHz (Twilio) without transcoding."
12293
+ });
12294
+
12295
+ // src/phone/voice-providers/grok.ts
12296
+ registerVoiceProvider({
12297
+ id: "grok",
12298
+ displayName: "xAI Grok Voice Agent",
12299
+ websocketBaseUrl: "wss://api.x.ai/v1/realtime",
12300
+ defaultModel: "grok-voice-latest",
12301
+ apiKeyEnvVar: "XAI_API_KEY",
12302
+ description: 'xAI Grok Voice Agent \u2014 OpenAI-Realtime-compatible WebSocket protocol; select via mission policy.voiceRuntime="grok" or env AGENTICMAIL_VOICE_RUNTIME=grok.'
12303
+ });
12304
+
12135
12305
  // src/telemetry.ts
12136
12306
  var import_crypto = require("crypto");
12137
12307
  var import_fs = require("fs");
@@ -16446,6 +16616,7 @@ function saveAgentPersona(agentName, content) {
16446
16616
  getTelegramMe,
16447
16617
  getTelegramUpdates,
16448
16618
  getTelegramWebhookInfo,
16619
+ getVoiceProvider,
16449
16620
  hostSessionStoragePath,
16450
16621
  inferPhoneRegion,
16451
16622
  invalidateSkillCache,
@@ -16458,6 +16629,7 @@ function saveAgentPersona(agentName, content) {
16458
16629
  isTelegramStopCommand,
16459
16630
  isValidPhoneNumber,
16460
16631
  listSkills,
16632
+ listVoiceProviders,
16461
16633
  loadAgentPersona,
16462
16634
  loadHostSession,
16463
16635
  loadSkill,
@@ -16486,6 +16658,7 @@ function saveAgentPersona(agentName, content) {
16486
16658
  redactSecret,
16487
16659
  redactSmsConfig,
16488
16660
  redactTelegramConfig,
16661
+ registerVoiceProvider,
16489
16662
  renderSkillAsPrompt,
16490
16663
  requireBinary,
16491
16664
  requireWhisperModel,
@@ -16493,6 +16666,7 @@ function saveAgentPersona(agentName, content) {
16493
16666
  resolveConfig,
16494
16667
  resolveExtensionPolicy,
16495
16668
  resolveTlsRejectUnauthorized,
16669
+ resolveVoiceRuntime,
16496
16670
  safeJoin,
16497
16671
  sanitizeEmail,
16498
16672
  saveAgentPersona,
package/dist/index.d.cts CHANGED
@@ -229,6 +229,22 @@ interface AgenticMailConfig {
229
229
  * `config.json`. Optional — no other feature depends on it.
230
230
  */
231
231
  openaiApiKey?: string;
232
+ /**
233
+ * v0.9.93 — voice-runtime provider keys. Each is a thin Bearer-token
234
+ * for a provider's realtime API; required only when that provider
235
+ * is selected as the runtime for a call. New providers register
236
+ * themselves in `packages/core/src/phone/voice-providers/` and
237
+ * declare which key field they consume — this record is the central
238
+ * pool. Read from per-provider env vars (e.g. `XAI_API_KEY`) at boot.
239
+ */
240
+ voiceProviderKeys?: Record<string, string>;
241
+ /**
242
+ * v0.9.93 — default voice-runtime provider id for phone missions
243
+ * that don't pin one on their own policy. `'openai'` (the existing
244
+ * default) or any provider registered in `voice-providers/`.
245
+ * Read from `AGENTICMAIL_VOICE_RUNTIME` env var or `config.json`.
246
+ */
247
+ voiceRuntime?: string;
232
248
  masterKey: string;
233
249
  dataDir: string;
234
250
  }
@@ -3824,6 +3840,127 @@ declare class RealtimeVoiceBridge {
3824
3840
  private safeSend;
3825
3841
  }
3826
3842
 
3843
+ /**
3844
+ * Voice-runtime provider plugin interface.
3845
+ *
3846
+ * Each backend (OpenAI Realtime, Grok Voice Agent, future Anthropic
3847
+ * realtime, Cartesia, ElevenLabs ConvAI, etc.) lives in its own file
3848
+ * under `packages/core/src/phone/voice-providers/` and registers
3849
+ * itself with the registry by calling {@link registerVoiceProvider}
3850
+ * at module load.
3851
+ *
3852
+ * Adding a new provider is meant to be a literal FILE DROP:
3853
+ *
3854
+ * 1. Create `voice-providers/<id>.ts` exporting a {@link VoiceProvider}.
3855
+ * 2. Add a single `import './<id>.js';` line to `voice-providers/index.ts`
3856
+ * so the side-effect registration runs.
3857
+ * 3. (Optional) document the env var the provider expects.
3858
+ *
3859
+ * No other file in the codebase needs to know about the new provider —
3860
+ * the realtime bridge looks providers up by id through the registry.
3861
+ *
3862
+ * Currently every supported provider is an OpenAI-Realtime-compatible
3863
+ * WebSocket. If a future provider diverges enough to need its own wire
3864
+ * protocol (custom event shape, gRPC, WebRTC SDP, etc.), the seam to
3865
+ * extend is here: add `buildSessionUpdate` / `parseInboundEvent` /
3866
+ * etc. hooks to {@link VoiceProvider}, then make `realtime-bridge.ts`
3867
+ * route through them instead of speaking OpenAI Realtime directly.
3868
+ */
3869
+ /**
3870
+ * One voice-runtime provider. Carries enough information for the
3871
+ * bridge to open the right WebSocket with the right auth + model.
3872
+ */
3873
+ interface VoiceProvider {
3874
+ /**
3875
+ * Stable identifier used in mission policy / config (`'openai'`,
3876
+ * `'grok'`, …). Keep lowercase, no spaces — this is what operators
3877
+ * type into `AGENTICMAIL_VOICE_RUNTIME=` or pass via mission policy.
3878
+ */
3879
+ id: string;
3880
+ /** Human-readable display name for logs + the web UI. */
3881
+ displayName: string;
3882
+ /** WebSocket base URL (the bridge appends `?model=…`). */
3883
+ websocketBaseUrl: string;
3884
+ /**
3885
+ * Default model when the caller doesn't pin one. Sent as the
3886
+ * `?model=…` query string AND echoed in the session.update.
3887
+ */
3888
+ defaultModel: string;
3889
+ /**
3890
+ * The env var the operator sets for this provider's API key
3891
+ * (`OPENAI_API_KEY`, `XAI_API_KEY`, …). The bootstrap / config
3892
+ * loader reads from this name and stores into
3893
+ * `AgenticMailConfig.voiceProviderKeys[<id>]`. Used in the
3894
+ * "you didn't set the key" error message too.
3895
+ */
3896
+ apiKeyEnvVar: string;
3897
+ /**
3898
+ * Optional fallback config-field name. When the provider's API key
3899
+ * has a long-standing dedicated config field (e.g. OpenAI's
3900
+ * `config.openaiApiKey`), this lets the resolver check that field
3901
+ * BEFORE looking in the generic `voiceProviderKeys` map — so
3902
+ * existing installs don't need to migrate. Leave undefined for
3903
+ * new providers.
3904
+ */
3905
+ apiKeyConfigField?: 'openaiApiKey';
3906
+ /**
3907
+ * Per-provider notes that surface in error / boot-log messages.
3908
+ * Optional — defaults are fine for the standard providers.
3909
+ */
3910
+ description?: string;
3911
+ }
3912
+ /**
3913
+ * The bridge needs all the inputs needed to open a session in one
3914
+ * resolved struct — URL with model encoded, the API key, the source
3915
+ * for logging. {@link resolveVoiceRuntime} produces this.
3916
+ */
3917
+ interface VoiceRuntimeConnection {
3918
+ providerId: string;
3919
+ providerDisplayName: string;
3920
+ /** Full WebSocket URL including `?model=…`. */
3921
+ url: string;
3922
+ /** Resolved model name (also passed in the URL). */
3923
+ model: string;
3924
+ /** Bearer token for the `Authorization` header. */
3925
+ apiKey: string;
3926
+ /** Human-readable source of the key, for boot logs only (e.g. `"env XAI_API_KEY"`). */
3927
+ apiKeySource: string;
3928
+ }
3929
+
3930
+ /**
3931
+ * Voice-provider registry — drop-in plugin discovery.
3932
+ *
3933
+ * Providers register themselves at module-load time via
3934
+ * {@link registerVoiceProvider}. The barrel `voice-providers/index.ts`
3935
+ * imports each provider file for its side effect, populating this map
3936
+ * before any caller asks for one.
3937
+ *
3938
+ * No reflection, no filesystem scan, no decorator magic — adding a
3939
+ * provider is "create a file + add one import line". That gives us
3940
+ * both first-class TypeScript types AND the file-drop ergonomics.
3941
+ */
3942
+
3943
+ /**
3944
+ * Register a provider. Called once per file at module load. Throws on
3945
+ * duplicate id so an accidental copy-paste collision is loud, not
3946
+ * silently overriding.
3947
+ */
3948
+ declare function registerVoiceProvider(provider: VoiceProvider): void;
3949
+ /** All registered providers — for `agenticmail voice list` / web UI menus. */
3950
+ declare function listVoiceProviders(): VoiceProvider[];
3951
+ /** Look up a provider by id. Returns undefined for unknown ids. */
3952
+ declare function getVoiceProvider(id: string): VoiceProvider | undefined;
3953
+ /**
3954
+ * Resolve a provider id + the runtime's config / overrides into the
3955
+ * connection struct the bridge consumes (URL with model, api key,
3956
+ * source). Throws if the provider id is unknown or its API key isn't
3957
+ * configured — the realtime-ws layer turns the throw into a clear
3958
+ * startup error instead of a mid-call surprise.
3959
+ */
3960
+ declare function resolveVoiceRuntime(providerId: string | undefined, config: AgenticMailConfig, options?: {
3961
+ model?: string;
3962
+ }): VoiceRuntimeConnection;
3963
+
3827
3964
  type PhoneTransportProvider = '46elks' | 'twilio';
3828
3965
  /** Providers that support starting outbound call-control missions. */
3829
3966
  declare const PHONE_CALL_CONTROL_PROVIDERS: readonly PhoneTransportProvider[];
@@ -4109,6 +4246,37 @@ declare class PhoneManager {
4109
4246
  query: PhoneOperatorQuery;
4110
4247
  alreadyAnswered: boolean;
4111
4248
  } | null;
4249
+ /**
4250
+ * v0.9.92 — auto-close stale operator queries.
4251
+ *
4252
+ * An operator query is "stale" when:
4253
+ * - it's unanswered, AND
4254
+ * - either (a) the mission is no longer live (status in
4255
+ * {completed, failed, cancelled}), OR (b) the query is older
4256
+ * than `maxAgeSeconds`.
4257
+ *
4258
+ * The sweeper marks each stale query as answered with a synthetic
4259
+ * "[auto-closed: ...]" answer + `answeredVia: 'auto-sweeper'`. This
4260
+ * (a) gets the query out of `listOpenOperatorQueries`, (b) clears
4261
+ * the bridge's "you have N open questions" hint to the operator,
4262
+ * and (c) leaves a permanent audit trail of WHY the query was
4263
+ * closed.
4264
+ *
4265
+ * Returns the count of queries closed + a per-mission breakdown
4266
+ * for logging.
4267
+ */
4268
+ sweepStaleOperatorQueries(opts?: {
4269
+ maxAgeSeconds?: number;
4270
+ nowMs?: number;
4271
+ }): {
4272
+ closed: number;
4273
+ missionsTouched: number;
4274
+ breakdown: Array<{
4275
+ missionId: string;
4276
+ closed: number;
4277
+ reason: 'mission-terminal' | 'age';
4278
+ }>;
4279
+ };
4112
4280
  /**
4113
4281
  * Flag a mission for callback-on-disconnect: the call dropped while
4114
4282
  * an operator query was still unanswered, so once the operator
@@ -6010,6 +6178,32 @@ interface SkillSummary {
6010
6178
  version: string;
6011
6179
  disclaimer_required: boolean;
6012
6180
  estimated_call_duration_minutes: number;
6181
+ /**
6182
+ * v0.9.92 — surfaced from `context.when_to_use`. This is the field
6183
+ * that actually tells the model "should I load this for the
6184
+ * current situation?" — far more diagnostic than the generic
6185
+ * `description` (which is just a one-liner for browsing). The
6186
+ * realtime `search_skills` tool result includes it so the model
6187
+ * can decide WITHOUT a second `load_skill` round-trip on a wrong
6188
+ * guess.
6189
+ */
6190
+ when_to_use: string;
6191
+ /**
6192
+ * v0.9.92 — the skill's first principle, surfaced for the same
6193
+ * "is this the right playbook" decision. Principles are the
6194
+ * strategic frame ("be calm and friendly — the rep didn't choose
6195
+ * your bill"); seeing one principle tells the model whether the
6196
+ * skill's POSTURE matches the situation, not just its topic.
6197
+ */
6198
+ first_principle: string;
6199
+ /**
6200
+ * v0.9.92 — BM25F search score from the rank. Only present on
6201
+ * results from `searchSkills`; absent on `listSkills` output
6202
+ * (where the ordering is by id, not by relevance). Lets the
6203
+ * model thresholds "definitely load" vs. "re-search with a
6204
+ * better query".
6205
+ */
6206
+ score?: number;
6013
6207
  }
6014
6208
  /** Failed-validation result from the schema validator. */
6015
6209
  interface SkillValidationError {
@@ -6191,4 +6385,4 @@ declare function loadAgentPersona(agentName: string): string;
6191
6385
  */
6192
6386
  declare function saveAgentPersona(agentName: string, content: string): string;
6193
6387
 
6194
- export { AGENT_ROLES, AGENT_STATE_ROOT, ASK_OPERATOR_TOOL, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentMemoryEntry, type AgentMemoryFields, AgentMemoryManager, type AgentMemoryOptions, type AgentMemoryRead, AgentMemoryStore, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, type AudioAction, type AudioEditOptions, BRIDGE_OPERATOR_LIVE_WINDOW_MS, type BridgeMailContext, type BridgeWakeError, type BridgeWakePromptArgs, type BridgeWakeResult, type BridgeWakeRoute, type CachedMessage, CloudflareClient, type CreateAgentOptions, type CreateMemoryInput, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DEFAULT_CALLBACK_POLICY, DEFAULT_EXTENSION_POLICY, DEFAULT_REALTIME_AUDIO_FORMAT, DEFAULT_REALTIME_MODEL, DEFAULT_REALTIME_VOICE, DEFAULT_SESSION_MAX_AGE_MS, DEFAULT_WEB_SEARCH_ENDPOINT, DNSConfigurator, type Database, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, ELKS_REALTIME_AUDIO_FORMATS, ELKS_REALTIME_WS_PATH, END_CALL_TOOL, EXTEND_CALL_TIME_TOOL, type ElksRealtimeAudioFormat, type ElksRealtimeAudioMessage, type ElksRealtimeByeMessage, type ElksRealtimeHelloMessage, type ElksRealtimeInboundMessage, type ElksRealtimeOutboundMessage, ElksRealtimeTransport, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, GET_CALL_STATUS_TOOL, GET_DATETIME_TOOL, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type GetDatetimeOptions, type GetUpdatesOptions, type HostName, type HostSession, type HostSessionResumeMode, type ImageAction, type ImageEditOptions, type InboundEmail, type InboundSmsEvent, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, LOAD_SKILL_TOOL, type LinkAdvisory, type LocalSmtpConfig, MEMORY_CATEGORIES, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type MediaBinary, type MediaCapability, type MediaCapabilityReport, type MediaFileResult, type MediaInfoResult, MediaManager, type MediaManagerOptions, type MediaStreamInfo, type MemoryCategory, type MemoryImportance, type MemoryQueryOptions, type MemoryRecaller, MemorySearchIndex, type MemorySource, type MemoryStats, OPENAI_REALTIME_URL, OPERATOR_QUERY_POLL_INTERVAL_MS, OPERATOR_QUERY_SUBJECT_TAG, OPERATOR_QUERY_TIMEOUT_MS, OPERATOR_QUERY_TIMEOUT_SENTINEL, type OpenClawPhoneMissionPolicy, type OperatorQueryNotificationInput, type OperatorQueryPollOptions, type OperatorQueryUrgency, type OperatorReplyKind, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, PERSONA_FILENAME, PHONE_CALLBACK_MAX_DELAY_SECONDS, PHONE_CALLBACK_MIN_DELAY_SECONDS, PHONE_CALL_CONTROL_PROVIDERS, PHONE_MAX_CONCURRENT_MISSIONS, PHONE_MIN_WEBHOOK_SECRET_LENGTH, PHONE_MISSION_STATES, PHONE_RATE_LIMIT_PER_HOUR, PHONE_RATE_LIMIT_PER_MINUTE, PHONE_REGION_SCOPES, PHONE_SERVER_MAX_ATTEMPTS, PHONE_SERVER_MAX_CALLBACK_CHAIN, PHONE_SERVER_MAX_CALL_DURATION_SECONDS, PHONE_SERVER_MAX_COST_PER_MISSION, PHONE_SERVER_MAX_EXTENSION_REQUESTS_PER_CALL, PHONE_SERVER_MAX_EXTENSION_SECONDS_PER_REQUEST, PHONE_SERVER_MAX_TOTAL_EXTENSION_SECONDS, PHONE_TASK_MAX_LENGTH, type ParsedAttachment, type ParsedEmail, type ParsedOperatorReply, type ParsedSms, type ParsedTelegramMessage, PathTraversalError, type PhoneAlternativePolicy, type PhoneCallMission, type PhoneCallbackPolicy, type PhoneConfirmPolicy, type PhoneExtensionPolicy, PhoneManager, type PhoneMissionStartValidationResult, type PhoneMissionState, type PhoneMissionTranscriptEntry, type PhoneMissionValidationIssue, type PhoneMissionValidationResult, type PhoneNumberRisk, type PhoneOperatorQuery, PhoneRateLimitError, type PhoneRegionScope, type PhoneScheduledCallback, type PhoneTransportConfig, type PhoneTransportProfile, type PhoneTransportProvider, type PhoneTransportValidationResult, PhoneWebhookAuthError, type PhoneWebhookResult, type PlanBridgeWakeArgs, type PurchasedDomain, REALTIME_AUDIO_SAMPLE_RATE, REALTIME_MAX_AUDIO_FRAME_BASE64, REALTIME_TOOL_CALL_TIMEOUT_MS, REALTIME_TOOL_DEFINITIONS, RECALL_MEMORY_TOOL, REDACTED, RELAY_PRESETS, type RealtimeBridgePort, type RealtimeBridgeTranscriptEntry, type RealtimeInboundEvent, type RealtimeInstructionOptions, type RealtimeSessionConfigOptions, type RealtimeToolCall, type RealtimeToolDefinition, type RealtimeToolHandler, type RealtimeToolResult, type RealtimeTransportAdapter, type RealtimeTransportProvider, RealtimeVoiceBridge, type RealtimeVoiceBridgeOptions, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, type ResumeErrorClassificationOptions, SCHEDULE_CALLBACK_TOOL, SEARCH_EMAIL_TOOL, SEARCH_SKILLS_TOOL, SPAM_THRESHOLD, type SafeJoinOptions, type SanitizeDetection, type SanitizeResult, type ScheduledCallbackRequest, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, type SendSmsInput, type SendSmsResult, type SendTelegramMessageOptions, type SendTelegramMessageResult, ServiceManager, type ServiceStatus, type SetWebhookOptions, type SetupConfig, SetupManager, type SetupResult, type Severity, type Skill, type SkillCategory, type SkillContext, type SkillExitStrategy, type SkillSummary, type SkillTactic, type SkillValidationError, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SmsProvider, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, type StartPhoneCallOptions, type StartPhoneCallResult, type StartPhoneMissionInput, TELEGRAM_API_BASE, TELEGRAM_CHUNK_SIZE, TELEGRAM_MESSAGE_LIMIT, TELEGRAM_MIN_WEBHOOK_SECRET_LENGTH, TELEGRAM_OPERATOR_QUERY_TAG, TELEGRAM_STOP_WORDS, TELEGRAM_WEBHOOK_SECRET_RE, TELEPHONY_TRANSPORT_CAPABILITIES, TWILIO_MEDIA_SAMPLE_RATE, TWILIO_REALTIME_WS_PATH, TelegramApiError, type TelegramApiOptions, type TelegramBotInfo, type TelegramChatType, type TelegramConfig, TelegramManager, type TelegramMessage, type TelegramMode, type TelephonyTransportCapability, ThreadCache, type ThreadCacheEntry, type ThreadCacheOptions, type ThreadIdInput, type ToolExecutor, type TtsGenerateOptions, type TunnelConfig, TunnelManager, type TwilioConnectedMessage, type TwilioMarkMessage, type TwilioMediaMessage, type TwilioRealtimeInboundMessage, type TwilioRealtimeOutboundMessage, TwilioRealtimeTransport, type TwilioStartMessage, type TwilioStopMessage, type TwilioStreamTwiMLOptions, UnsafeApiUrlError, type UpdateMemoryInput, type ValidatedPhoneMissionStart, type VideoAction, type VideoEditOptions, type VideoTimelineEntry, type VideoUnderstandOptions, type VideoUnderstandResult, type VoiceCloneOptions, WARNING_THRESHOLD, WEB_SEARCH_TOOL, WEB_SEARCH_UNTRUSTED_PREFIX, type WatcherOptions, type WebSearchOptions, assertWithinBase, bridgeWakeErrorMessage, bridgeWakeLastSeenAgeMs, buildApiUrl, buildDefaultPersona, buildElksAudioMessage, buildElksByeMessage, buildElksHandshakeMessages, buildElksInterruptMessage, buildElksListeningMessage, buildElksSendingMessage, buildInboundSecurityAdvisory, buildOpenAIRealtimeUrl, buildPhoneTransportConfig, buildRealtimeInstructions, buildRealtimeSessionConfig, buildRealtimeToolGuidance, buildTwilioClearMessage, buildTwilioMarkMessage, buildTwilioMediaMessage, buildTwilioSayTwiML, buildTwilioSignature, buildTwilioStreamTwiML, callTelegramApi, classifyEmailRoute, classifyPhoneNumberRisk, classifyResumeError, clearMediaCapabilityCache, closeDatabase, composeBridgeWakePrompt, createRealtimeTransport, createTestDatabase, createToolExecutor, debug, debugWarn, deleteTelegramWebhook, detectBinary, ensureDataDir, escapeXml, extractEmailAddress, extractVerificationCode, flushTelemetry, forgetHostSession, formatOperatorQueryTelegramMessage, getDatabase, getDatetime, getMediaCapabilities, getOperatorEmail, getSmsProvider, getTelegramChat, getTelegramMe, getTelegramUpdates, getTelegramWebhookInfo, hostSessionStoragePath, inferPhoneRegion, invalidateSkillCache, isInternalEmail, isLoopbackMailHost, isOperatorReplySender, isPhoneRegionAllowed, isSessionFresh, isTelegramChatAllowed, isTelegramStopCommand, isValidPhoneNumber, listSkills, loadAgentPersona, loadHostSession, loadSkill, mapProviderSmsStatus, nextTelegramOffset, normalizeAddress, normalizePhoneNumber, normalizeSubject, operatorPrefsStoragePath, operatorQuerySubject, parseElksRealtimeMessage, parseEmail, parseGoogleVoiceSms, parseOperatorQueryReply, parseTelegramOperatorReply, parseTelegramUpdate, parseTwilioRealtimeMessage, personaPathFor, planBridgeWake, pollForOperatorAnswer, recallMemory, recordToolCall, redactBotToken, redactObject, redactPhoneTransportConfig, redactSecret, redactSmsConfig, redactTelegramConfig, renderSkillAsPrompt, requireBinary, requireWhisperModel, resolveCallbackPolicy, resolveConfig, resolveExtensionPolicy, resolveTlsRejectUnauthorized, safeJoin, sanitizeEmail, saveAgentPersona, saveConfig, saveHostSession, saveUserSkill, scanOutboundEmail, scoreEmail, searchSkills, sendTelegramMessage, setOperatorEmail, setTelegramWebhook, setTelemetryVersion, shouldSkipBridgeWakeForLiveOperator, splitTelegramMessage, startRelayBridge, stem, stripTelegramMarkdown, threadIdFor, tokenize, tryJoin, userSkillsDir, validateApiUrl, validatePhoneMissionPolicy, validatePhoneMissionStart, validatePhoneTransportProfile, validateSkill, validateTwilioSignature, webSearch };
6388
+ export { AGENT_ROLES, AGENT_STATE_ROOT, ASK_OPERATOR_TOOL, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentMemoryEntry, type AgentMemoryFields, AgentMemoryManager, type AgentMemoryOptions, type AgentMemoryRead, AgentMemoryStore, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, type AudioAction, type AudioEditOptions, BRIDGE_OPERATOR_LIVE_WINDOW_MS, type BridgeMailContext, type BridgeWakeError, type BridgeWakePromptArgs, type BridgeWakeResult, type BridgeWakeRoute, type CachedMessage, CloudflareClient, type CreateAgentOptions, type CreateMemoryInput, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DEFAULT_CALLBACK_POLICY, DEFAULT_EXTENSION_POLICY, DEFAULT_REALTIME_AUDIO_FORMAT, DEFAULT_REALTIME_MODEL, DEFAULT_REALTIME_VOICE, DEFAULT_SESSION_MAX_AGE_MS, DEFAULT_WEB_SEARCH_ENDPOINT, DNSConfigurator, type Database, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, ELKS_REALTIME_AUDIO_FORMATS, ELKS_REALTIME_WS_PATH, END_CALL_TOOL, EXTEND_CALL_TIME_TOOL, type ElksRealtimeAudioFormat, type ElksRealtimeAudioMessage, type ElksRealtimeByeMessage, type ElksRealtimeHelloMessage, type ElksRealtimeInboundMessage, type ElksRealtimeOutboundMessage, ElksRealtimeTransport, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, GET_CALL_STATUS_TOOL, GET_DATETIME_TOOL, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type GetDatetimeOptions, type GetUpdatesOptions, type HostName, type HostSession, type HostSessionResumeMode, type ImageAction, type ImageEditOptions, type InboundEmail, type InboundSmsEvent, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, LOAD_SKILL_TOOL, type LinkAdvisory, type LocalSmtpConfig, MEMORY_CATEGORIES, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type MediaBinary, type MediaCapability, type MediaCapabilityReport, type MediaFileResult, type MediaInfoResult, MediaManager, type MediaManagerOptions, type MediaStreamInfo, type MemoryCategory, type MemoryImportance, type MemoryQueryOptions, type MemoryRecaller, MemorySearchIndex, type MemorySource, type MemoryStats, OPENAI_REALTIME_URL, OPERATOR_QUERY_POLL_INTERVAL_MS, OPERATOR_QUERY_SUBJECT_TAG, OPERATOR_QUERY_TIMEOUT_MS, OPERATOR_QUERY_TIMEOUT_SENTINEL, type OpenClawPhoneMissionPolicy, type OperatorQueryNotificationInput, type OperatorQueryPollOptions, type OperatorQueryUrgency, type OperatorReplyKind, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, PERSONA_FILENAME, PHONE_CALLBACK_MAX_DELAY_SECONDS, PHONE_CALLBACK_MIN_DELAY_SECONDS, PHONE_CALL_CONTROL_PROVIDERS, PHONE_MAX_CONCURRENT_MISSIONS, PHONE_MIN_WEBHOOK_SECRET_LENGTH, PHONE_MISSION_STATES, PHONE_RATE_LIMIT_PER_HOUR, PHONE_RATE_LIMIT_PER_MINUTE, PHONE_REGION_SCOPES, PHONE_SERVER_MAX_ATTEMPTS, PHONE_SERVER_MAX_CALLBACK_CHAIN, PHONE_SERVER_MAX_CALL_DURATION_SECONDS, PHONE_SERVER_MAX_COST_PER_MISSION, PHONE_SERVER_MAX_EXTENSION_REQUESTS_PER_CALL, PHONE_SERVER_MAX_EXTENSION_SECONDS_PER_REQUEST, PHONE_SERVER_MAX_TOTAL_EXTENSION_SECONDS, PHONE_TASK_MAX_LENGTH, type ParsedAttachment, type ParsedEmail, type ParsedOperatorReply, type ParsedSms, type ParsedTelegramMessage, PathTraversalError, type PhoneAlternativePolicy, type PhoneCallMission, type PhoneCallbackPolicy, type PhoneConfirmPolicy, type PhoneExtensionPolicy, PhoneManager, type PhoneMissionStartValidationResult, type PhoneMissionState, type PhoneMissionTranscriptEntry, type PhoneMissionValidationIssue, type PhoneMissionValidationResult, type PhoneNumberRisk, type PhoneOperatorQuery, PhoneRateLimitError, type PhoneRegionScope, type PhoneScheduledCallback, type PhoneTransportConfig, type PhoneTransportProfile, type PhoneTransportProvider, type PhoneTransportValidationResult, PhoneWebhookAuthError, type PhoneWebhookResult, type PlanBridgeWakeArgs, type PurchasedDomain, REALTIME_AUDIO_SAMPLE_RATE, REALTIME_MAX_AUDIO_FRAME_BASE64, REALTIME_TOOL_CALL_TIMEOUT_MS, REALTIME_TOOL_DEFINITIONS, RECALL_MEMORY_TOOL, REDACTED, RELAY_PRESETS, type RealtimeBridgePort, type RealtimeBridgeTranscriptEntry, type RealtimeInboundEvent, type RealtimeInstructionOptions, type RealtimeSessionConfigOptions, type RealtimeToolCall, type RealtimeToolDefinition, type RealtimeToolHandler, type RealtimeToolResult, type RealtimeTransportAdapter, type RealtimeTransportProvider, RealtimeVoiceBridge, type RealtimeVoiceBridgeOptions, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, type ResumeErrorClassificationOptions, SCHEDULE_CALLBACK_TOOL, SEARCH_EMAIL_TOOL, SEARCH_SKILLS_TOOL, SPAM_THRESHOLD, type SafeJoinOptions, type SanitizeDetection, type SanitizeResult, type ScheduledCallbackRequest, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, type SendSmsInput, type SendSmsResult, type SendTelegramMessageOptions, type SendTelegramMessageResult, ServiceManager, type ServiceStatus, type SetWebhookOptions, type SetupConfig, SetupManager, type SetupResult, type Severity, type Skill, type SkillCategory, type SkillContext, type SkillExitStrategy, type SkillSummary, type SkillTactic, type SkillValidationError, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SmsProvider, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, type StartPhoneCallOptions, type StartPhoneCallResult, type StartPhoneMissionInput, TELEGRAM_API_BASE, TELEGRAM_CHUNK_SIZE, TELEGRAM_MESSAGE_LIMIT, TELEGRAM_MIN_WEBHOOK_SECRET_LENGTH, TELEGRAM_OPERATOR_QUERY_TAG, TELEGRAM_STOP_WORDS, TELEGRAM_WEBHOOK_SECRET_RE, TELEPHONY_TRANSPORT_CAPABILITIES, TWILIO_MEDIA_SAMPLE_RATE, TWILIO_REALTIME_WS_PATH, TelegramApiError, type TelegramApiOptions, type TelegramBotInfo, type TelegramChatType, type TelegramConfig, TelegramManager, type TelegramMessage, type TelegramMode, type TelephonyTransportCapability, ThreadCache, type ThreadCacheEntry, type ThreadCacheOptions, type ThreadIdInput, type ToolExecutor, type TtsGenerateOptions, type TunnelConfig, TunnelManager, type TwilioConnectedMessage, type TwilioMarkMessage, type TwilioMediaMessage, type TwilioRealtimeInboundMessage, type TwilioRealtimeOutboundMessage, TwilioRealtimeTransport, type TwilioStartMessage, type TwilioStopMessage, type TwilioStreamTwiMLOptions, UnsafeApiUrlError, type UpdateMemoryInput, type ValidatedPhoneMissionStart, type VideoAction, type VideoEditOptions, type VideoTimelineEntry, type VideoUnderstandOptions, type VideoUnderstandResult, type VoiceCloneOptions, type VoiceProvider, type VoiceRuntimeConnection, WARNING_THRESHOLD, WEB_SEARCH_TOOL, WEB_SEARCH_UNTRUSTED_PREFIX, type WatcherOptions, type WebSearchOptions, assertWithinBase, bridgeWakeErrorMessage, bridgeWakeLastSeenAgeMs, buildApiUrl, buildDefaultPersona, buildElksAudioMessage, buildElksByeMessage, buildElksHandshakeMessages, buildElksInterruptMessage, buildElksListeningMessage, buildElksSendingMessage, buildInboundSecurityAdvisory, buildOpenAIRealtimeUrl, buildPhoneTransportConfig, buildRealtimeInstructions, buildRealtimeSessionConfig, buildRealtimeToolGuidance, buildTwilioClearMessage, buildTwilioMarkMessage, buildTwilioMediaMessage, buildTwilioSayTwiML, buildTwilioSignature, buildTwilioStreamTwiML, callTelegramApi, classifyEmailRoute, classifyPhoneNumberRisk, classifyResumeError, clearMediaCapabilityCache, closeDatabase, composeBridgeWakePrompt, createRealtimeTransport, createTestDatabase, createToolExecutor, debug, debugWarn, deleteTelegramWebhook, detectBinary, ensureDataDir, escapeXml, extractEmailAddress, extractVerificationCode, flushTelemetry, forgetHostSession, formatOperatorQueryTelegramMessage, getDatabase, getDatetime, getMediaCapabilities, getOperatorEmail, getSmsProvider, getTelegramChat, getTelegramMe, getTelegramUpdates, getTelegramWebhookInfo, getVoiceProvider, hostSessionStoragePath, inferPhoneRegion, invalidateSkillCache, isInternalEmail, isLoopbackMailHost, isOperatorReplySender, isPhoneRegionAllowed, isSessionFresh, isTelegramChatAllowed, isTelegramStopCommand, isValidPhoneNumber, listSkills, listVoiceProviders, loadAgentPersona, loadHostSession, loadSkill, mapProviderSmsStatus, nextTelegramOffset, normalizeAddress, normalizePhoneNumber, normalizeSubject, operatorPrefsStoragePath, operatorQuerySubject, parseElksRealtimeMessage, parseEmail, parseGoogleVoiceSms, parseOperatorQueryReply, parseTelegramOperatorReply, parseTelegramUpdate, parseTwilioRealtimeMessage, personaPathFor, planBridgeWake, pollForOperatorAnswer, recallMemory, recordToolCall, redactBotToken, redactObject, redactPhoneTransportConfig, redactSecret, redactSmsConfig, redactTelegramConfig, registerVoiceProvider, renderSkillAsPrompt, requireBinary, requireWhisperModel, resolveCallbackPolicy, resolveConfig, resolveExtensionPolicy, resolveTlsRejectUnauthorized, resolveVoiceRuntime, safeJoin, sanitizeEmail, saveAgentPersona, saveConfig, saveHostSession, saveUserSkill, scanOutboundEmail, scoreEmail, searchSkills, sendTelegramMessage, setOperatorEmail, setTelegramWebhook, setTelemetryVersion, shouldSkipBridgeWakeForLiveOperator, splitTelegramMessage, startRelayBridge, stem, stripTelegramMarkdown, threadIdFor, tokenize, tryJoin, userSkillsDir, validateApiUrl, validatePhoneMissionPolicy, validatePhoneMissionStart, validatePhoneTransportProfile, validateSkill, validateTwilioSignature, webSearch };
package/dist/index.d.ts CHANGED
@@ -229,6 +229,22 @@ interface AgenticMailConfig {
229
229
  * `config.json`. Optional — no other feature depends on it.
230
230
  */
231
231
  openaiApiKey?: string;
232
+ /**
233
+ * v0.9.93 — voice-runtime provider keys. Each is a thin Bearer-token
234
+ * for a provider's realtime API; required only when that provider
235
+ * is selected as the runtime for a call. New providers register
236
+ * themselves in `packages/core/src/phone/voice-providers/` and
237
+ * declare which key field they consume — this record is the central
238
+ * pool. Read from per-provider env vars (e.g. `XAI_API_KEY`) at boot.
239
+ */
240
+ voiceProviderKeys?: Record<string, string>;
241
+ /**
242
+ * v0.9.93 — default voice-runtime provider id for phone missions
243
+ * that don't pin one on their own policy. `'openai'` (the existing
244
+ * default) or any provider registered in `voice-providers/`.
245
+ * Read from `AGENTICMAIL_VOICE_RUNTIME` env var or `config.json`.
246
+ */
247
+ voiceRuntime?: string;
232
248
  masterKey: string;
233
249
  dataDir: string;
234
250
  }
@@ -3824,6 +3840,127 @@ declare class RealtimeVoiceBridge {
3824
3840
  private safeSend;
3825
3841
  }
3826
3842
 
3843
+ /**
3844
+ * Voice-runtime provider plugin interface.
3845
+ *
3846
+ * Each backend (OpenAI Realtime, Grok Voice Agent, future Anthropic
3847
+ * realtime, Cartesia, ElevenLabs ConvAI, etc.) lives in its own file
3848
+ * under `packages/core/src/phone/voice-providers/` and registers
3849
+ * itself with the registry by calling {@link registerVoiceProvider}
3850
+ * at module load.
3851
+ *
3852
+ * Adding a new provider is meant to be a literal FILE DROP:
3853
+ *
3854
+ * 1. Create `voice-providers/<id>.ts` exporting a {@link VoiceProvider}.
3855
+ * 2. Add a single `import './<id>.js';` line to `voice-providers/index.ts`
3856
+ * so the side-effect registration runs.
3857
+ * 3. (Optional) document the env var the provider expects.
3858
+ *
3859
+ * No other file in the codebase needs to know about the new provider —
3860
+ * the realtime bridge looks providers up by id through the registry.
3861
+ *
3862
+ * Currently every supported provider is an OpenAI-Realtime-compatible
3863
+ * WebSocket. If a future provider diverges enough to need its own wire
3864
+ * protocol (custom event shape, gRPC, WebRTC SDP, etc.), the seam to
3865
+ * extend is here: add `buildSessionUpdate` / `parseInboundEvent` /
3866
+ * etc. hooks to {@link VoiceProvider}, then make `realtime-bridge.ts`
3867
+ * route through them instead of speaking OpenAI Realtime directly.
3868
+ */
3869
+ /**
3870
+ * One voice-runtime provider. Carries enough information for the
3871
+ * bridge to open the right WebSocket with the right auth + model.
3872
+ */
3873
+ interface VoiceProvider {
3874
+ /**
3875
+ * Stable identifier used in mission policy / config (`'openai'`,
3876
+ * `'grok'`, …). Keep lowercase, no spaces — this is what operators
3877
+ * type into `AGENTICMAIL_VOICE_RUNTIME=` or pass via mission policy.
3878
+ */
3879
+ id: string;
3880
+ /** Human-readable display name for logs + the web UI. */
3881
+ displayName: string;
3882
+ /** WebSocket base URL (the bridge appends `?model=…`). */
3883
+ websocketBaseUrl: string;
3884
+ /**
3885
+ * Default model when the caller doesn't pin one. Sent as the
3886
+ * `?model=…` query string AND echoed in the session.update.
3887
+ */
3888
+ defaultModel: string;
3889
+ /**
3890
+ * The env var the operator sets for this provider's API key
3891
+ * (`OPENAI_API_KEY`, `XAI_API_KEY`, …). The bootstrap / config
3892
+ * loader reads from this name and stores into
3893
+ * `AgenticMailConfig.voiceProviderKeys[<id>]`. Used in the
3894
+ * "you didn't set the key" error message too.
3895
+ */
3896
+ apiKeyEnvVar: string;
3897
+ /**
3898
+ * Optional fallback config-field name. When the provider's API key
3899
+ * has a long-standing dedicated config field (e.g. OpenAI's
3900
+ * `config.openaiApiKey`), this lets the resolver check that field
3901
+ * BEFORE looking in the generic `voiceProviderKeys` map — so
3902
+ * existing installs don't need to migrate. Leave undefined for
3903
+ * new providers.
3904
+ */
3905
+ apiKeyConfigField?: 'openaiApiKey';
3906
+ /**
3907
+ * Per-provider notes that surface in error / boot-log messages.
3908
+ * Optional — defaults are fine for the standard providers.
3909
+ */
3910
+ description?: string;
3911
+ }
3912
+ /**
3913
+ * The bridge needs all the inputs needed to open a session in one
3914
+ * resolved struct — URL with model encoded, the API key, the source
3915
+ * for logging. {@link resolveVoiceRuntime} produces this.
3916
+ */
3917
+ interface VoiceRuntimeConnection {
3918
+ providerId: string;
3919
+ providerDisplayName: string;
3920
+ /** Full WebSocket URL including `?model=…`. */
3921
+ url: string;
3922
+ /** Resolved model name (also passed in the URL). */
3923
+ model: string;
3924
+ /** Bearer token for the `Authorization` header. */
3925
+ apiKey: string;
3926
+ /** Human-readable source of the key, for boot logs only (e.g. `"env XAI_API_KEY"`). */
3927
+ apiKeySource: string;
3928
+ }
3929
+
3930
+ /**
3931
+ * Voice-provider registry — drop-in plugin discovery.
3932
+ *
3933
+ * Providers register themselves at module-load time via
3934
+ * {@link registerVoiceProvider}. The barrel `voice-providers/index.ts`
3935
+ * imports each provider file for its side effect, populating this map
3936
+ * before any caller asks for one.
3937
+ *
3938
+ * No reflection, no filesystem scan, no decorator magic — adding a
3939
+ * provider is "create a file + add one import line". That gives us
3940
+ * both first-class TypeScript types AND the file-drop ergonomics.
3941
+ */
3942
+
3943
+ /**
3944
+ * Register a provider. Called once per file at module load. Throws on
3945
+ * duplicate id so an accidental copy-paste collision is loud, not
3946
+ * silently overriding.
3947
+ */
3948
+ declare function registerVoiceProvider(provider: VoiceProvider): void;
3949
+ /** All registered providers — for `agenticmail voice list` / web UI menus. */
3950
+ declare function listVoiceProviders(): VoiceProvider[];
3951
+ /** Look up a provider by id. Returns undefined for unknown ids. */
3952
+ declare function getVoiceProvider(id: string): VoiceProvider | undefined;
3953
+ /**
3954
+ * Resolve a provider id + the runtime's config / overrides into the
3955
+ * connection struct the bridge consumes (URL with model, api key,
3956
+ * source). Throws if the provider id is unknown or its API key isn't
3957
+ * configured — the realtime-ws layer turns the throw into a clear
3958
+ * startup error instead of a mid-call surprise.
3959
+ */
3960
+ declare function resolveVoiceRuntime(providerId: string | undefined, config: AgenticMailConfig, options?: {
3961
+ model?: string;
3962
+ }): VoiceRuntimeConnection;
3963
+
3827
3964
  type PhoneTransportProvider = '46elks' | 'twilio';
3828
3965
  /** Providers that support starting outbound call-control missions. */
3829
3966
  declare const PHONE_CALL_CONTROL_PROVIDERS: readonly PhoneTransportProvider[];
@@ -4109,6 +4246,37 @@ declare class PhoneManager {
4109
4246
  query: PhoneOperatorQuery;
4110
4247
  alreadyAnswered: boolean;
4111
4248
  } | null;
4249
+ /**
4250
+ * v0.9.92 — auto-close stale operator queries.
4251
+ *
4252
+ * An operator query is "stale" when:
4253
+ * - it's unanswered, AND
4254
+ * - either (a) the mission is no longer live (status in
4255
+ * {completed, failed, cancelled}), OR (b) the query is older
4256
+ * than `maxAgeSeconds`.
4257
+ *
4258
+ * The sweeper marks each stale query as answered with a synthetic
4259
+ * "[auto-closed: ...]" answer + `answeredVia: 'auto-sweeper'`. This
4260
+ * (a) gets the query out of `listOpenOperatorQueries`, (b) clears
4261
+ * the bridge's "you have N open questions" hint to the operator,
4262
+ * and (c) leaves a permanent audit trail of WHY the query was
4263
+ * closed.
4264
+ *
4265
+ * Returns the count of queries closed + a per-mission breakdown
4266
+ * for logging.
4267
+ */
4268
+ sweepStaleOperatorQueries(opts?: {
4269
+ maxAgeSeconds?: number;
4270
+ nowMs?: number;
4271
+ }): {
4272
+ closed: number;
4273
+ missionsTouched: number;
4274
+ breakdown: Array<{
4275
+ missionId: string;
4276
+ closed: number;
4277
+ reason: 'mission-terminal' | 'age';
4278
+ }>;
4279
+ };
4112
4280
  /**
4113
4281
  * Flag a mission for callback-on-disconnect: the call dropped while
4114
4282
  * an operator query was still unanswered, so once the operator
@@ -6010,6 +6178,32 @@ interface SkillSummary {
6010
6178
  version: string;
6011
6179
  disclaimer_required: boolean;
6012
6180
  estimated_call_duration_minutes: number;
6181
+ /**
6182
+ * v0.9.92 — surfaced from `context.when_to_use`. This is the field
6183
+ * that actually tells the model "should I load this for the
6184
+ * current situation?" — far more diagnostic than the generic
6185
+ * `description` (which is just a one-liner for browsing). The
6186
+ * realtime `search_skills` tool result includes it so the model
6187
+ * can decide WITHOUT a second `load_skill` round-trip on a wrong
6188
+ * guess.
6189
+ */
6190
+ when_to_use: string;
6191
+ /**
6192
+ * v0.9.92 — the skill's first principle, surfaced for the same
6193
+ * "is this the right playbook" decision. Principles are the
6194
+ * strategic frame ("be calm and friendly — the rep didn't choose
6195
+ * your bill"); seeing one principle tells the model whether the
6196
+ * skill's POSTURE matches the situation, not just its topic.
6197
+ */
6198
+ first_principle: string;
6199
+ /**
6200
+ * v0.9.92 — BM25F search score from the rank. Only present on
6201
+ * results from `searchSkills`; absent on `listSkills` output
6202
+ * (where the ordering is by id, not by relevance). Lets the
6203
+ * model thresholds "definitely load" vs. "re-search with a
6204
+ * better query".
6205
+ */
6206
+ score?: number;
6013
6207
  }
6014
6208
  /** Failed-validation result from the schema validator. */
6015
6209
  interface SkillValidationError {
@@ -6191,4 +6385,4 @@ declare function loadAgentPersona(agentName: string): string;
6191
6385
  */
6192
6386
  declare function saveAgentPersona(agentName: string, content: string): string;
6193
6387
 
6194
- export { AGENT_ROLES, AGENT_STATE_ROOT, ASK_OPERATOR_TOOL, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentMemoryEntry, type AgentMemoryFields, AgentMemoryManager, type AgentMemoryOptions, type AgentMemoryRead, AgentMemoryStore, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, type AudioAction, type AudioEditOptions, BRIDGE_OPERATOR_LIVE_WINDOW_MS, type BridgeMailContext, type BridgeWakeError, type BridgeWakePromptArgs, type BridgeWakeResult, type BridgeWakeRoute, type CachedMessage, CloudflareClient, type CreateAgentOptions, type CreateMemoryInput, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DEFAULT_CALLBACK_POLICY, DEFAULT_EXTENSION_POLICY, DEFAULT_REALTIME_AUDIO_FORMAT, DEFAULT_REALTIME_MODEL, DEFAULT_REALTIME_VOICE, DEFAULT_SESSION_MAX_AGE_MS, DEFAULT_WEB_SEARCH_ENDPOINT, DNSConfigurator, type Database, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, ELKS_REALTIME_AUDIO_FORMATS, ELKS_REALTIME_WS_PATH, END_CALL_TOOL, EXTEND_CALL_TIME_TOOL, type ElksRealtimeAudioFormat, type ElksRealtimeAudioMessage, type ElksRealtimeByeMessage, type ElksRealtimeHelloMessage, type ElksRealtimeInboundMessage, type ElksRealtimeOutboundMessage, ElksRealtimeTransport, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, GET_CALL_STATUS_TOOL, GET_DATETIME_TOOL, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type GetDatetimeOptions, type GetUpdatesOptions, type HostName, type HostSession, type HostSessionResumeMode, type ImageAction, type ImageEditOptions, type InboundEmail, type InboundSmsEvent, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, LOAD_SKILL_TOOL, type LinkAdvisory, type LocalSmtpConfig, MEMORY_CATEGORIES, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type MediaBinary, type MediaCapability, type MediaCapabilityReport, type MediaFileResult, type MediaInfoResult, MediaManager, type MediaManagerOptions, type MediaStreamInfo, type MemoryCategory, type MemoryImportance, type MemoryQueryOptions, type MemoryRecaller, MemorySearchIndex, type MemorySource, type MemoryStats, OPENAI_REALTIME_URL, OPERATOR_QUERY_POLL_INTERVAL_MS, OPERATOR_QUERY_SUBJECT_TAG, OPERATOR_QUERY_TIMEOUT_MS, OPERATOR_QUERY_TIMEOUT_SENTINEL, type OpenClawPhoneMissionPolicy, type OperatorQueryNotificationInput, type OperatorQueryPollOptions, type OperatorQueryUrgency, type OperatorReplyKind, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, PERSONA_FILENAME, PHONE_CALLBACK_MAX_DELAY_SECONDS, PHONE_CALLBACK_MIN_DELAY_SECONDS, PHONE_CALL_CONTROL_PROVIDERS, PHONE_MAX_CONCURRENT_MISSIONS, PHONE_MIN_WEBHOOK_SECRET_LENGTH, PHONE_MISSION_STATES, PHONE_RATE_LIMIT_PER_HOUR, PHONE_RATE_LIMIT_PER_MINUTE, PHONE_REGION_SCOPES, PHONE_SERVER_MAX_ATTEMPTS, PHONE_SERVER_MAX_CALLBACK_CHAIN, PHONE_SERVER_MAX_CALL_DURATION_SECONDS, PHONE_SERVER_MAX_COST_PER_MISSION, PHONE_SERVER_MAX_EXTENSION_REQUESTS_PER_CALL, PHONE_SERVER_MAX_EXTENSION_SECONDS_PER_REQUEST, PHONE_SERVER_MAX_TOTAL_EXTENSION_SECONDS, PHONE_TASK_MAX_LENGTH, type ParsedAttachment, type ParsedEmail, type ParsedOperatorReply, type ParsedSms, type ParsedTelegramMessage, PathTraversalError, type PhoneAlternativePolicy, type PhoneCallMission, type PhoneCallbackPolicy, type PhoneConfirmPolicy, type PhoneExtensionPolicy, PhoneManager, type PhoneMissionStartValidationResult, type PhoneMissionState, type PhoneMissionTranscriptEntry, type PhoneMissionValidationIssue, type PhoneMissionValidationResult, type PhoneNumberRisk, type PhoneOperatorQuery, PhoneRateLimitError, type PhoneRegionScope, type PhoneScheduledCallback, type PhoneTransportConfig, type PhoneTransportProfile, type PhoneTransportProvider, type PhoneTransportValidationResult, PhoneWebhookAuthError, type PhoneWebhookResult, type PlanBridgeWakeArgs, type PurchasedDomain, REALTIME_AUDIO_SAMPLE_RATE, REALTIME_MAX_AUDIO_FRAME_BASE64, REALTIME_TOOL_CALL_TIMEOUT_MS, REALTIME_TOOL_DEFINITIONS, RECALL_MEMORY_TOOL, REDACTED, RELAY_PRESETS, type RealtimeBridgePort, type RealtimeBridgeTranscriptEntry, type RealtimeInboundEvent, type RealtimeInstructionOptions, type RealtimeSessionConfigOptions, type RealtimeToolCall, type RealtimeToolDefinition, type RealtimeToolHandler, type RealtimeToolResult, type RealtimeTransportAdapter, type RealtimeTransportProvider, RealtimeVoiceBridge, type RealtimeVoiceBridgeOptions, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, type ResumeErrorClassificationOptions, SCHEDULE_CALLBACK_TOOL, SEARCH_EMAIL_TOOL, SEARCH_SKILLS_TOOL, SPAM_THRESHOLD, type SafeJoinOptions, type SanitizeDetection, type SanitizeResult, type ScheduledCallbackRequest, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, type SendSmsInput, type SendSmsResult, type SendTelegramMessageOptions, type SendTelegramMessageResult, ServiceManager, type ServiceStatus, type SetWebhookOptions, type SetupConfig, SetupManager, type SetupResult, type Severity, type Skill, type SkillCategory, type SkillContext, type SkillExitStrategy, type SkillSummary, type SkillTactic, type SkillValidationError, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SmsProvider, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, type StartPhoneCallOptions, type StartPhoneCallResult, type StartPhoneMissionInput, TELEGRAM_API_BASE, TELEGRAM_CHUNK_SIZE, TELEGRAM_MESSAGE_LIMIT, TELEGRAM_MIN_WEBHOOK_SECRET_LENGTH, TELEGRAM_OPERATOR_QUERY_TAG, TELEGRAM_STOP_WORDS, TELEGRAM_WEBHOOK_SECRET_RE, TELEPHONY_TRANSPORT_CAPABILITIES, TWILIO_MEDIA_SAMPLE_RATE, TWILIO_REALTIME_WS_PATH, TelegramApiError, type TelegramApiOptions, type TelegramBotInfo, type TelegramChatType, type TelegramConfig, TelegramManager, type TelegramMessage, type TelegramMode, type TelephonyTransportCapability, ThreadCache, type ThreadCacheEntry, type ThreadCacheOptions, type ThreadIdInput, type ToolExecutor, type TtsGenerateOptions, type TunnelConfig, TunnelManager, type TwilioConnectedMessage, type TwilioMarkMessage, type TwilioMediaMessage, type TwilioRealtimeInboundMessage, type TwilioRealtimeOutboundMessage, TwilioRealtimeTransport, type TwilioStartMessage, type TwilioStopMessage, type TwilioStreamTwiMLOptions, UnsafeApiUrlError, type UpdateMemoryInput, type ValidatedPhoneMissionStart, type VideoAction, type VideoEditOptions, type VideoTimelineEntry, type VideoUnderstandOptions, type VideoUnderstandResult, type VoiceCloneOptions, WARNING_THRESHOLD, WEB_SEARCH_TOOL, WEB_SEARCH_UNTRUSTED_PREFIX, type WatcherOptions, type WebSearchOptions, assertWithinBase, bridgeWakeErrorMessage, bridgeWakeLastSeenAgeMs, buildApiUrl, buildDefaultPersona, buildElksAudioMessage, buildElksByeMessage, buildElksHandshakeMessages, buildElksInterruptMessage, buildElksListeningMessage, buildElksSendingMessage, buildInboundSecurityAdvisory, buildOpenAIRealtimeUrl, buildPhoneTransportConfig, buildRealtimeInstructions, buildRealtimeSessionConfig, buildRealtimeToolGuidance, buildTwilioClearMessage, buildTwilioMarkMessage, buildTwilioMediaMessage, buildTwilioSayTwiML, buildTwilioSignature, buildTwilioStreamTwiML, callTelegramApi, classifyEmailRoute, classifyPhoneNumberRisk, classifyResumeError, clearMediaCapabilityCache, closeDatabase, composeBridgeWakePrompt, createRealtimeTransport, createTestDatabase, createToolExecutor, debug, debugWarn, deleteTelegramWebhook, detectBinary, ensureDataDir, escapeXml, extractEmailAddress, extractVerificationCode, flushTelemetry, forgetHostSession, formatOperatorQueryTelegramMessage, getDatabase, getDatetime, getMediaCapabilities, getOperatorEmail, getSmsProvider, getTelegramChat, getTelegramMe, getTelegramUpdates, getTelegramWebhookInfo, hostSessionStoragePath, inferPhoneRegion, invalidateSkillCache, isInternalEmail, isLoopbackMailHost, isOperatorReplySender, isPhoneRegionAllowed, isSessionFresh, isTelegramChatAllowed, isTelegramStopCommand, isValidPhoneNumber, listSkills, loadAgentPersona, loadHostSession, loadSkill, mapProviderSmsStatus, nextTelegramOffset, normalizeAddress, normalizePhoneNumber, normalizeSubject, operatorPrefsStoragePath, operatorQuerySubject, parseElksRealtimeMessage, parseEmail, parseGoogleVoiceSms, parseOperatorQueryReply, parseTelegramOperatorReply, parseTelegramUpdate, parseTwilioRealtimeMessage, personaPathFor, planBridgeWake, pollForOperatorAnswer, recallMemory, recordToolCall, redactBotToken, redactObject, redactPhoneTransportConfig, redactSecret, redactSmsConfig, redactTelegramConfig, renderSkillAsPrompt, requireBinary, requireWhisperModel, resolveCallbackPolicy, resolveConfig, resolveExtensionPolicy, resolveTlsRejectUnauthorized, safeJoin, sanitizeEmail, saveAgentPersona, saveConfig, saveHostSession, saveUserSkill, scanOutboundEmail, scoreEmail, searchSkills, sendTelegramMessage, setOperatorEmail, setTelegramWebhook, setTelemetryVersion, shouldSkipBridgeWakeForLiveOperator, splitTelegramMessage, startRelayBridge, stem, stripTelegramMarkdown, threadIdFor, tokenize, tryJoin, userSkillsDir, validateApiUrl, validatePhoneMissionPolicy, validatePhoneMissionStart, validatePhoneTransportProfile, validateSkill, validateTwilioSignature, webSearch };
6388
+ export { AGENT_ROLES, AGENT_STATE_ROOT, ASK_OPERATOR_TOOL, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentMemoryEntry, type AgentMemoryFields, AgentMemoryManager, type AgentMemoryOptions, type AgentMemoryRead, AgentMemoryStore, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, type AudioAction, type AudioEditOptions, BRIDGE_OPERATOR_LIVE_WINDOW_MS, type BridgeMailContext, type BridgeWakeError, type BridgeWakePromptArgs, type BridgeWakeResult, type BridgeWakeRoute, type CachedMessage, CloudflareClient, type CreateAgentOptions, type CreateMemoryInput, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DEFAULT_CALLBACK_POLICY, DEFAULT_EXTENSION_POLICY, DEFAULT_REALTIME_AUDIO_FORMAT, DEFAULT_REALTIME_MODEL, DEFAULT_REALTIME_VOICE, DEFAULT_SESSION_MAX_AGE_MS, DEFAULT_WEB_SEARCH_ENDPOINT, DNSConfigurator, type Database, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, ELKS_REALTIME_AUDIO_FORMATS, ELKS_REALTIME_WS_PATH, END_CALL_TOOL, EXTEND_CALL_TIME_TOOL, type ElksRealtimeAudioFormat, type ElksRealtimeAudioMessage, type ElksRealtimeByeMessage, type ElksRealtimeHelloMessage, type ElksRealtimeInboundMessage, type ElksRealtimeOutboundMessage, ElksRealtimeTransport, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, GET_CALL_STATUS_TOOL, GET_DATETIME_TOOL, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type GetDatetimeOptions, type GetUpdatesOptions, type HostName, type HostSession, type HostSessionResumeMode, type ImageAction, type ImageEditOptions, type InboundEmail, type InboundSmsEvent, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, LOAD_SKILL_TOOL, type LinkAdvisory, type LocalSmtpConfig, MEMORY_CATEGORIES, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type MediaBinary, type MediaCapability, type MediaCapabilityReport, type MediaFileResult, type MediaInfoResult, MediaManager, type MediaManagerOptions, type MediaStreamInfo, type MemoryCategory, type MemoryImportance, type MemoryQueryOptions, type MemoryRecaller, MemorySearchIndex, type MemorySource, type MemoryStats, OPENAI_REALTIME_URL, OPERATOR_QUERY_POLL_INTERVAL_MS, OPERATOR_QUERY_SUBJECT_TAG, OPERATOR_QUERY_TIMEOUT_MS, OPERATOR_QUERY_TIMEOUT_SENTINEL, type OpenClawPhoneMissionPolicy, type OperatorQueryNotificationInput, type OperatorQueryPollOptions, type OperatorQueryUrgency, type OperatorReplyKind, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, PERSONA_FILENAME, PHONE_CALLBACK_MAX_DELAY_SECONDS, PHONE_CALLBACK_MIN_DELAY_SECONDS, PHONE_CALL_CONTROL_PROVIDERS, PHONE_MAX_CONCURRENT_MISSIONS, PHONE_MIN_WEBHOOK_SECRET_LENGTH, PHONE_MISSION_STATES, PHONE_RATE_LIMIT_PER_HOUR, PHONE_RATE_LIMIT_PER_MINUTE, PHONE_REGION_SCOPES, PHONE_SERVER_MAX_ATTEMPTS, PHONE_SERVER_MAX_CALLBACK_CHAIN, PHONE_SERVER_MAX_CALL_DURATION_SECONDS, PHONE_SERVER_MAX_COST_PER_MISSION, PHONE_SERVER_MAX_EXTENSION_REQUESTS_PER_CALL, PHONE_SERVER_MAX_EXTENSION_SECONDS_PER_REQUEST, PHONE_SERVER_MAX_TOTAL_EXTENSION_SECONDS, PHONE_TASK_MAX_LENGTH, type ParsedAttachment, type ParsedEmail, type ParsedOperatorReply, type ParsedSms, type ParsedTelegramMessage, PathTraversalError, type PhoneAlternativePolicy, type PhoneCallMission, type PhoneCallbackPolicy, type PhoneConfirmPolicy, type PhoneExtensionPolicy, PhoneManager, type PhoneMissionStartValidationResult, type PhoneMissionState, type PhoneMissionTranscriptEntry, type PhoneMissionValidationIssue, type PhoneMissionValidationResult, type PhoneNumberRisk, type PhoneOperatorQuery, PhoneRateLimitError, type PhoneRegionScope, type PhoneScheduledCallback, type PhoneTransportConfig, type PhoneTransportProfile, type PhoneTransportProvider, type PhoneTransportValidationResult, PhoneWebhookAuthError, type PhoneWebhookResult, type PlanBridgeWakeArgs, type PurchasedDomain, REALTIME_AUDIO_SAMPLE_RATE, REALTIME_MAX_AUDIO_FRAME_BASE64, REALTIME_TOOL_CALL_TIMEOUT_MS, REALTIME_TOOL_DEFINITIONS, RECALL_MEMORY_TOOL, REDACTED, RELAY_PRESETS, type RealtimeBridgePort, type RealtimeBridgeTranscriptEntry, type RealtimeInboundEvent, type RealtimeInstructionOptions, type RealtimeSessionConfigOptions, type RealtimeToolCall, type RealtimeToolDefinition, type RealtimeToolHandler, type RealtimeToolResult, type RealtimeTransportAdapter, type RealtimeTransportProvider, RealtimeVoiceBridge, type RealtimeVoiceBridgeOptions, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, type ResumeErrorClassificationOptions, SCHEDULE_CALLBACK_TOOL, SEARCH_EMAIL_TOOL, SEARCH_SKILLS_TOOL, SPAM_THRESHOLD, type SafeJoinOptions, type SanitizeDetection, type SanitizeResult, type ScheduledCallbackRequest, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, type SendSmsInput, type SendSmsResult, type SendTelegramMessageOptions, type SendTelegramMessageResult, ServiceManager, type ServiceStatus, type SetWebhookOptions, type SetupConfig, SetupManager, type SetupResult, type Severity, type Skill, type SkillCategory, type SkillContext, type SkillExitStrategy, type SkillSummary, type SkillTactic, type SkillValidationError, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SmsProvider, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, type StartPhoneCallOptions, type StartPhoneCallResult, type StartPhoneMissionInput, TELEGRAM_API_BASE, TELEGRAM_CHUNK_SIZE, TELEGRAM_MESSAGE_LIMIT, TELEGRAM_MIN_WEBHOOK_SECRET_LENGTH, TELEGRAM_OPERATOR_QUERY_TAG, TELEGRAM_STOP_WORDS, TELEGRAM_WEBHOOK_SECRET_RE, TELEPHONY_TRANSPORT_CAPABILITIES, TWILIO_MEDIA_SAMPLE_RATE, TWILIO_REALTIME_WS_PATH, TelegramApiError, type TelegramApiOptions, type TelegramBotInfo, type TelegramChatType, type TelegramConfig, TelegramManager, type TelegramMessage, type TelegramMode, type TelephonyTransportCapability, ThreadCache, type ThreadCacheEntry, type ThreadCacheOptions, type ThreadIdInput, type ToolExecutor, type TtsGenerateOptions, type TunnelConfig, TunnelManager, type TwilioConnectedMessage, type TwilioMarkMessage, type TwilioMediaMessage, type TwilioRealtimeInboundMessage, type TwilioRealtimeOutboundMessage, TwilioRealtimeTransport, type TwilioStartMessage, type TwilioStopMessage, type TwilioStreamTwiMLOptions, UnsafeApiUrlError, type UpdateMemoryInput, type ValidatedPhoneMissionStart, type VideoAction, type VideoEditOptions, type VideoTimelineEntry, type VideoUnderstandOptions, type VideoUnderstandResult, type VoiceCloneOptions, type VoiceProvider, type VoiceRuntimeConnection, WARNING_THRESHOLD, WEB_SEARCH_TOOL, WEB_SEARCH_UNTRUSTED_PREFIX, type WatcherOptions, type WebSearchOptions, assertWithinBase, bridgeWakeErrorMessage, bridgeWakeLastSeenAgeMs, buildApiUrl, buildDefaultPersona, buildElksAudioMessage, buildElksByeMessage, buildElksHandshakeMessages, buildElksInterruptMessage, buildElksListeningMessage, buildElksSendingMessage, buildInboundSecurityAdvisory, buildOpenAIRealtimeUrl, buildPhoneTransportConfig, buildRealtimeInstructions, buildRealtimeSessionConfig, buildRealtimeToolGuidance, buildTwilioClearMessage, buildTwilioMarkMessage, buildTwilioMediaMessage, buildTwilioSayTwiML, buildTwilioSignature, buildTwilioStreamTwiML, callTelegramApi, classifyEmailRoute, classifyPhoneNumberRisk, classifyResumeError, clearMediaCapabilityCache, closeDatabase, composeBridgeWakePrompt, createRealtimeTransport, createTestDatabase, createToolExecutor, debug, debugWarn, deleteTelegramWebhook, detectBinary, ensureDataDir, escapeXml, extractEmailAddress, extractVerificationCode, flushTelemetry, forgetHostSession, formatOperatorQueryTelegramMessage, getDatabase, getDatetime, getMediaCapabilities, getOperatorEmail, getSmsProvider, getTelegramChat, getTelegramMe, getTelegramUpdates, getTelegramWebhookInfo, getVoiceProvider, hostSessionStoragePath, inferPhoneRegion, invalidateSkillCache, isInternalEmail, isLoopbackMailHost, isOperatorReplySender, isPhoneRegionAllowed, isSessionFresh, isTelegramChatAllowed, isTelegramStopCommand, isValidPhoneNumber, listSkills, listVoiceProviders, loadAgentPersona, loadHostSession, loadSkill, mapProviderSmsStatus, nextTelegramOffset, normalizeAddress, normalizePhoneNumber, normalizeSubject, operatorPrefsStoragePath, operatorQuerySubject, parseElksRealtimeMessage, parseEmail, parseGoogleVoiceSms, parseOperatorQueryReply, parseTelegramOperatorReply, parseTelegramUpdate, parseTwilioRealtimeMessage, personaPathFor, planBridgeWake, pollForOperatorAnswer, recallMemory, recordToolCall, redactBotToken, redactObject, redactPhoneTransportConfig, redactSecret, redactSmsConfig, redactTelegramConfig, registerVoiceProvider, renderSkillAsPrompt, requireBinary, requireWhisperModel, resolveCallbackPolicy, resolveConfig, resolveExtensionPolicy, resolveTlsRejectUnauthorized, resolveVoiceRuntime, safeJoin, sanitizeEmail, saveAgentPersona, saveConfig, saveHostSession, saveUserSkill, scanOutboundEmail, scoreEmail, searchSkills, sendTelegramMessage, setOperatorEmail, setTelegramWebhook, setTelemetryVersion, shouldSkipBridgeWakeForLiveOperator, splitTelegramMessage, startRelayBridge, stem, stripTelegramMarkdown, threadIdFor, tokenize, tryJoin, userSkillsDir, validateApiUrl, validatePhoneMissionPolicy, validatePhoneMissionStart, validatePhoneTransportProfile, validateSkill, validateTwilioSignature, webSearch };
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  tokenize,
17
17
  userSkillsDir,
18
18
  validateSkill
19
- } from "./chunk-J6JINNJ3.js";
19
+ } from "./chunk-FYULLCZX.js";
20
20
  import {
21
21
  __require
22
22
  } from "./chunk-3RG5ZIWI.js";
@@ -920,6 +920,13 @@ function resolveConfig(overrides) {
920
920
  dataDir: env.AGENTICMAIL_DATA_DIR?.replace(/^~(?=\/|$)/, homedir()) ?? DEFAULT_CONFIG.dataDir
921
921
  };
922
922
  if (env.OPENAI_API_KEY) config.openaiApiKey = env.OPENAI_API_KEY;
923
+ if (env.XAI_API_KEY) {
924
+ config.voiceProviderKeys = config.voiceProviderKeys ?? {};
925
+ config.voiceProviderKeys.grok = env.XAI_API_KEY;
926
+ }
927
+ if (env.AGENTICMAIL_VOICE_RUNTIME && env.AGENTICMAIL_VOICE_RUNTIME.trim()) {
928
+ config.voiceRuntime = env.AGENTICMAIL_VOICE_RUNTIME.trim();
929
+ }
923
930
  const configPath = join(config.dataDir, "config.json");
924
931
  if (existsSync(configPath)) {
925
932
  try {
@@ -7009,6 +7016,73 @@ var PhoneManager = class {
7009
7016
  }]);
7010
7017
  return { mission: updated, query: answered, alreadyAnswered: false };
7011
7018
  }
7019
+ /**
7020
+ * v0.9.92 — auto-close stale operator queries.
7021
+ *
7022
+ * An operator query is "stale" when:
7023
+ * - it's unanswered, AND
7024
+ * - either (a) the mission is no longer live (status in
7025
+ * {completed, failed, cancelled}), OR (b) the query is older
7026
+ * than `maxAgeSeconds`.
7027
+ *
7028
+ * The sweeper marks each stale query as answered with a synthetic
7029
+ * "[auto-closed: ...]" answer + `answeredVia: 'auto-sweeper'`. This
7030
+ * (a) gets the query out of `listOpenOperatorQueries`, (b) clears
7031
+ * the bridge's "you have N open questions" hint to the operator,
7032
+ * and (c) leaves a permanent audit trail of WHY the query was
7033
+ * closed.
7034
+ *
7035
+ * Returns the count of queries closed + a per-mission breakdown
7036
+ * for logging.
7037
+ */
7038
+ sweepStaleOperatorQueries(opts = {}) {
7039
+ const maxAgeMs = (opts.maxAgeSeconds ?? 3600) * 1e3;
7040
+ const nowMs = opts.nowMs ?? Date.now();
7041
+ const TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
7042
+ const rows = this.db.prepare(
7043
+ `SELECT * FROM phone_missions WHERE metadata_json LIKE '%"operatorQueries"%'`
7044
+ ).all();
7045
+ const breakdown = [];
7046
+ let totalClosed = 0;
7047
+ let missionsTouched = 0;
7048
+ for (const row of rows) {
7049
+ const mission = rowToMission(row);
7050
+ const queries = readOperatorQueries(mission);
7051
+ const open = queries.filter((q) => !q.answer);
7052
+ if (open.length === 0) continue;
7053
+ const reason = TERMINAL.has(mission.status) ? "mission-terminal" : null;
7054
+ const closedAt = new Date(nowMs).toISOString();
7055
+ const nextQueries = queries.map((q) => {
7056
+ if (q.answer) return q;
7057
+ const askedAtMs = Date.parse(q.askedAt);
7058
+ const tooOld = Number.isFinite(askedAtMs) && nowMs - askedAtMs >= maxAgeMs;
7059
+ const queryReason = reason ?? (tooOld ? "age" : null);
7060
+ if (!queryReason) return q;
7061
+ return {
7062
+ ...q,
7063
+ answer: queryReason === "mission-terminal" ? `[auto-closed: mission ended (${mission.status}) before this question was answered]` : `[auto-closed: question went unanswered for over ${Math.round(maxAgeMs / 6e4)} minutes]`,
7064
+ answeredAt: closedAt,
7065
+ answeredVia: "auto-sweeper"
7066
+ };
7067
+ });
7068
+ const closedHere = nextQueries.filter(
7069
+ (q, i) => q.answer && !queries[i].answer && q.answeredVia === "auto-sweeper"
7070
+ ).length;
7071
+ if (closedHere === 0) continue;
7072
+ this.updateMissionStatus(mission.id, mission.status, {
7073
+ operatorQueries: nextQueries
7074
+ }, [{
7075
+ at: closedAt,
7076
+ source: "system",
7077
+ text: `Auto-closed ${closedHere} stale operator query(ies): ${reason ?? "aged out"}.`,
7078
+ metadata: { closedCount: closedHere, reason: reason ?? "age" }
7079
+ }]);
7080
+ totalClosed += closedHere;
7081
+ missionsTouched += 1;
7082
+ breakdown.push({ missionId: mission.id, closed: closedHere, reason: reason ?? "age" });
7083
+ }
7084
+ return { closed: totalClosed, missionsTouched, breakdown };
7085
+ }
7012
7086
  // ─── Callback on disconnect (plan §7) ─────────────────
7013
7087
  /**
7014
7088
  * Flag a mission for callback-on-disconnect: the call dropped while
@@ -9021,7 +9095,7 @@ var SEARCH_EMAIL_TOOL = {
9021
9095
  var SEARCH_SKILLS_TOOL = {
9022
9096
  type: "function",
9023
9097
  name: "search_skills",
9024
- description: "Search your skill library for a playbook that fits the situation you just hit on this call (billing dispute, debt collector tactics, reservation deadlock, etc). Returns ranked summaries \u2014 pick the best match and pass its id to load_skill. Fast.",
9098
+ description: 'Search your skill library for a playbook that fits the situation you just hit on this call (billing dispute, debt collector tactics, reservation deadlock, etc). Returns up to 5 ranked matches, each with: id, name, BM25 score, when_to_use (the specific situation the skill is for), first_principle (the playbook\'s strategic frame), and a `recommendation` field. If `recommendation` says LOAD IT NOW or the top score > 0.3, immediately call load_skill with the top id \u2014 do not deliberate. If scores are weak (< 0.15), re-search with a different phrasing instead of loading a poor match. The model that judges "is this the right skill" is YOU \u2014 but the recommendation field will tell you what the right move usually is.',
9025
9099
  parameters: {
9026
9100
  type: "object",
9027
9101
  properties: {
@@ -9676,7 +9750,7 @@ var RealtimeVoiceBridge = class {
9676
9750
  let loadSkill2;
9677
9751
  let renderSkillAsPrompt2;
9678
9752
  try {
9679
- ({ loadSkill: loadSkill2, renderSkillAsPrompt: renderSkillAsPrompt2 } = await import("./skills-RE3S767B.js"));
9753
+ ({ loadSkill: loadSkill2, renderSkillAsPrompt: renderSkillAsPrompt2 } = await import("./skills-DZVDIMTD.js"));
9680
9754
  } catch (err) {
9681
9755
  return { ok: false, message: `Skill registry unavailable: ${errorText(err)}` };
9682
9756
  }
@@ -10463,6 +10537,94 @@ function withTimeout(promise, ms) {
10463
10537
  return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
10464
10538
  }
10465
10539
 
10540
+ // src/phone/voice-providers/registry.ts
10541
+ var PROVIDERS2 = /* @__PURE__ */ new Map();
10542
+ function registerVoiceProvider(provider) {
10543
+ if (PROVIDERS2.has(provider.id)) {
10544
+ throw new Error(`Voice provider "${provider.id}" registered twice \u2014 id collision.`);
10545
+ }
10546
+ PROVIDERS2.set(provider.id, provider);
10547
+ }
10548
+ function listVoiceProviders() {
10549
+ return Array.from(PROVIDERS2.values());
10550
+ }
10551
+ function getVoiceProvider(id) {
10552
+ return PROVIDERS2.get(id);
10553
+ }
10554
+ function resolveVoiceRuntime(providerId, config, options = {}) {
10555
+ const id = (providerId || "openai").trim() || "openai";
10556
+ const provider = PROVIDERS2.get(id);
10557
+ if (!provider) {
10558
+ const known = Array.from(PROVIDERS2.keys()).join(", ") || "(none registered)";
10559
+ throw new Error(
10560
+ `Unknown voice runtime "${id}". Known providers: ${known}. Add a new one by dropping a file into packages/core/src/phone/voice-providers/.`
10561
+ );
10562
+ }
10563
+ let apiKey = "";
10564
+ let apiKeySource = "";
10565
+ if (provider.apiKeyConfigField) {
10566
+ const legacy = config[provider.apiKeyConfigField];
10567
+ if (legacy && legacy.trim()) {
10568
+ apiKey = legacy.trim();
10569
+ apiKeySource = `config.${provider.apiKeyConfigField}`;
10570
+ }
10571
+ }
10572
+ if (!apiKey) {
10573
+ const fromMap = config.voiceProviderKeys?.[provider.id];
10574
+ if (fromMap && fromMap.trim()) {
10575
+ apiKey = fromMap.trim();
10576
+ apiKeySource = `config.voiceProviderKeys.${provider.id}`;
10577
+ }
10578
+ }
10579
+ if (!apiKey) {
10580
+ const fromEnv = process.env[provider.apiKeyEnvVar];
10581
+ if (fromEnv && fromEnv.trim()) {
10582
+ apiKey = fromEnv.trim();
10583
+ apiKeySource = `env ${provider.apiKeyEnvVar}`;
10584
+ }
10585
+ }
10586
+ if (!apiKey) {
10587
+ throw new Error(
10588
+ `Voice provider "${provider.id}" (${provider.displayName}) selected, but no API key is configured. Set ${provider.apiKeyEnvVar} in your environment or save it to ~/.agenticmail/config.json under voiceProviderKeys.${provider.id}.`
10589
+ );
10590
+ }
10591
+ const model = options.model && options.model.trim() || provider.defaultModel;
10592
+ const url = `${provider.websocketBaseUrl}?model=${encodeURIComponent(model)}`;
10593
+ return {
10594
+ providerId: provider.id,
10595
+ providerDisplayName: provider.displayName,
10596
+ url,
10597
+ model,
10598
+ apiKey,
10599
+ apiKeySource
10600
+ };
10601
+ }
10602
+
10603
+ // src/phone/voice-providers/openai.ts
10604
+ registerVoiceProvider({
10605
+ id: "openai",
10606
+ displayName: "OpenAI Realtime (gpt-realtime)",
10607
+ websocketBaseUrl: "wss://api.openai.com/v1/realtime",
10608
+ defaultModel: "gpt-realtime",
10609
+ apiKeyEnvVar: "OPENAI_API_KEY",
10610
+ // Legacy: the original config.json schema used a dedicated
10611
+ // `openaiApiKey` field for this key. The resolver checks that field
10612
+ // before the generic voiceProviderKeys map so existing installs
10613
+ // continue to work without migration.
10614
+ apiKeyConfigField: "openaiApiKey",
10615
+ description: "OpenAI Realtime (gpt-realtime). Default voice runtime; supports linear PCM @ 24 kHz (46elks) and G.711 \xB5-law @ 8 kHz (Twilio) without transcoding."
10616
+ });
10617
+
10618
+ // src/phone/voice-providers/grok.ts
10619
+ registerVoiceProvider({
10620
+ id: "grok",
10621
+ displayName: "xAI Grok Voice Agent",
10622
+ websocketBaseUrl: "wss://api.x.ai/v1/realtime",
10623
+ defaultModel: "grok-voice-latest",
10624
+ apiKeyEnvVar: "XAI_API_KEY",
10625
+ description: 'xAI Grok Voice Agent \u2014 OpenAI-Realtime-compatible WebSocket protocol; select via mission policy.voiceRuntime="grok" or env AGENTICMAIL_VOICE_RUNTIME=grok.'
10626
+ });
10627
+
10466
10628
  // src/telemetry.ts
10467
10629
  import { randomUUID as randomUUID2 } from "crypto";
10468
10630
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "fs";
@@ -14792,6 +14954,7 @@ export {
14792
14954
  getTelegramMe,
14793
14955
  getTelegramUpdates,
14794
14956
  getTelegramWebhookInfo,
14957
+ getVoiceProvider,
14795
14958
  hostSessionStoragePath,
14796
14959
  inferPhoneRegion,
14797
14960
  invalidateSkillCache,
@@ -14804,6 +14967,7 @@ export {
14804
14967
  isTelegramStopCommand,
14805
14968
  isValidPhoneNumber,
14806
14969
  listSkills,
14970
+ listVoiceProviders,
14807
14971
  loadAgentPersona,
14808
14972
  loadHostSession,
14809
14973
  loadSkill,
@@ -14832,6 +14996,7 @@ export {
14832
14996
  redactSecret,
14833
14997
  redactSmsConfig,
14834
14998
  redactTelegramConfig,
14999
+ registerVoiceProvider,
14835
15000
  renderSkillAsPrompt,
14836
15001
  requireBinary,
14837
15002
  requireWhisperModel,
@@ -14839,6 +15004,7 @@ export {
14839
15004
  resolveConfig,
14840
15005
  resolveExtensionPolicy,
14841
15006
  resolveTlsRejectUnauthorized,
15007
+ resolveVoiceRuntime,
14842
15008
  safeJoin,
14843
15009
  sanitizeEmail,
14844
15010
  saveAgentPersona,
@@ -8,7 +8,7 @@ import {
8
8
  skillFilename,
9
9
  userSkillsDir,
10
10
  validateSkill
11
- } from "./chunk-J6JINNJ3.js";
11
+ } from "./chunk-FYULLCZX.js";
12
12
  import "./chunk-3RG5ZIWI.js";
13
13
  export {
14
14
  invalidateSkillCache,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/core",
3
- "version": "0.9.37",
3
+ "version": "0.9.39",
4
4
  "description": "Core SDK for AgenticMail — email, SMS, and phone call-control for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",