@agenticmail/core 0.9.26 → 0.9.28
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/dist/index.cjs +834 -6
- package/dist/index.d.cts +474 -161
- package/dist/index.d.ts +474 -161
- package/dist/index.js +834 -6
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -2600,6 +2600,161 @@ declare class TwilioRealtimeTransport implements RealtimeTransportAdapter {
|
|
|
2600
2600
|
/** Construct the transport adapter for a provider. */
|
|
2601
2601
|
declare function createRealtimeTransport(provider: RealtimeTransportProvider): RealtimeTransportAdapter;
|
|
2602
2602
|
|
|
2603
|
+
declare const PHONE_REGION_SCOPES: readonly ["AT", "DE", "EU", "WORLD"];
|
|
2604
|
+
type PhoneRegionScope = typeof PHONE_REGION_SCOPES[number];
|
|
2605
|
+
declare const TELEPHONY_TRANSPORT_CAPABILITIES: readonly ["sms", "call_control", "realtime_media", "recording_supported"];
|
|
2606
|
+
type TelephonyTransportCapability = typeof TELEPHONY_TRANSPORT_CAPABILITIES[number];
|
|
2607
|
+
declare const PHONE_MISSION_STATES: readonly ["draft", "approved", "dialing", "connected", "conversing", "needs_operator", "completed", "failed", "cancelled"];
|
|
2608
|
+
type PhoneMissionState = typeof PHONE_MISSION_STATES[number];
|
|
2609
|
+
type PhoneNumberRisk = 'invalid' | 'standard' | 'premium_or_special';
|
|
2610
|
+
/**
|
|
2611
|
+
* Server-side hard ceilings for a phone mission policy.
|
|
2612
|
+
*
|
|
2613
|
+
* Hardening (#42-H1 / #43-H2) — `policy` is supplied by the calling agent,
|
|
2614
|
+
* so a caller-set `maxCallDurationSeconds: 999999` or `maxCostPerMission:
|
|
2615
|
+
* 1e9` is self-authorized and meaningless as a limit. These constants are
|
|
2616
|
+
* the real ceiling: `validatePhoneMissionPolicy` clamps every caller value
|
|
2617
|
+
* down to (at most) the server cap, so the effective policy can only ever
|
|
2618
|
+
* be MORE restrictive than the server, never less. A phone mission places
|
|
2619
|
+
* real, billed calls — these bounds are the financial blast-radius cap.
|
|
2620
|
+
*/
|
|
2621
|
+
declare const PHONE_SERVER_MAX_CALL_DURATION_SECONDS = 7200;
|
|
2622
|
+
declare const PHONE_SERVER_MAX_COST_PER_MISSION = 5;
|
|
2623
|
+
declare const PHONE_SERVER_MAX_ATTEMPTS = 3;
|
|
2624
|
+
/** Hard cap on the free-text `task` fed to the voice runtime. */
|
|
2625
|
+
declare const PHONE_TASK_MAX_LENGTH = 2000;
|
|
2626
|
+
interface PhoneConfirmPolicy {
|
|
2627
|
+
paymentDetails: 'never';
|
|
2628
|
+
contractCommitment: 'never';
|
|
2629
|
+
costOverLimit: 'needs_operator';
|
|
2630
|
+
sensitivePersonalData: 'needs_operator';
|
|
2631
|
+
unclearAlternative: 'needs_operator';
|
|
2632
|
+
}
|
|
2633
|
+
interface PhoneAlternativePolicy {
|
|
2634
|
+
maxTimeShiftMinutes: number;
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* How aggressively the voice agent is allowed to ask for more time on a
|
|
2638
|
+
* live call. Three independently-enforced caps:
|
|
2639
|
+
*
|
|
2640
|
+
* - maxSecondsPerRequest: ceiling on a SINGLE `extend_call_time` call.
|
|
2641
|
+
* The agent can ask for less; it cannot ask for more.
|
|
2642
|
+
* - maxRequestsPerCall: how many times it may invoke the tool at all.
|
|
2643
|
+
* Once exhausted, further requests are denied even if there is
|
|
2644
|
+
* budget left in the total ceiling.
|
|
2645
|
+
* - maxTotalExtensionSeconds: TOTAL extra seconds across all granted
|
|
2646
|
+
* requests for this single call. A safety net under
|
|
2647
|
+
* `maxRequestsPerCall × maxSecondsPerRequest` in case the math
|
|
2648
|
+
* drifts. The call cannot run past
|
|
2649
|
+
* PHONE_SERVER_MAX_CALL_DURATION_SECONDS regardless of this.
|
|
2650
|
+
*
|
|
2651
|
+
* Auto-approved within these bounds — the agent does NOT need to bother
|
|
2652
|
+
* the operator for routine "5 more minutes" requests. The whole point
|
|
2653
|
+
* is to keep the agent agentic without making it ask permission every
|
|
2654
|
+
* time it needs more line time to finish the job it was sent to do.
|
|
2655
|
+
*/
|
|
2656
|
+
interface PhoneExtensionPolicy {
|
|
2657
|
+
maxSecondsPerRequest: number;
|
|
2658
|
+
maxRequestsPerCall: number;
|
|
2659
|
+
maxTotalExtensionSeconds: number;
|
|
2660
|
+
}
|
|
2661
|
+
/**
|
|
2662
|
+
* Whether (and how) the agent may schedule an automatic callback when
|
|
2663
|
+
* it can't finish the call in time, hits a context limit, gets cut off,
|
|
2664
|
+
* or just wants to follow up later.
|
|
2665
|
+
*
|
|
2666
|
+
* - allowAutoCallback: master switch. False disables `schedule_callback`
|
|
2667
|
+
* entirely (the tool is still callable but it returns a denial).
|
|
2668
|
+
* - maxCallbackChain: how many re-dials can chain off this mission.
|
|
2669
|
+
* 0 means "no callbacks at all" (equivalent to allowAutoCallback=false
|
|
2670
|
+
* for this purpose); 1 means one callback is permitted but the
|
|
2671
|
+
* callback itself cannot schedule another; 2 means two hops are
|
|
2672
|
+
* allowed; etc. Bounded by PHONE_SERVER_MAX_CALLBACK_CHAIN.
|
|
2673
|
+
*/
|
|
2674
|
+
interface PhoneCallbackPolicy {
|
|
2675
|
+
allowAutoCallback: boolean;
|
|
2676
|
+
maxCallbackChain: number;
|
|
2677
|
+
}
|
|
2678
|
+
interface OpenClawPhoneMissionPolicy {
|
|
2679
|
+
policyVersion: 1;
|
|
2680
|
+
regionAllowlist: PhoneRegionScope[];
|
|
2681
|
+
maxCallDurationSeconds: number;
|
|
2682
|
+
maxCostPerMission: number;
|
|
2683
|
+
maxAttempts: number;
|
|
2684
|
+
transcriptEnabled: boolean;
|
|
2685
|
+
recordingEnabled: boolean;
|
|
2686
|
+
confirmPolicy: PhoneConfirmPolicy;
|
|
2687
|
+
alternativePolicy: PhoneAlternativePolicy;
|
|
2688
|
+
/**
|
|
2689
|
+
* NEW in v0.9.81 — extension envelope. Optional for backward
|
|
2690
|
+
* compatibility; falls back to {@link DEFAULT_EXTENSION_POLICY} when
|
|
2691
|
+
* the caller omits it.
|
|
2692
|
+
*/
|
|
2693
|
+
extensionPolicy?: PhoneExtensionPolicy;
|
|
2694
|
+
/**
|
|
2695
|
+
* NEW in v0.9.81 — callback envelope. Optional; falls back to
|
|
2696
|
+
* {@link DEFAULT_CALLBACK_POLICY} when omitted.
|
|
2697
|
+
*/
|
|
2698
|
+
callbackPolicy?: PhoneCallbackPolicy;
|
|
2699
|
+
}
|
|
2700
|
+
interface StartPhoneMissionInput {
|
|
2701
|
+
to: string;
|
|
2702
|
+
task: string;
|
|
2703
|
+
policy: OpenClawPhoneMissionPolicy;
|
|
2704
|
+
voiceRuntimeRef?: string;
|
|
2705
|
+
}
|
|
2706
|
+
interface PhoneTransportProfile {
|
|
2707
|
+
provider: string;
|
|
2708
|
+
phoneNumber: string;
|
|
2709
|
+
capabilities: TelephonyTransportCapability[];
|
|
2710
|
+
supportedRegions: PhoneRegionScope[];
|
|
2711
|
+
}
|
|
2712
|
+
interface PhoneMissionValidationIssue {
|
|
2713
|
+
code: string;
|
|
2714
|
+
field: string;
|
|
2715
|
+
message: string;
|
|
2716
|
+
}
|
|
2717
|
+
type PhoneMissionValidationResult = {
|
|
2718
|
+
ok: true;
|
|
2719
|
+
policy: OpenClawPhoneMissionPolicy;
|
|
2720
|
+
issues: [];
|
|
2721
|
+
} | {
|
|
2722
|
+
ok: false;
|
|
2723
|
+
issues: PhoneMissionValidationIssue[];
|
|
2724
|
+
};
|
|
2725
|
+
type PhoneTransportValidationResult = {
|
|
2726
|
+
ok: true;
|
|
2727
|
+
transport: PhoneTransportProfile;
|
|
2728
|
+
issues: [];
|
|
2729
|
+
} | {
|
|
2730
|
+
ok: false;
|
|
2731
|
+
issues: PhoneMissionValidationIssue[];
|
|
2732
|
+
};
|
|
2733
|
+
interface ValidatedPhoneMissionStart {
|
|
2734
|
+
to: string;
|
|
2735
|
+
task: string;
|
|
2736
|
+
policy: OpenClawPhoneMissionPolicy;
|
|
2737
|
+
targetRegion: PhoneRegionScope;
|
|
2738
|
+
transport: PhoneTransportProfile;
|
|
2739
|
+
voiceRuntimeRef?: string;
|
|
2740
|
+
}
|
|
2741
|
+
type PhoneMissionStartValidationResult = {
|
|
2742
|
+
ok: true;
|
|
2743
|
+
mission: ValidatedPhoneMissionStart;
|
|
2744
|
+
issues: [];
|
|
2745
|
+
} | {
|
|
2746
|
+
ok: false;
|
|
2747
|
+
issues: PhoneMissionValidationIssue[];
|
|
2748
|
+
};
|
|
2749
|
+
declare function validatePhoneMissionPolicy(policy: unknown): PhoneMissionValidationResult;
|
|
2750
|
+
declare function validatePhoneTransportProfile(transport: unknown): PhoneTransportValidationResult;
|
|
2751
|
+
declare function inferPhoneRegion(phoneNumber: string): PhoneRegionScope | null;
|
|
2752
|
+
declare function isPhoneRegionAllowed(region: PhoneRegionScope, allowlist: readonly PhoneRegionScope[]): boolean;
|
|
2753
|
+
declare function classifyPhoneNumberRisk(phoneNumber: string): PhoneNumberRisk;
|
|
2754
|
+
declare function validatePhoneMissionStart(input: unknown, transport: unknown, options?: {
|
|
2755
|
+
allowPremiumOrSpecialNumbers?: boolean;
|
|
2756
|
+
}): PhoneMissionStartValidationResult;
|
|
2757
|
+
|
|
2603
2758
|
/**
|
|
2604
2759
|
* Realtime voice tools — the tool-using layer on top of the v0.9.52
|
|
2605
2760
|
* audio bridge (see `realtime-bridge.ts`).
|
|
@@ -2931,64 +3086,6 @@ declare function extractEmailAddress(value: string | null | undefined): string;
|
|
|
2931
3086
|
*/
|
|
2932
3087
|
declare function isOperatorReplySender(from: string | null | undefined, operatorEmail: string | null | undefined): boolean;
|
|
2933
3088
|
|
|
2934
|
-
/**
|
|
2935
|
-
* Realtime voice bridge — wires the OpenAI Realtime API to a phone
|
|
2936
|
-
* carrier's realtime-media WebSocket so a phone mission can actually
|
|
2937
|
-
* *converse*.
|
|
2938
|
-
*
|
|
2939
|
-
* # Shape of the integration
|
|
2940
|
-
*
|
|
2941
|
-
* caller ⇄ carrier ⇄ (carrier media WebSocket) ⇄ AgenticMail
|
|
2942
|
-
* │
|
|
2943
|
-
* RealtimeVoiceBridge
|
|
2944
|
-
* │
|
|
2945
|
-
* (OpenAI Realtime WebSocket)
|
|
2946
|
-
* │
|
|
2947
|
-
* gpt-realtime
|
|
2948
|
-
*
|
|
2949
|
-
* The carrier streams the live call audio to AgenticMail as JSON frames
|
|
2950
|
-
* (base64 audio); AgenticMail relays them to OpenAI as
|
|
2951
|
-
* `input_audio_buffer.append`; OpenAI streams synthesised speech back
|
|
2952
|
-
* as `response.output_audio.delta`; AgenticMail relays that to the
|
|
2953
|
-
* carrier. Server-side VAD on the OpenAI session handles turn-taking —
|
|
2954
|
-
* no manual commit / response.create.
|
|
2955
|
-
*
|
|
2956
|
-
* # Provider-pluggable transport
|
|
2957
|
-
*
|
|
2958
|
-
* The carrier side speaks a provider-specific wire protocol — 46elks
|
|
2959
|
-
* (`hello`/`audio`/`bye`, linear PCM) and Twilio Media Streams
|
|
2960
|
-
* (`connected`/`start`/`media`/`stop`, G.711 µ-law). Those differences
|
|
2961
|
-
* are isolated behind a {@link RealtimeTransportAdapter}: the bridge
|
|
2962
|
-
* itself — OpenAI session lifecycle, function calling, barge-in,
|
|
2963
|
-
* transcript, teardown — is identical across providers and lives here
|
|
2964
|
-
* once. The bridge defaults to the 46elks adapter, so existing callers
|
|
2965
|
-
* that pass no `transport` keep their exact prior behaviour.
|
|
2966
|
-
*
|
|
2967
|
-
* # Memory injection — the whole point
|
|
2968
|
-
*
|
|
2969
|
-
* Before the OpenAI session starts, the agent's persistent memory is
|
|
2970
|
-
* rendered (`AgentMemoryManager.generateMemoryContext()`) and folded
|
|
2971
|
-
* into the Realtime session `instructions`. The model is told to treat
|
|
2972
|
-
* that block as *its own* long-term knowledge — so on the call it acts
|
|
2973
|
-
* with full continuity, as if it had always known those things.
|
|
2974
|
-
*
|
|
2975
|
-
* # Why this file is transport-agnostic
|
|
2976
|
-
*
|
|
2977
|
-
* `RealtimeVoiceBridge` never touches a socket. It takes two abstract
|
|
2978
|
-
* {@link RealtimeBridgePort}s (one per side) and is driven by
|
|
2979
|
-
* `handle*Message` / `handle*Open` / `handle*Close` calls. The real
|
|
2980
|
-
* WebSocket plumbing lives in `@agenticmail/api` (which has the `ws`
|
|
2981
|
-
* dependency); tests drive the bridge with in-memory fake ports. This
|
|
2982
|
-
* keeps `@agenticmail/core` dependency-free and the bridge logic fully
|
|
2983
|
-
* unit-testable without a live OpenAI key or a carrier websocket number.
|
|
2984
|
-
*
|
|
2985
|
-
* The exact OpenAI Realtime wire shapes below are the GA `gpt-realtime`
|
|
2986
|
-
* protocol (session config nested under `audio.input` / `audio.output`,
|
|
2987
|
-
* `format` as an object, `response.output_audio.delta` for output). The
|
|
2988
|
-
* legacy beta output event name `response.audio.delta` is also handled
|
|
2989
|
-
* defensively — some `gpt-realtime` deployments still emit it.
|
|
2990
|
-
*/
|
|
2991
|
-
|
|
2992
3089
|
/** OpenAI Realtime WebSocket base URL (model passed as `?model=`). */
|
|
2993
3090
|
declare const OPENAI_REALTIME_URL = "wss://api.openai.com/v1/realtime";
|
|
2994
3091
|
/** GA Realtime model. */
|
|
@@ -3033,6 +3130,25 @@ interface RealtimeInstructionOptions {
|
|
|
3033
3130
|
* automatically by `buildRealtimeSessionConfig()` when tools are set.
|
|
3034
3131
|
*/
|
|
3035
3132
|
toolGuidance?: string;
|
|
3133
|
+
/**
|
|
3134
|
+
* v0.9.81 — when set, the agent gets an explicit "you have N minutes"
|
|
3135
|
+
* preamble so it can pace the conversation. Pulled into the
|
|
3136
|
+
* instructions even when the bridge itself isn't enforcing a soft
|
|
3137
|
+
* deadline (e.g. for downstream voice runtimes that consume the
|
|
3138
|
+
* same instructions string).
|
|
3139
|
+
*/
|
|
3140
|
+
callBudget?: {
|
|
3141
|
+
/** Total budget for this call in seconds. */
|
|
3142
|
+
seconds: number;
|
|
3143
|
+
/**
|
|
3144
|
+
* True when extend_call_time + schedule_callback are wired to the
|
|
3145
|
+
* session. Toggles a separate "if you need more time" hint into
|
|
3146
|
+
* the preamble. False ⇒ the agent should manage strictly within
|
|
3147
|
+
* its budget.
|
|
3148
|
+
*/
|
|
3149
|
+
extensionEnabled: boolean;
|
|
3150
|
+
callbackEnabled: boolean;
|
|
3151
|
+
};
|
|
3036
3152
|
}
|
|
3037
3153
|
/**
|
|
3038
3154
|
* Compose the Realtime session `instructions` string. The agent's
|
|
@@ -3163,12 +3279,80 @@ interface RealtimeVoiceBridgeOptions {
|
|
|
3163
3279
|
* is how many tool calls were still in flight at teardown — non-zero
|
|
3164
3280
|
* means the call dropped mid-tool (e.g. an unanswered `ask_operator`),
|
|
3165
3281
|
* which is the signal the API layer uses to arm callback-on-disconnect
|
|
3166
|
-
* (plan §7).
|
|
3282
|
+
* (plan §7). `endedByTimeBudget` is true when the bridge itself ended
|
|
3283
|
+
* the call because the soft deadline elapsed and the grace period
|
|
3284
|
+
* expired — the API layer uses this to log a different teardown
|
|
3285
|
+
* reason and to skip "callback flagged" if the agent already used
|
|
3286
|
+
* `schedule_callback` itself.
|
|
3167
3287
|
*/
|
|
3168
3288
|
onEnd?: (summary: {
|
|
3169
3289
|
reason: string;
|
|
3170
3290
|
pendingToolCalls: number;
|
|
3291
|
+
endedByTimeBudget?: boolean;
|
|
3171
3292
|
}) => void;
|
|
3293
|
+
/**
|
|
3294
|
+
* NEW in v0.9.81 — initial soft time budget for the call, in seconds.
|
|
3295
|
+
* When set, the bridge schedules a graceful-end timer with reminders
|
|
3296
|
+
* (T-120s, T-30s) and a final 30s grace window after the budget
|
|
3297
|
+
* elapses. The carrier-level hard cap (Twilio `TimeLimit` / 46elks
|
|
3298
|
+
* `timeout`) still applies on top; this is the AGENT's view of how
|
|
3299
|
+
* long it has, which it can grow via {@link extendCallTime}.
|
|
3300
|
+
* Omitted ⇒ no bridge-level timer (legacy behaviour).
|
|
3301
|
+
*/
|
|
3302
|
+
callBudgetSeconds?: number;
|
|
3303
|
+
/**
|
|
3304
|
+
* NEW in v0.9.81 — extension policy for {@link extendCallTime}. When
|
|
3305
|
+
* the agent asks for more time, every request is auto-approved up to
|
|
3306
|
+
* these caps. Omitted ⇒ extensions are denied.
|
|
3307
|
+
*/
|
|
3308
|
+
extensionPolicy?: PhoneExtensionPolicy;
|
|
3309
|
+
/**
|
|
3310
|
+
* NEW in v0.9.81 — callback policy for {@link scheduleCallback}.
|
|
3311
|
+
* Omitted ⇒ scheduled callbacks are denied.
|
|
3312
|
+
*/
|
|
3313
|
+
callbackPolicy?: PhoneCallbackPolicy;
|
|
3314
|
+
/**
|
|
3315
|
+
* NEW in v0.9.81 — fires when the agent calls schedule_callback and the
|
|
3316
|
+
* bridge has approved it against the policy. The API layer persists
|
|
3317
|
+
* the request to mission metadata and the scheduler dials the
|
|
3318
|
+
* callback when the requested `at` timestamp arrives. `priorContext`
|
|
3319
|
+
* is the model-supplied summary plus a system-built transcript
|
|
3320
|
+
* digest the bridge composed at request-time.
|
|
3321
|
+
*/
|
|
3322
|
+
onCallbackScheduled?: (req: ScheduledCallbackRequest) => void;
|
|
3323
|
+
/**
|
|
3324
|
+
* NEW in v0.9.81 — injectable wall-clock + timer functions so unit
|
|
3325
|
+
* tests can fast-forward the deadline without sleeping. Defaults to
|
|
3326
|
+
* the real `Date.now` / `setTimeout` / `clearTimeout`.
|
|
3327
|
+
*/
|
|
3328
|
+
now?: () => number;
|
|
3329
|
+
setTimeoutFn?: typeof setTimeout;
|
|
3330
|
+
clearTimeoutFn?: typeof clearTimeout;
|
|
3331
|
+
}
|
|
3332
|
+
/**
|
|
3333
|
+
* Captures everything the API layer needs to honour a `schedule_callback`
|
|
3334
|
+
* tool call: when to dial, why, what the model wants to remember about
|
|
3335
|
+
* the conversation so far, and a transcript digest the bridge composed
|
|
3336
|
+
* at the moment of the request. The digest is the "context from the
|
|
3337
|
+
* previous call" the operator asked for — without it, the next call's
|
|
3338
|
+
* agent would start cold.
|
|
3339
|
+
*/
|
|
3340
|
+
interface ScheduledCallbackRequest {
|
|
3341
|
+
/** Wall-clock ISO timestamp when the callback should fire. */
|
|
3342
|
+
at: string;
|
|
3343
|
+
/** Free-text reason from the agent (transcribed verbatim to logs). */
|
|
3344
|
+
reason: string;
|
|
3345
|
+
/**
|
|
3346
|
+
* The agent's own summary of what the next-call agent should know.
|
|
3347
|
+
* Trimmed to {@link MAX_CALLBACK_SUMMARY_LENGTH} chars.
|
|
3348
|
+
*/
|
|
3349
|
+
agentSummary: string;
|
|
3350
|
+
/**
|
|
3351
|
+
* System-built digest of the assistant + system transcript so far,
|
|
3352
|
+
* trimmed to {@link MAX_CALLBACK_TRANSCRIPT_DIGEST_LENGTH} chars.
|
|
3353
|
+
* The next call sees this verbatim under "# What you said before".
|
|
3354
|
+
*/
|
|
3355
|
+
transcriptDigest: string;
|
|
3172
3356
|
}
|
|
3173
3357
|
/**
|
|
3174
3358
|
* Bridges a phone carrier's realtime-media connection to an OpenAI
|
|
@@ -3194,6 +3378,46 @@ declare class RealtimeVoiceBridge {
|
|
|
3194
3378
|
private readonly maxToolCallMs;
|
|
3195
3379
|
private readonly onTranscript?;
|
|
3196
3380
|
private readonly onEnd?;
|
|
3381
|
+
/** Injectable clock + timers (tests substitute fakes). */
|
|
3382
|
+
private readonly nowFn;
|
|
3383
|
+
private readonly setTimeoutFn;
|
|
3384
|
+
private readonly clearTimeoutFn;
|
|
3385
|
+
/** v0.9.81 — extension / callback state. */
|
|
3386
|
+
private readonly extensionPolicy?;
|
|
3387
|
+
private readonly callbackPolicy?;
|
|
3388
|
+
private readonly onCallbackScheduled?;
|
|
3389
|
+
/** Initial soft budget, in seconds. 0 = no bridge-side timer (legacy). */
|
|
3390
|
+
private readonly initialBudgetSeconds;
|
|
3391
|
+
/** Wall-clock ms when the call started, set on first carrier hello. */
|
|
3392
|
+
private callStartedAtMs;
|
|
3393
|
+
/** Wall-clock ms when the soft deadline fires. Bumped by extensions. */
|
|
3394
|
+
private softDeadlineMs;
|
|
3395
|
+
/** Soft-end timer (fires once, then schedules the grace timer). */
|
|
3396
|
+
private softEndTimer;
|
|
3397
|
+
/** Final hard-end timer that fires after the grace window. */
|
|
3398
|
+
private graceEndTimer;
|
|
3399
|
+
/** Reminder timers for the T-N marks. Cleared/re-armed on extensions. */
|
|
3400
|
+
private reminderTimers;
|
|
3401
|
+
/** Marks (in seconds-remaining) we've already fired this call. Dedup
|
|
3402
|
+
* prevents re-injecting the same reminder after an extension if the
|
|
3403
|
+
* new deadline still has us past the same mark. */
|
|
3404
|
+
private firedReminderMarks;
|
|
3405
|
+
/** Count of extensions granted this call. */
|
|
3406
|
+
private extensionsUsed;
|
|
3407
|
+
/** Total extra seconds granted across all extensions this call. */
|
|
3408
|
+
private extensionSecondsUsed;
|
|
3409
|
+
/** True once the agent's schedule_callback request was accepted. */
|
|
3410
|
+
private callbackArmed;
|
|
3411
|
+
/** Captured for the API layer when the soft deadline fires. */
|
|
3412
|
+
private endedByTimeBudgetFlag;
|
|
3413
|
+
/**
|
|
3414
|
+
* Sliding window of recent assistant + system utterances, used to
|
|
3415
|
+
* build the transcript digest carried into a scheduled callback.
|
|
3416
|
+
* Capped at {@link MAX_CALLBACK_TRANSCRIPT_DIGEST_LENGTH} chars so
|
|
3417
|
+
* the digest itself can always be produced cheaply even on a long
|
|
3418
|
+
* call.
|
|
3419
|
+
*/
|
|
3420
|
+
private readonly recentUtterances;
|
|
3197
3421
|
/** Carrier `hello`/`start` received — the call leg is live. */
|
|
3198
3422
|
private helloSeen;
|
|
3199
3423
|
/** OpenAI socket open + `session.update` sent. */
|
|
@@ -3343,6 +3567,149 @@ declare class RealtimeVoiceBridge {
|
|
|
3343
3567
|
* > against current OpenAI docs before the live smoke test.
|
|
3344
3568
|
*/
|
|
3345
3569
|
private answerToolCall;
|
|
3570
|
+
/** True if the agent's `schedule_callback` request has been accepted. */
|
|
3571
|
+
get isCallbackArmed(): boolean;
|
|
3572
|
+
/**
|
|
3573
|
+
* Seconds remaining on the current soft deadline, floored at 0. Returns
|
|
3574
|
+
* the initial budget if hello hasn't fired yet, and `Infinity` if no
|
|
3575
|
+
* call budget was configured (legacy mode). Used by `get_call_status`.
|
|
3576
|
+
*/
|
|
3577
|
+
getTimeRemainingSeconds(): number;
|
|
3578
|
+
/**
|
|
3579
|
+
* Public extension state snapshot for `get_call_status`. Each value is
|
|
3580
|
+
* "what the agent has left" so the model can decide whether to call
|
|
3581
|
+
* `extend_call_time` at all — exposing both the per-request cap AND
|
|
3582
|
+
* the remaining budget makes greedy / unbounded extension attempts
|
|
3583
|
+
* impossible.
|
|
3584
|
+
*/
|
|
3585
|
+
getExtensionStatus(): {
|
|
3586
|
+
extensionsUsed: number;
|
|
3587
|
+
extensionsRemaining: number;
|
|
3588
|
+
secondsUsedSoFar: number;
|
|
3589
|
+
secondsAvailable: number;
|
|
3590
|
+
maxSecondsPerRequest: number;
|
|
3591
|
+
};
|
|
3592
|
+
/**
|
|
3593
|
+
* Grant (or refuse) more time on the call. Auto-approved within all
|
|
3594
|
+
* three policy caps; the granted amount is the min of:
|
|
3595
|
+
*
|
|
3596
|
+
* - the agent's requested seconds (positive integer, clamped > 0)
|
|
3597
|
+
* - policy.maxSecondsPerRequest
|
|
3598
|
+
* - policy.maxTotalExtensionSeconds − seconds already granted
|
|
3599
|
+
*
|
|
3600
|
+
* AND we won't push the new deadline past the hard ceiling
|
|
3601
|
+
* (PHONE_SERVER_MAX_CALL_DURATION_SECONDS from call start). The
|
|
3602
|
+
* returned shape always includes a model-readable `reason` so a
|
|
3603
|
+
* partial grant ("you asked for 5 min, you got 2 min") doesn't
|
|
3604
|
+
* confuse the agent.
|
|
3605
|
+
*
|
|
3606
|
+
* Failure modes (granted: 0):
|
|
3607
|
+
* - no extension policy on this call
|
|
3608
|
+
* - max requests already used
|
|
3609
|
+
* - max total seconds already used
|
|
3610
|
+
* - call already ended
|
|
3611
|
+
* - non-positive `seconds`
|
|
3612
|
+
*/
|
|
3613
|
+
extendCallTime(requestedSeconds: number, reason?: string): {
|
|
3614
|
+
granted: boolean;
|
|
3615
|
+
secondsGranted: number;
|
|
3616
|
+
secondsRemaining: number;
|
|
3617
|
+
extensionsRemaining: number;
|
|
3618
|
+
message: string;
|
|
3619
|
+
};
|
|
3620
|
+
/**
|
|
3621
|
+
* Capture the agent's `schedule_callback` request. The bridge VALIDATES
|
|
3622
|
+
* (policy allows it, delay is in the legal window, summary present),
|
|
3623
|
+
* builds a transcript digest from {@link recentUtterances}, then fires
|
|
3624
|
+
* {@link onCallbackScheduled} so the API layer can persist + arm the
|
|
3625
|
+
* scheduler. The bridge does NOT itself dial — that's the scheduler's
|
|
3626
|
+
* job, fired at the requested wall-clock time.
|
|
3627
|
+
*
|
|
3628
|
+
* Returning `{ accepted: true }` arms `isCallbackArmed`, which the
|
|
3629
|
+
* end-of-call path uses to skip the legacy operator-query callback
|
|
3630
|
+
* flag (the agent has already declared its own follow-up plan).
|
|
3631
|
+
*/
|
|
3632
|
+
scheduleCallback(req: {
|
|
3633
|
+
delaySeconds: number;
|
|
3634
|
+
reason?: string;
|
|
3635
|
+
summary: string;
|
|
3636
|
+
}): {
|
|
3637
|
+
accepted: boolean;
|
|
3638
|
+
at?: string;
|
|
3639
|
+
message: string;
|
|
3640
|
+
};
|
|
3641
|
+
/**
|
|
3642
|
+
* Public time-budget snapshot for `get_call_status`. Bundles
|
|
3643
|
+
* everything the agent needs to decide whether to keep going, ask
|
|
3644
|
+
* for more time, or schedule a callback.
|
|
3645
|
+
*/
|
|
3646
|
+
getCallStatus(): {
|
|
3647
|
+
secondsRemaining: number;
|
|
3648
|
+
softDeadlineAt: string | null;
|
|
3649
|
+
extension: ReturnType<RealtimeVoiceBridge['getExtensionStatus']>;
|
|
3650
|
+
callbackAvailable: boolean;
|
|
3651
|
+
callbackArmed: boolean;
|
|
3652
|
+
};
|
|
3653
|
+
/**
|
|
3654
|
+
* Arm the soft-deadline timer + reminder timers. Called once at hello.
|
|
3655
|
+
* No-op when the bridge has no budget configured.
|
|
3656
|
+
*/
|
|
3657
|
+
private startCallBudget;
|
|
3658
|
+
/**
|
|
3659
|
+
* Cancel all existing budget timers and re-arm them against
|
|
3660
|
+
* {@link softDeadlineMs}. Called at startCallBudget time AND after
|
|
3661
|
+
* every successful {@link extendCallTime} grant — the timers always
|
|
3662
|
+
* reflect the CURRENT deadline.
|
|
3663
|
+
*/
|
|
3664
|
+
private rearmBudgetTimers;
|
|
3665
|
+
/** Cancel all currently-armed budget timers. Idempotent. */
|
|
3666
|
+
private clearBudgetTimers;
|
|
3667
|
+
/**
|
|
3668
|
+
* Inject a "you have ~N seconds left" system message into the live
|
|
3669
|
+
* OpenAI session. The model receives it as a `conversation.item.create`
|
|
3670
|
+
* with role:`system`, followed by `response.create` so it can decide
|
|
3671
|
+
* whether to acknowledge it out loud (often it just naturally
|
|
3672
|
+
* accelerates wrap-up; we don't force a verbal "I have 30 seconds").
|
|
3673
|
+
*/
|
|
3674
|
+
private injectReminder;
|
|
3675
|
+
/**
|
|
3676
|
+
* Fires once the soft deadline elapses. Injects a "your time is up"
|
|
3677
|
+
* system message + schedules the grace-window hard end. If the agent
|
|
3678
|
+
* uses the grace window to call `schedule_callback` or `extend_call_time`
|
|
3679
|
+
* the latter can push the deadline forward again — that's fine, the
|
|
3680
|
+
* grace timer is cancelled by {@link extendCallTime} via rearmBudgetTimers.
|
|
3681
|
+
*/
|
|
3682
|
+
private onSoftDeadline;
|
|
3683
|
+
/**
|
|
3684
|
+
* Add an utterance to the rolling buffer used for the callback
|
|
3685
|
+
* transcript digest. Bounded by char count, not entry count, so a
|
|
3686
|
+
* burst of short turns doesn't get pruned prematurely.
|
|
3687
|
+
*/
|
|
3688
|
+
private noteUtterance;
|
|
3689
|
+
/**
|
|
3690
|
+
* Compose a transcript digest from the rolling buffer. Used as the
|
|
3691
|
+
* "context from the previous call" payload in {@link scheduleCallback}.
|
|
3692
|
+
* Always honours {@link MAX_CALLBACK_TRANSCRIPT_DIGEST_LENGTH}.
|
|
3693
|
+
*/
|
|
3694
|
+
private composeTranscriptDigest;
|
|
3695
|
+
/**
|
|
3696
|
+
* v0.9.82 — agent-initiated hangup. Called when the `end_call` tool
|
|
3697
|
+
* fires. Logs a marker, then routes through {@link end} so the
|
|
3698
|
+
* carrier sees the bye frame and `onEnd` fires exactly once (the
|
|
3699
|
+
* same teardown path the human-hangup case takes). The "agent-
|
|
3700
|
+
* requested" reason flows through to the mission transcript so a
|
|
3701
|
+
* post-call audit can tell apart "agent hung up" from "human hung
|
|
3702
|
+
* up" from "time budget exceeded".
|
|
3703
|
+
*
|
|
3704
|
+
* Returns the structured result the tool handler echoes back to the
|
|
3705
|
+
* model — even though by the time the model receives it the line
|
|
3706
|
+
* will already be closed, keeping a consistent return shape lets the
|
|
3707
|
+
* executor JSON-stringify deterministically.
|
|
3708
|
+
*/
|
|
3709
|
+
endByAgentRequest(reason?: string): {
|
|
3710
|
+
ok: boolean;
|
|
3711
|
+
message: string;
|
|
3712
|
+
};
|
|
3346
3713
|
/**
|
|
3347
3714
|
* End the bridge. Idempotent — the first call wins, later calls are
|
|
3348
3715
|
* no-ops. Sends the carrier's end-of-call frame (if it has one — 46elks
|
|
@@ -3354,108 +3721,6 @@ declare class RealtimeVoiceBridge {
|
|
|
3354
3721
|
private safeSend;
|
|
3355
3722
|
}
|
|
3356
3723
|
|
|
3357
|
-
declare const PHONE_REGION_SCOPES: readonly ["AT", "DE", "EU", "WORLD"];
|
|
3358
|
-
type PhoneRegionScope = typeof PHONE_REGION_SCOPES[number];
|
|
3359
|
-
declare const TELEPHONY_TRANSPORT_CAPABILITIES: readonly ["sms", "call_control", "realtime_media", "recording_supported"];
|
|
3360
|
-
type TelephonyTransportCapability = typeof TELEPHONY_TRANSPORT_CAPABILITIES[number];
|
|
3361
|
-
declare const PHONE_MISSION_STATES: readonly ["draft", "approved", "dialing", "connected", "conversing", "needs_operator", "completed", "failed", "cancelled"];
|
|
3362
|
-
type PhoneMissionState = typeof PHONE_MISSION_STATES[number];
|
|
3363
|
-
type PhoneNumberRisk = 'invalid' | 'standard' | 'premium_or_special';
|
|
3364
|
-
/**
|
|
3365
|
-
* Server-side hard ceilings for a phone mission policy.
|
|
3366
|
-
*
|
|
3367
|
-
* Hardening (#42-H1 / #43-H2) — `policy` is supplied by the calling agent,
|
|
3368
|
-
* so a caller-set `maxCallDurationSeconds: 999999` or `maxCostPerMission:
|
|
3369
|
-
* 1e9` is self-authorized and meaningless as a limit. These constants are
|
|
3370
|
-
* the real ceiling: `validatePhoneMissionPolicy` clamps every caller value
|
|
3371
|
-
* down to (at most) the server cap, so the effective policy can only ever
|
|
3372
|
-
* be MORE restrictive than the server, never less. A phone mission places
|
|
3373
|
-
* real, billed calls — these bounds are the financial blast-radius cap.
|
|
3374
|
-
*/
|
|
3375
|
-
declare const PHONE_SERVER_MAX_CALL_DURATION_SECONDS = 3600;
|
|
3376
|
-
declare const PHONE_SERVER_MAX_COST_PER_MISSION = 5;
|
|
3377
|
-
declare const PHONE_SERVER_MAX_ATTEMPTS = 3;
|
|
3378
|
-
/** Hard cap on the free-text `task` fed to the voice runtime. */
|
|
3379
|
-
declare const PHONE_TASK_MAX_LENGTH = 2000;
|
|
3380
|
-
interface PhoneConfirmPolicy {
|
|
3381
|
-
paymentDetails: 'never';
|
|
3382
|
-
contractCommitment: 'never';
|
|
3383
|
-
costOverLimit: 'needs_operator';
|
|
3384
|
-
sensitivePersonalData: 'needs_operator';
|
|
3385
|
-
unclearAlternative: 'needs_operator';
|
|
3386
|
-
}
|
|
3387
|
-
interface PhoneAlternativePolicy {
|
|
3388
|
-
maxTimeShiftMinutes: number;
|
|
3389
|
-
}
|
|
3390
|
-
interface OpenClawPhoneMissionPolicy {
|
|
3391
|
-
policyVersion: 1;
|
|
3392
|
-
regionAllowlist: PhoneRegionScope[];
|
|
3393
|
-
maxCallDurationSeconds: number;
|
|
3394
|
-
maxCostPerMission: number;
|
|
3395
|
-
maxAttempts: number;
|
|
3396
|
-
transcriptEnabled: boolean;
|
|
3397
|
-
recordingEnabled: boolean;
|
|
3398
|
-
confirmPolicy: PhoneConfirmPolicy;
|
|
3399
|
-
alternativePolicy: PhoneAlternativePolicy;
|
|
3400
|
-
}
|
|
3401
|
-
interface StartPhoneMissionInput {
|
|
3402
|
-
to: string;
|
|
3403
|
-
task: string;
|
|
3404
|
-
policy: OpenClawPhoneMissionPolicy;
|
|
3405
|
-
voiceRuntimeRef?: string;
|
|
3406
|
-
}
|
|
3407
|
-
interface PhoneTransportProfile {
|
|
3408
|
-
provider: string;
|
|
3409
|
-
phoneNumber: string;
|
|
3410
|
-
capabilities: TelephonyTransportCapability[];
|
|
3411
|
-
supportedRegions: PhoneRegionScope[];
|
|
3412
|
-
}
|
|
3413
|
-
interface PhoneMissionValidationIssue {
|
|
3414
|
-
code: string;
|
|
3415
|
-
field: string;
|
|
3416
|
-
message: string;
|
|
3417
|
-
}
|
|
3418
|
-
type PhoneMissionValidationResult = {
|
|
3419
|
-
ok: true;
|
|
3420
|
-
policy: OpenClawPhoneMissionPolicy;
|
|
3421
|
-
issues: [];
|
|
3422
|
-
} | {
|
|
3423
|
-
ok: false;
|
|
3424
|
-
issues: PhoneMissionValidationIssue[];
|
|
3425
|
-
};
|
|
3426
|
-
type PhoneTransportValidationResult = {
|
|
3427
|
-
ok: true;
|
|
3428
|
-
transport: PhoneTransportProfile;
|
|
3429
|
-
issues: [];
|
|
3430
|
-
} | {
|
|
3431
|
-
ok: false;
|
|
3432
|
-
issues: PhoneMissionValidationIssue[];
|
|
3433
|
-
};
|
|
3434
|
-
interface ValidatedPhoneMissionStart {
|
|
3435
|
-
to: string;
|
|
3436
|
-
task: string;
|
|
3437
|
-
policy: OpenClawPhoneMissionPolicy;
|
|
3438
|
-
targetRegion: PhoneRegionScope;
|
|
3439
|
-
transport: PhoneTransportProfile;
|
|
3440
|
-
voiceRuntimeRef?: string;
|
|
3441
|
-
}
|
|
3442
|
-
type PhoneMissionStartValidationResult = {
|
|
3443
|
-
ok: true;
|
|
3444
|
-
mission: ValidatedPhoneMissionStart;
|
|
3445
|
-
issues: [];
|
|
3446
|
-
} | {
|
|
3447
|
-
ok: false;
|
|
3448
|
-
issues: PhoneMissionValidationIssue[];
|
|
3449
|
-
};
|
|
3450
|
-
declare function validatePhoneMissionPolicy(policy: unknown): PhoneMissionValidationResult;
|
|
3451
|
-
declare function validatePhoneTransportProfile(transport: unknown): PhoneTransportValidationResult;
|
|
3452
|
-
declare function inferPhoneRegion(phoneNumber: string): PhoneRegionScope | null;
|
|
3453
|
-
declare function isPhoneRegionAllowed(region: PhoneRegionScope, allowlist: readonly PhoneRegionScope[]): boolean;
|
|
3454
|
-
declare function classifyPhoneNumberRisk(phoneNumber: string): PhoneNumberRisk;
|
|
3455
|
-
declare function validatePhoneMissionStart(input: unknown, transport: unknown, options?: {
|
|
3456
|
-
allowPremiumOrSpecialNumbers?: boolean;
|
|
3457
|
-
}): PhoneMissionStartValidationResult;
|
|
3458
|
-
|
|
3459
3724
|
type PhoneTransportProvider = '46elks' | 'twilio';
|
|
3460
3725
|
/** Providers that support starting outbound call-control missions. */
|
|
3461
3726
|
declare const PHONE_CALL_CONTROL_PROVIDERS: readonly PhoneTransportProvider[];
|
|
@@ -3733,6 +3998,54 @@ declare class PhoneManager {
|
|
|
3733
3998
|
mission: PhoneCallMission;
|
|
3734
3999
|
callbackMission: PhoneCallMission;
|
|
3735
4000
|
} | null>;
|
|
4001
|
+
/**
|
|
4002
|
+
* Persist a `schedule_callback` request to the mission. Called from
|
|
4003
|
+
* the realtime bridge's `onCallbackScheduled` hook. The scheduler
|
|
4004
|
+
* picks this up later when `payload.at <= now`. ChainDepth is
|
|
4005
|
+
* computed from the parent's metadata so {@link triggerScheduledCallback}
|
|
4006
|
+
* can enforce policy.callbackPolicy.maxCallbackChain without
|
|
4007
|
+
* walking back through the mission history.
|
|
4008
|
+
*
|
|
4009
|
+
* Returns the updated mission, or null if the mission isn't known.
|
|
4010
|
+
* Idempotent on the `mission.metadata.scheduledCallback.at` key: if
|
|
4011
|
+
* a scheduled callback already exists on the mission this writes a
|
|
4012
|
+
* SECOND copy on `scheduledCallbacks` as an audit trail but does
|
|
4013
|
+
* NOT overwrite the active record (the bridge only allows one per
|
|
4014
|
+
* call anyway; the audit log is a belt-and-braces guard against
|
|
4015
|
+
* the unusual case where a server restart re-runs the bridge logic).
|
|
4016
|
+
*/
|
|
4017
|
+
armScheduledCallback(missionId: string, payload: {
|
|
4018
|
+
at: string;
|
|
4019
|
+
reason: string;
|
|
4020
|
+
agentSummary: string;
|
|
4021
|
+
transcriptDigest: string;
|
|
4022
|
+
}): PhoneCallMission | null;
|
|
4023
|
+
/**
|
|
4024
|
+
* All missions with a `scheduledCallback.status === 'pending'` whose
|
|
4025
|
+
* `at` is <= now. The scheduler's per-tick worklist. Pass an upper
|
|
4026
|
+
* bound on count so a backlog doesn't dial every overdue callback in
|
|
4027
|
+
* one frame.
|
|
4028
|
+
*/
|
|
4029
|
+
findDueScheduledCallbacks(nowIso: string, limit?: number): PhoneCallMission[];
|
|
4030
|
+
/**
|
|
4031
|
+
* Dial a due scheduled callback. Mirrors {@link triggerCallback} for
|
|
4032
|
+
* the operator-query path:
|
|
4033
|
+
*
|
|
4034
|
+
* 1. Reject if the mission's policy.callbackPolicy disallows it OR
|
|
4035
|
+
* `chainDepth > maxCallbackChain` (no infinite chains).
|
|
4036
|
+
* 2. Transition status pending → dialing BEFORE dialing so a
|
|
4037
|
+
* concurrent tick can't double-dial.
|
|
4038
|
+
* 3. Build the continuation task with prior-call context and dial.
|
|
4039
|
+
* 4. On success: write `status: 'fired'` + the new mission id.
|
|
4040
|
+
* 5. On failure: write `status: 'pending'` + `lastError` so the
|
|
4041
|
+
* next tick can retry, then rethrow.
|
|
4042
|
+
*
|
|
4043
|
+
* Returns `null` if the mission isn't known or has no due callback.
|
|
4044
|
+
*/
|
|
4045
|
+
triggerScheduledCallback(missionId: string, options?: StartPhoneCallOptions): Promise<{
|
|
4046
|
+
mission: PhoneCallMission;
|
|
4047
|
+
callbackMission: PhoneCallMission;
|
|
4048
|
+
} | null>;
|
|
3736
4049
|
private build46ElksCallRequest;
|
|
3737
4050
|
/**
|
|
3738
4051
|
* Build the Twilio outbound-call request — the mirror of
|