@komaa/standin-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/ambient.d.ts +80 -0
- package/dist/ambient.d.ts.map +1 -0
- package/dist/ambient.js +281 -0
- package/dist/ambient.js.map +1 -0
- package/dist/attachments.d.ts +149 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +451 -0
- package/dist/attachments.js.map +1 -0
- package/dist/audio.d.ts +80 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/audio.js +170 -0
- package/dist/audio.js.map +1 -0
- package/dist/avatar.d.ts +111 -0
- package/dist/avatar.d.ts.map +1 -0
- package/dist/avatar.js +201 -0
- package/dist/avatar.js.map +1 -0
- package/dist/callServer.d.ts +151 -0
- package/dist/callServer.d.ts.map +1 -0
- package/dist/callServer.js +1097 -0
- package/dist/callServer.js.map +1 -0
- package/dist/callTools.d.ts +154 -0
- package/dist/callTools.d.ts.map +1 -0
- package/dist/callTools.js +255 -0
- package/dist/callTools.js.map +1 -0
- package/dist/chat.d.ts +226 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +574 -0
- package/dist/chat.js.map +1 -0
- package/dist/config.d.ts +32 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +86 -0
- package/dist/config.js.map +1 -0
- package/dist/consult.d.ts +147 -0
- package/dist/consult.d.ts.map +1 -0
- package/dist/consult.js +388 -0
- package/dist/consult.js.map +1 -0
- package/dist/delivery.d.ts +81 -0
- package/dist/delivery.d.ts.map +1 -0
- package/dist/delivery.js +187 -0
- package/dist/delivery.js.map +1 -0
- package/dist/echoGuard.d.ts +56 -0
- package/dist/echoGuard.d.ts.map +1 -0
- package/dist/echoGuard.js +57 -0
- package/dist/echoGuard.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +10 -0
- package/dist/errors.js.map +1 -0
- package/dist/fetch.d.ts +40 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +240 -0
- package/dist/fetch.js.map +1 -0
- package/dist/gate.d.ts +80 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +249 -0
- package/dist/gate.js.map +1 -0
- package/dist/handler.d.ts +283 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +4 -0
- package/dist/handler.js.map +1 -0
- package/dist/hmac.d.ts +32 -0
- package/dist/hmac.d.ts.map +1 -0
- package/dist/hmac.js +108 -0
- package/dist/hmac.js.map +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/lane.d.ts +92 -0
- package/dist/lane.d.ts.map +1 -0
- package/dist/lane.js +269 -0
- package/dist/lane.js.map +1 -0
- package/dist/lipsync.d.ts +136 -0
- package/dist/lipsync.d.ts.map +1 -0
- package/dist/lipsync.js +310 -0
- package/dist/lipsync.js.map +1 -0
- package/dist/log.d.ts +18 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +20 -0
- package/dist/log.js.map +1 -0
- package/dist/media.d.ts +43 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +190 -0
- package/dist/media.js.map +1 -0
- package/dist/minutes.d.ts +398 -0
- package/dist/minutes.d.ts.map +1 -0
- package/dist/minutes.js +805 -0
- package/dist/minutes.js.map +1 -0
- package/dist/outbound.d.ts +388 -0
- package/dist/outbound.d.ts.map +1 -0
- package/dist/outbound.js +933 -0
- package/dist/outbound.js.map +1 -0
- package/dist/plugins/cartesia/agent.d.ts +58 -0
- package/dist/plugins/cartesia/agent.d.ts.map +1 -0
- package/dist/plugins/cartesia/agent.js +254 -0
- package/dist/plugins/cartesia/agent.js.map +1 -0
- package/dist/plugins/cartesia/cli.d.ts +3 -0
- package/dist/plugins/cartesia/cli.d.ts.map +1 -0
- package/dist/plugins/cartesia/cli.js +10 -0
- package/dist/plugins/cartesia/cli.js.map +1 -0
- package/dist/plugins/cartesia/config.d.ts +30 -0
- package/dist/plugins/cartesia/config.d.ts.map +1 -0
- package/dist/plugins/cartesia/config.js +29 -0
- package/dist/plugins/cartesia/config.js.map +1 -0
- package/dist/plugins/cartesia/handler.d.ts +40 -0
- package/dist/plugins/cartesia/handler.d.ts.map +1 -0
- package/dist/plugins/cartesia/handler.js +142 -0
- package/dist/plugins/cartesia/handler.js.map +1 -0
- package/dist/plugins/cartesia/index.d.ts +6 -0
- package/dist/plugins/cartesia/index.d.ts.map +1 -0
- package/dist/plugins/cartesia/index.js +54 -0
- package/dist/plugins/cartesia/index.js.map +1 -0
- package/dist/plugins/deepgram/agent.d.ts +60 -0
- package/dist/plugins/deepgram/agent.d.ts.map +1 -0
- package/dist/plugins/deepgram/agent.js +302 -0
- package/dist/plugins/deepgram/agent.js.map +1 -0
- package/dist/plugins/deepgram/cli.d.ts +3 -0
- package/dist/plugins/deepgram/cli.d.ts.map +1 -0
- package/dist/plugins/deepgram/cli.js +10 -0
- package/dist/plugins/deepgram/cli.js.map +1 -0
- package/dist/plugins/deepgram/config.d.ts +39 -0
- package/dist/plugins/deepgram/config.d.ts.map +1 -0
- package/dist/plugins/deepgram/config.js +66 -0
- package/dist/plugins/deepgram/config.js.map +1 -0
- package/dist/plugins/deepgram/handler.d.ts +87 -0
- package/dist/plugins/deepgram/handler.d.ts.map +1 -0
- package/dist/plugins/deepgram/handler.js +268 -0
- package/dist/plugins/deepgram/handler.js.map +1 -0
- package/dist/plugins/deepgram/index.d.ts +11 -0
- package/dist/plugins/deepgram/index.d.ts.map +1 -0
- package/dist/plugins/deepgram/index.js +76 -0
- package/dist/plugins/deepgram/index.js.map +1 -0
- package/dist/plugins/echo/cli.d.ts +3 -0
- package/dist/plugins/echo/cli.d.ts.map +1 -0
- package/dist/plugins/echo/cli.js +10 -0
- package/dist/plugins/echo/cli.js.map +1 -0
- package/dist/plugins/echo/index.d.ts +37 -0
- package/dist/plugins/echo/index.d.ts.map +1 -0
- package/dist/plugins/echo/index.js +61 -0
- package/dist/plugins/echo/index.js.map +1 -0
- package/dist/plugins/elevenlabs/agent.d.ts +72 -0
- package/dist/plugins/elevenlabs/agent.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/agent.js +305 -0
- package/dist/plugins/elevenlabs/agent.js.map +1 -0
- package/dist/plugins/elevenlabs/cli.d.ts +3 -0
- package/dist/plugins/elevenlabs/cli.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/cli.js +10 -0
- package/dist/plugins/elevenlabs/cli.js.map +1 -0
- package/dist/plugins/elevenlabs/config.d.ts +27 -0
- package/dist/plugins/elevenlabs/config.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/config.js +42 -0
- package/dist/plugins/elevenlabs/config.js.map +1 -0
- package/dist/plugins/elevenlabs/handler.d.ts +67 -0
- package/dist/plugins/elevenlabs/handler.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/handler.js +419 -0
- package/dist/plugins/elevenlabs/handler.js.map +1 -0
- package/dist/plugins/elevenlabs/index.d.ts +11 -0
- package/dist/plugins/elevenlabs/index.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/index.js +72 -0
- package/dist/plugins/elevenlabs/index.js.map +1 -0
- package/dist/plugins/livekit/cli.d.ts +3 -0
- package/dist/plugins/livekit/cli.d.ts.map +1 -0
- package/dist/plugins/livekit/cli.js +10 -0
- package/dist/plugins/livekit/cli.js.map +1 -0
- package/dist/plugins/livekit/config.d.ts +47 -0
- package/dist/plugins/livekit/config.d.ts.map +1 -0
- package/dist/plugins/livekit/config.js +46 -0
- package/dist/plugins/livekit/config.js.map +1 -0
- package/dist/plugins/livekit/handler.d.ts +59 -0
- package/dist/plugins/livekit/handler.d.ts.map +1 -0
- package/dist/plugins/livekit/handler.js +211 -0
- package/dist/plugins/livekit/handler.js.map +1 -0
- package/dist/plugins/livekit/index.d.ts +6 -0
- package/dist/plugins/livekit/index.d.ts.map +1 -0
- package/dist/plugins/livekit/index.js +68 -0
- package/dist/plugins/livekit/index.js.map +1 -0
- package/dist/plugins/livekit/room.d.ts +117 -0
- package/dist/plugins/livekit/room.d.ts.map +1 -0
- package/dist/plugins/livekit/room.js +449 -0
- package/dist/plugins/livekit/room.js.map +1 -0
- package/dist/plugins/openai/agent.d.ts +74 -0
- package/dist/plugins/openai/agent.d.ts.map +1 -0
- package/dist/plugins/openai/agent.js +264 -0
- package/dist/plugins/openai/agent.js.map +1 -0
- package/dist/plugins/openai/cli.d.ts +3 -0
- package/dist/plugins/openai/cli.d.ts.map +1 -0
- package/dist/plugins/openai/cli.js +10 -0
- package/dist/plugins/openai/cli.js.map +1 -0
- package/dist/plugins/openai/config.d.ts +29 -0
- package/dist/plugins/openai/config.d.ts.map +1 -0
- package/dist/plugins/openai/config.js +37 -0
- package/dist/plugins/openai/config.js.map +1 -0
- package/dist/plugins/openai/handler.d.ts +65 -0
- package/dist/plugins/openai/handler.d.ts.map +1 -0
- package/dist/plugins/openai/handler.js +297 -0
- package/dist/plugins/openai/handler.js.map +1 -0
- package/dist/plugins/openai/index.d.ts +6 -0
- package/dist/plugins/openai/index.d.ts.map +1 -0
- package/dist/plugins/openai/index.js +72 -0
- package/dist/plugins/openai/index.js.map +1 -0
- package/dist/plugins/openclaw/allowlist.d.ts +9 -0
- package/dist/plugins/openclaw/allowlist.d.ts.map +1 -0
- package/dist/plugins/openclaw/allowlist.js +11 -0
- package/dist/plugins/openclaw/allowlist.js.map +1 -0
- package/dist/plugins/openclaw/config.d.ts +50 -0
- package/dist/plugins/openclaw/config.d.ts.map +1 -0
- package/dist/plugins/openclaw/config.js +53 -0
- package/dist/plugins/openclaw/config.js.map +1 -0
- package/dist/plugins/openclaw/handler.d.ts +90 -0
- package/dist/plugins/openclaw/handler.d.ts.map +1 -0
- package/dist/plugins/openclaw/handler.js +146 -0
- package/dist/plugins/openclaw/handler.js.map +1 -0
- package/dist/plugins/openclaw/index.d.ts +55 -0
- package/dist/plugins/openclaw/index.d.ts.map +1 -0
- package/dist/plugins/openclaw/index.js +89 -0
- package/dist/plugins/openclaw/index.js.map +1 -0
- package/dist/plugins/openclaw/openclaw.plugin.json +74 -0
- package/dist/plugins/openclaw/package.json +14 -0
- package/dist/plugins/openclaw/realtime.d.ts +82 -0
- package/dist/plugins/openclaw/realtime.d.ts.map +1 -0
- package/dist/plugins/openclaw/realtime.js +238 -0
- package/dist/plugins/openclaw/realtime.js.map +1 -0
- package/dist/plugins/openclaw/runtime.d.ts +19 -0
- package/dist/plugins/openclaw/runtime.d.ts.map +1 -0
- package/dist/plugins/openclaw/runtime.js +160 -0
- package/dist/plugins/openclaw/runtime.js.map +1 -0
- package/dist/policy.d.ts +38 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +95 -0
- package/dist/policy.js.map +1 -0
- package/dist/protocol.d.ts +44 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +77 -0
- package/dist/protocol.js.map +1 -0
- package/dist/protocolRuntime.d.ts +19 -0
- package/dist/protocolRuntime.d.ts.map +1 -0
- package/dist/protocolRuntime.js +78 -0
- package/dist/protocolRuntime.js.map +1 -0
- package/dist/smoke.d.ts +65 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/smoke.js +293 -0
- package/dist/smoke.js.map +1 -0
- package/dist/startup.d.ts +74 -0
- package/dist/startup.d.ts.map +1 -0
- package/dist/startup.js +112 -0
- package/dist/startup.js.map +1 -0
- package/dist/tile.d.ts +101 -0
- package/dist/tile.d.ts.map +1 -0
- package/dist/tile.js +195 -0
- package/dist/tile.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/dist/vision.d.ts +160 -0
- package/dist/vision.d.ts.map +1 -0
- package/dist/vision.js +301 -0
- package/dist/vision.js.map +1 -0
- package/dist/visionTools.d.ts +237 -0
- package/dist/visionTools.d.ts.map +1 -0
- package/dist/visionTools.js +578 -0
- package/dist/visionTools.js.map +1 -0
- package/dist/voice.d.ts +143 -0
- package/dist/voice.d.ts.map +1 -0
- package/dist/voice.js +403 -0
- package/dist/voice.js.map +1 -0
- package/package.json +129 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// Copyright (c) 2026 Komaa DigiTech
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* One Microsoft Teams call, bridged to OpenClaw's realtime speech-to-speech session.
|
|
5
|
+
*
|
|
6
|
+
* This file is the reason the plugin cannot be a standalone worker. It does not
|
|
7
|
+
* drive a model: it CONSUMES the host's realtime session
|
|
8
|
+
* (`createRealtimeVoiceBridgeSession`), the host's provider registry and the
|
|
9
|
+
* host's config, all of which are in-process objects inside the OpenClaw gateway.
|
|
10
|
+
* Nothing here opens a provider socket, and nothing here knows which vendor is on
|
|
11
|
+
* the other end.
|
|
12
|
+
*
|
|
13
|
+
* What it does own is the audio boundary between two rates and two framings:
|
|
14
|
+
*
|
|
15
|
+
* caller 16 kHz --> 24 kHz --> host realtime session
|
|
16
|
+
* model 24 kHz --> 16 kHz --> session.sendAudio
|
|
17
|
+
*
|
|
18
|
+
* and the three things that make a spoken call feel right rather than merely
|
|
19
|
+
* work: the echo guard, the playout clock the guard keys on, and barge-in.
|
|
20
|
+
*
|
|
21
|
+
* Everything the SDK owns is deliberately absent here: the socket, the sequence
|
|
22
|
+
* number, the outbound timeline, the frame loop and the watchdogs.
|
|
23
|
+
*/
|
|
24
|
+
import { FrameAligner, REALTIME_SAMPLE_RATE_HZ, SAMPLE_RATE_HZ, frameDurationMs, resamplePcm16, } from "../../index.js";
|
|
25
|
+
import { REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ, createRealtimeVoiceBridgeSession, } from "openclaw/plugin-sdk/realtime-voice";
|
|
26
|
+
import { shouldSuppressEcho } from "../../echoGuard.js";
|
|
27
|
+
import { isVerbalInterrupt } from "../../gate.js";
|
|
28
|
+
/**
|
|
29
|
+
* Build the bridge for one call.
|
|
30
|
+
*
|
|
31
|
+
* `session` is the SDK's {@link CallSession}: the only route to the caller, and
|
|
32
|
+
* the owner of the sequence number and the outbound timeline, so nothing here
|
|
33
|
+
* tracks either.
|
|
34
|
+
*/
|
|
35
|
+
export function createRealtimeCall(params) {
|
|
36
|
+
const { session, deps } = params;
|
|
37
|
+
const { logger } = deps;
|
|
38
|
+
const callId = session.callId;
|
|
39
|
+
let closed = false;
|
|
40
|
+
/**
|
|
41
|
+
* Estimated epoch ms at which the audio already sent finishes PLAYING.
|
|
42
|
+
*
|
|
43
|
+
* The model generates faster than realtime and the service queues what we send,
|
|
44
|
+
* so send-time is not play-time. Summing each chunk's own duration tracks the
|
|
45
|
+
* playout clock instead, which is what the echo guard has to be keyed on: a
|
|
46
|
+
* guard armed off last-send disarms in the middle of the sentence it exists to
|
|
47
|
+
* cover.
|
|
48
|
+
*/
|
|
49
|
+
let playbackEndAt = 0;
|
|
50
|
+
/**
|
|
51
|
+
* False until the caller has genuinely spoken once. See
|
|
52
|
+
* {@link EchoGuardOptions.allowBargeIn}: the opening greeting echoing off a
|
|
53
|
+
* speakerphone will clear the barge-in RMS, so the loudness exception has to
|
|
54
|
+
* stay off until there is a real caller turn to measure against.
|
|
55
|
+
*/
|
|
56
|
+
let callerTurnStarted = false;
|
|
57
|
+
/**
|
|
58
|
+
* Microsoft Teams recording status, seeded from `session.start` and updated by context.
|
|
59
|
+
* Gates every media-derived path (audio in, DTMF, transcripts) when
|
|
60
|
+
* `requireRecordingStatus` is on.
|
|
61
|
+
*/
|
|
62
|
+
let recordingActive = session.start.recordingStatus === "active";
|
|
63
|
+
const recordingGateBlocks = () => deps.requireRecordingStatus && !recordingActive;
|
|
64
|
+
/**
|
|
65
|
+
* Whole-frame alignment for the downlink.
|
|
66
|
+
*
|
|
67
|
+
* A 24 kHz delta resampled to 16 kHz does not divide evenly into the wire's
|
|
68
|
+
* 640-byte frame, and dropping the remainder clips a few milliseconds off every
|
|
69
|
+
* chunk seam - audible over a call as clipped word endings. The SDK owns this;
|
|
70
|
+
* do not hand-roll it.
|
|
71
|
+
*/
|
|
72
|
+
const aligner = new FrameAligner();
|
|
73
|
+
/**
|
|
74
|
+
* The host's audio sink is SYNCHRONOUS and `session.sendAudio` is not, so the
|
|
75
|
+
* sends are chained rather than awaited. A chain, not fire-and-forget: two
|
|
76
|
+
* unordered sends would put the model's voice out of order on the wire, which
|
|
77
|
+
* is worse than late audio. The catch is inside the chain so one failed send
|
|
78
|
+
* cannot poison every send after it.
|
|
79
|
+
*/
|
|
80
|
+
let sendChain = Promise.resolve();
|
|
81
|
+
const enqueue = (op) => {
|
|
82
|
+
sendChain = sendChain.then(op).catch((err) => {
|
|
83
|
+
logger?.debug?.(`standin-msteams: send failed on ${callId} - ${err instanceof Error ? err.message : String(err)}`);
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
/** Flush the held-back tail of a finished turn, zero-padded to a whole frame. */
|
|
87
|
+
const flushTail = () => {
|
|
88
|
+
const tail = aligner.flush();
|
|
89
|
+
if (tail)
|
|
90
|
+
enqueue(() => session.sendAudio(tail));
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Barge-in. THE ORDER HERE IS THE WHOLE POINT.
|
|
94
|
+
*
|
|
95
|
+
* `cancelPlayback` is the only lever that un-sends audio the service has already
|
|
96
|
+
* buffered, so it goes FIRST: cancel the model first and the caller still hears
|
|
97
|
+
* the rest of the interrupted turn play out, which is exactly the "it kept
|
|
98
|
+
* talking over me" complaint barge-in exists to fix. The aligner's residual
|
|
99
|
+
* belongs to that same dead turn, so it is dropped rather than flushed.
|
|
100
|
+
*/
|
|
101
|
+
const flushPlayback = () => {
|
|
102
|
+
aligner.reset();
|
|
103
|
+
enqueue(() => session.cancelPlayback());
|
|
104
|
+
// The flush stops playout immediately, so the playout estimate collapses to now.
|
|
105
|
+
playbackEndAt = Date.now();
|
|
106
|
+
};
|
|
107
|
+
const realtime = createRealtimeVoiceBridgeSession({
|
|
108
|
+
provider: deps.provider,
|
|
109
|
+
providerConfig: deps.providerConfig,
|
|
110
|
+
cfg: deps.cfg,
|
|
111
|
+
audioFormat: REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ,
|
|
112
|
+
instructions: deps.instructions,
|
|
113
|
+
initialGreetingInstructions: deps.greetingInstructions,
|
|
114
|
+
triggerGreetingOnReady: Boolean(deps.greetingInstructions),
|
|
115
|
+
autoRespondToAudio: true,
|
|
116
|
+
interruptResponseOnInputAudio: true,
|
|
117
|
+
audioSink: {
|
|
118
|
+
isOpen: () => !closed,
|
|
119
|
+
sendAudio: (pcm24k) => {
|
|
120
|
+
if (closed || pcm24k.length === 0) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const pcm16k = resamplePcm16(pcm24k, REALTIME_SAMPLE_RATE_HZ, SAMPLE_RATE_HZ);
|
|
124
|
+
playbackEndAt =
|
|
125
|
+
Math.max(playbackEndAt, Date.now()) + frameDurationMs(pcm16k);
|
|
126
|
+
for (const frame of aligner.push(pcm16k)) {
|
|
127
|
+
enqueue(() => session.sendAudio(frame));
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
// CANCEL SITE 1: the model truncated its own turn because it heard the
|
|
131
|
+
// caller start speaking. Upstream is already cancelled - the model is the
|
|
132
|
+
// one telling us - so the only thing left to undo is the audio the service
|
|
133
|
+
// still has queued.
|
|
134
|
+
clearAudio: () => {
|
|
135
|
+
flushPlayback();
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
onTranscript: (role, text, isFinal) => {
|
|
139
|
+
// The caller's first real speech ends the opening echo-only window and
|
|
140
|
+
// restores normal RMS barge-in for the rest of the call.
|
|
141
|
+
if (role === "user" && text.trim().length > 0) {
|
|
142
|
+
callerTurnStarted = true;
|
|
143
|
+
}
|
|
144
|
+
if (role === "assistant" && isFinal) {
|
|
145
|
+
// End of turn: the residual is real audio, not a fragment to discard.
|
|
146
|
+
flushTail();
|
|
147
|
+
}
|
|
148
|
+
if (role !== "user" || !isFinal) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// CANCEL SITE 2: a deterministic verbal interrupt ("stop", "hold on").
|
|
152
|
+
// Handled here rather than left to the model because the model is
|
|
153
|
+
// mid-generation when it arrives; matching the phrase ourselves is what
|
|
154
|
+
// makes the cut feel instant. Only while we are actually still speaking -
|
|
155
|
+
// otherwise "stop" is just a word in a sentence.
|
|
156
|
+
if (Date.now() < playbackEndAt && isVerbalInterrupt(text)) {
|
|
157
|
+
logger?.debug?.(`standin-msteams: verbal interrupt on ${callId} - flushing playback`);
|
|
158
|
+
flushPlayback();
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
onError: (error) => {
|
|
162
|
+
logger?.warn?.(`standin-msteams: realtime session error on ${callId} - ${error.message}`);
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
connect: () => realtime.connect(),
|
|
167
|
+
pushAudio: (pcm16k) => {
|
|
168
|
+
if (closed || pcm16k.length === 0) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// Recording gate: caller audio is call media, and the Microsoft Media Access
|
|
172
|
+
// API says a bot does not process it before recording status is active. A
|
|
173
|
+
// no-op when requireRecordingStatus is off.
|
|
174
|
+
if (recordingGateBlocks()) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (shouldSuppressEcho(pcm16k, playbackEndAt, {
|
|
178
|
+
...deps.echo,
|
|
179
|
+
allowBargeIn: callerTurnStarted,
|
|
180
|
+
})) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
realtime.sendAudio(resamplePcm16(pcm16k, SAMPLE_RATE_HZ, REALTIME_SAMPLE_RATE_HZ));
|
|
184
|
+
},
|
|
185
|
+
pushContext: (text) => {
|
|
186
|
+
if (closed || !text.trim()) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// DTMF and participant counts are media-derived too, so they sit behind the
|
|
190
|
+
// same gate as audio. Recording-status context itself is handled by the
|
|
191
|
+
// handler before it reaches here - that one must never be gated on itself.
|
|
192
|
+
if (recordingGateBlocks()) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
realtime.sendUserMessage(text);
|
|
196
|
+
},
|
|
197
|
+
setRecordingActive: (active) => {
|
|
198
|
+
recordingActive = active;
|
|
199
|
+
},
|
|
200
|
+
interrupt: () => {
|
|
201
|
+
if (closed)
|
|
202
|
+
return;
|
|
203
|
+
try {
|
|
204
|
+
realtime.handleBargeIn();
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
// Non-fatal: handleBargeIn throws when nothing is speaking, which is a
|
|
208
|
+
// perfectly ordinary state to interrupt from.
|
|
209
|
+
logger?.debug?.(`standin-msteams: barge-in on ${callId} - ${err instanceof Error ? err.message : String(err)}`);
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
say: (text) => {
|
|
213
|
+
if (closed || !text.trim())
|
|
214
|
+
return;
|
|
215
|
+
try {
|
|
216
|
+
// Same path as the greeting: inject the line as an instruction and trigger
|
|
217
|
+
// a spoken response. There is no separate "speak this" surface.
|
|
218
|
+
realtime.triggerGreeting(text);
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
logger?.warn?.(`standin-msteams: say failed on ${callId} - ${err instanceof Error ? err.message : String(err)}`);
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
close: () => {
|
|
225
|
+
if (closed)
|
|
226
|
+
return;
|
|
227
|
+
closed = true;
|
|
228
|
+
aligner.reset();
|
|
229
|
+
try {
|
|
230
|
+
realtime.close();
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
logger?.debug?.(`standin-msteams: realtime close on ${callId} - ${err instanceof Error ? err.message : String(err)}`);
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=realtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../../../src/plugins/openclaw/realtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
2
|
+
import type { ResolvedPluginConfig } from "./config.js";
|
|
3
|
+
/** The service OpenClaw starts and stops. One per gateway process. */
|
|
4
|
+
export declare class StandInCallRuntime {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(api: OpenClawPluginApi, config: ResolvedPluginConfig);
|
|
7
|
+
/** Live calls right now. Useful to a health check, and to a test. */
|
|
8
|
+
get activeCalls(): number;
|
|
9
|
+
start(): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Close live calls first, then the listener.
|
|
12
|
+
*
|
|
13
|
+
* That order is not cosmetic: the SDK's teardown frees a call's slot only once
|
|
14
|
+
* its close settles, so tearing the listener down over live calls is how a
|
|
15
|
+
* shutdown ends up waiting on sockets nobody is reading any more.
|
|
16
|
+
*/
|
|
17
|
+
stop(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/plugins/openclaw/runtime.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAGlE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAkDxD,sEAAsE;AACtE,qBAAa,kBAAkB;;gBAOjB,GAAG,EAAE,iBAAiB,EAAE,MAAM,EAAE,oBAAoB;IAMhE,qEAAqE;IACrE,IAAI,WAAW,IAAI,MAAM,CAExB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA6C5B;;;;;;OAMG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAmC5B"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// Copyright (c) 2026 Komaa DigiTech
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* The plugin's background service: one call listener, shared by every call.
|
|
5
|
+
*
|
|
6
|
+
* OpenClaw starts this on boot and stops it on shutdown or reload, so it is the
|
|
7
|
+
* teardown hook. It is short because the WebSocket server, the handshake, the
|
|
8
|
+
* replay guard, the frame loop and the watchdogs all belong to the SDK's
|
|
9
|
+
* `CallServer`. What is left is the two things the SDK cannot know: which
|
|
10
|
+
* realtime provider the host resolved, and how many calls this operator agreed
|
|
11
|
+
* to take at once.
|
|
12
|
+
*
|
|
13
|
+
* Two frictions with the SDK's defaults are handled here on purpose, both by
|
|
14
|
+
* passing the value explicitly rather than letting the default win:
|
|
15
|
+
*
|
|
16
|
+
* - `CallServer` reads `STANDIN_SECRET` from the environment. The secret arrives
|
|
17
|
+
* through `api.pluginConfig`, having been resolved by the host from a secret
|
|
18
|
+
* reference, so it is passed in. An env var would silently override a portal
|
|
19
|
+
* value the operator can actually see.
|
|
20
|
+
* - `CallServer` binds 0.0.0.0, which suits a worker behind an ingress but not a
|
|
21
|
+
* gateway reached through a local tunnel. Neither is wrong, so `bindAddress` is
|
|
22
|
+
* passed when the operator set it and the SDK's default stands when they did not.
|
|
23
|
+
*/
|
|
24
|
+
import { CallServer } from "../../index.js";
|
|
25
|
+
import { resolveConfiguredRealtimeVoiceProvider } from "openclaw/plugin-sdk/realtime-voice";
|
|
26
|
+
import { TeamsCallHandler, } from "./handler.js";
|
|
27
|
+
/**
|
|
28
|
+
* Live calls, and the operator's concurrency cap.
|
|
29
|
+
*
|
|
30
|
+
* A slot is reserved BEFORE the model session is built, so two calls arriving
|
|
31
|
+
* together cannot both pass the check and then both open a provider socket. A
|
|
32
|
+
* reserved-but-unbound entry is a call still in `onStart`.
|
|
33
|
+
*/
|
|
34
|
+
class LiveCalls {
|
|
35
|
+
#max;
|
|
36
|
+
#calls = new Map();
|
|
37
|
+
constructor(max) {
|
|
38
|
+
this.#max = max;
|
|
39
|
+
}
|
|
40
|
+
get size() {
|
|
41
|
+
return this.#calls.size;
|
|
42
|
+
}
|
|
43
|
+
acquire(callId) {
|
|
44
|
+
if (this.#calls.size >= this.#max)
|
|
45
|
+
return false;
|
|
46
|
+
this.#calls.set(callId, undefined);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
bind(callId, call) {
|
|
50
|
+
// Only if the slot is still ours: a teardown that raced onStart has already
|
|
51
|
+
// released it, and re-adding here would leak the slot for the worker's life.
|
|
52
|
+
if (this.#calls.has(callId))
|
|
53
|
+
this.#calls.set(callId, call);
|
|
54
|
+
}
|
|
55
|
+
release(callId) {
|
|
56
|
+
this.#calls.delete(callId);
|
|
57
|
+
}
|
|
58
|
+
/** Close every live call. Used by shutdown, before the listener goes away. */
|
|
59
|
+
closeAll() {
|
|
60
|
+
for (const call of this.#calls.values())
|
|
61
|
+
call?.close();
|
|
62
|
+
this.#calls.clear();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** The service OpenClaw starts and stops. One per gateway process. */
|
|
66
|
+
export class StandInCallRuntime {
|
|
67
|
+
#api;
|
|
68
|
+
#config;
|
|
69
|
+
#calls;
|
|
70
|
+
#server;
|
|
71
|
+
#realtime;
|
|
72
|
+
constructor(api, config) {
|
|
73
|
+
this.#api = api;
|
|
74
|
+
this.#config = config;
|
|
75
|
+
this.#calls = new LiveCalls(config.media.maxConcurrentCalls);
|
|
76
|
+
}
|
|
77
|
+
/** Live calls right now. Useful to a health check, and to a test. */
|
|
78
|
+
get activeCalls() {
|
|
79
|
+
return this.#calls.size;
|
|
80
|
+
}
|
|
81
|
+
async start() {
|
|
82
|
+
const log = this.#api.logger;
|
|
83
|
+
this.#realtime = this.#resolveRealtime();
|
|
84
|
+
// Fail LOUD, not closed. A gateway that boots healthy and then refuses every
|
|
85
|
+
// call with "realtime-unavailable" is the single most expensive way to
|
|
86
|
+
// discover a missing API key, so name the configured provider here where
|
|
87
|
+
// someone is reading the log, rather than only on the first call that fails.
|
|
88
|
+
if (!this.#realtime) {
|
|
89
|
+
const providerId = this.#config.voice.realtime.provider;
|
|
90
|
+
log.warn("standin-msteams: no realtime voice provider resolved" +
|
|
91
|
+
(providerId
|
|
92
|
+
? ` (configured provider "${providerId}" has no usable credentials)`
|
|
93
|
+
: " (no realtime provider configured)") +
|
|
94
|
+
'. Every call will be refused with "realtime-unavailable". Set the provider API key.');
|
|
95
|
+
}
|
|
96
|
+
const server = new CallServer({
|
|
97
|
+
handlerFactory: () => new TeamsCallHandler({
|
|
98
|
+
config: this.#config,
|
|
99
|
+
realtime: this.#realtime,
|
|
100
|
+
cfg: this.#api.config,
|
|
101
|
+
registry: this.#calls,
|
|
102
|
+
logger: log,
|
|
103
|
+
}),
|
|
104
|
+
secret: this.#config.media.secret,
|
|
105
|
+
port: this.#config.media.port,
|
|
106
|
+
wsPath: this.#config.media.path,
|
|
107
|
+
maxConnections: this.#config.media.maxConcurrentCalls,
|
|
108
|
+
...(this.#config.media.bindAddress
|
|
109
|
+
? { host: this.#config.media.bindAddress }
|
|
110
|
+
: {}),
|
|
111
|
+
});
|
|
112
|
+
await server.start();
|
|
113
|
+
this.#server = server;
|
|
114
|
+
log.info(`standin-msteams: listening on ${this.#config.media.bindAddress ?? "0.0.0.0"}:${server.port}${server.wsPath}` +
|
|
115
|
+
` (max ${this.#config.media.maxConcurrentCalls} concurrent)`);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Close live calls first, then the listener.
|
|
119
|
+
*
|
|
120
|
+
* That order is not cosmetic: the SDK's teardown frees a call's slot only once
|
|
121
|
+
* its close settles, so tearing the listener down over live calls is how a
|
|
122
|
+
* shutdown ends up waiting on sockets nobody is reading any more.
|
|
123
|
+
*/
|
|
124
|
+
async stop() {
|
|
125
|
+
this.#calls.closeAll();
|
|
126
|
+
const server = this.#server;
|
|
127
|
+
this.#server = undefined;
|
|
128
|
+
if (server)
|
|
129
|
+
await server.aclose();
|
|
130
|
+
this.#api.logger.info("standin-msteams: stopped");
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Ask the host which realtime provider is configured.
|
|
134
|
+
*
|
|
135
|
+
* Returns undefined rather than throwing on ANY failure, including a throw from
|
|
136
|
+
* the host resolver: a provider that cannot be resolved is a call that gets
|
|
137
|
+
* refused, not a gateway that will not boot. The plugin is one service among
|
|
138
|
+
* many in that process.
|
|
139
|
+
*/
|
|
140
|
+
#resolveRealtime() {
|
|
141
|
+
try {
|
|
142
|
+
const resolved = resolveConfiguredRealtimeVoiceProvider({
|
|
143
|
+
configuredProviderId: this.#config.voice.realtime.provider,
|
|
144
|
+
providerConfigs: this.#config.voice.realtime.providers,
|
|
145
|
+
cfg: this.#api.config,
|
|
146
|
+
});
|
|
147
|
+
if (!resolved?.provider || !resolved.providerConfig)
|
|
148
|
+
return undefined;
|
|
149
|
+
return {
|
|
150
|
+
provider: resolved.provider,
|
|
151
|
+
providerConfig: resolved.providerConfig,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
this.#api.logger.warn(`standin-msteams: realtime provider resolution failed - ${err instanceof Error ? err.message : String(err)}`);
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/plugins/openclaw/runtime.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAE/B;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAG5C,OAAO,EAAE,sCAAsC,EAAE,MAAM,oCAAoC,CAAC;AAG5F,OAAO,EACL,gBAAgB,GAGjB,MAAM,cAAc,CAAC;AAGtB;;;;;;GAMG;AACH,MAAM,SAAS;IACJ,IAAI,CAAS;IACb,MAAM,GAAG,IAAI,GAAG,EAAoC,CAAC;IAE9D,YAAY,GAAW;QACrB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;IAClB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;IAC1B,CAAC;IAED,OAAO,CAAC,MAAc;QACpB,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QAChD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC,MAAc,EAAE,IAAkB;QACrC,4EAA4E;QAC5E,6EAA6E;QAC7E,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC7D,CAAC;IAED,OAAO,CAAC,MAAc;QACpB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IAED,8EAA8E;IAC9E,QAAQ;QACN,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QACvD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;CACF;AAED,sEAAsE;AACtE,MAAM,OAAO,kBAAkB;IACpB,IAAI,CAAoB;IACxB,OAAO,CAAuB;IAC9B,MAAM,CAAY;IAC3B,OAAO,CAAyB;IAChC,SAAS,CAA+B;IAExC,YAAY,GAAsB,EAAE,MAA4B;QAC9D,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAC/D,CAAC;IAED,qEAAqE;IACrE,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAEzC,6EAA6E;QAC7E,uEAAuE;QACvE,yEAAyE;QACzE,6EAA6E;QAC7E,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACxD,GAAG,CAAC,IAAI,CACN,sDAAsD;gBACpD,CAAC,UAAU;oBACT,CAAC,CAAC,0BAA0B,UAAU,8BAA8B;oBACpE,CAAC,CAAC,oCAAoC,CAAC;gBACzC,qFAAqF,CACxF,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC;YAC5B,cAAc,EAAE,GAAG,EAAE,CACnB,IAAI,gBAAgB,CAAC;gBACnB,MAAM,EAAE,IAAI,CAAC,OAAO;gBACpB,QAAQ,EAAE,IAAI,CAAC,SAAS;gBACxB,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,MAAmC;gBAClD,QAAQ,EAAE,IAAI,CAAC,MAAM;gBACrB,MAAM,EAAE,GAAG;aACZ,CAAC;YACJ,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM;YACjC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI;YAC7B,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI;YAC/B,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,kBAAkB;YACrD,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW;gBAChC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE;gBAC1C,CAAC,CAAC,EAAE,CAAC;SACR,CAAC,CAAC;QAEH,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,GAAG,CAAC,IAAI,CACN,iCAAiC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,IAAI,SAAS,IAAI,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE;YAC3G,SAAS,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,kBAAkB,cAAc,CAC/D,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,IAAI,MAAM;YAAE,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IACpD,CAAC;IAED;;;;;;;OAOG;IACH,gBAAgB;QACd,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,sCAAsC,CAAC;gBACtD,oBAAoB,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ;gBAC1D,eAAe,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS;gBACtD,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,MAAmC;aACnD,CAA0C,CAAC;YAC5C,IAAI,CAAC,QAAQ,EAAE,QAAQ,IAAI,CAAC,QAAQ,CAAC,cAAc;gBAAE,OAAO,SAAS,CAAC;YACtE,OAAO;gBACL,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,cAAc,EAAE,QAAQ,CAAC,cAAc;aACxC,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CACnB,0DAA0D,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC7G,CAAC;YACF,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;CACF"}
|
package/dist/policy.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who is allowed to call the agent.
|
|
3
|
+
*
|
|
4
|
+
* StandIn authenticates the SOCKET, not the human: the HMAC handshake proves the
|
|
5
|
+
* call came from StandIn, and `session.start` then names a caller. Deciding
|
|
6
|
+
* whether that caller may be answered is the deployment's business, so it lives
|
|
7
|
+
* here rather than in the SDK.
|
|
8
|
+
*
|
|
9
|
+
* Framework-neutral on purpose: nothing here imports OpenClaw, so the policy can
|
|
10
|
+
* be read, tested and reused without a gateway in the process.
|
|
11
|
+
*/
|
|
12
|
+
/** Normalize a phone number to digits only. */
|
|
13
|
+
export declare function normalizePhoneNumber(input?: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* True when the caller matches an allowlist entry - by phone number (digits
|
|
16
|
+
* only) OR by exact caller id, case-insensitive.
|
|
17
|
+
*
|
|
18
|
+
* The id match is what lets a Microsoft Teams caller be allowlisted at all: their id is an
|
|
19
|
+
* AAD object id, not a phone number, so phone normalization would reduce it to
|
|
20
|
+
* the empty string and match nothing.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isAllowlistedCaller(from: string | undefined, allowFrom: string[] | undefined): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Inbound-policy decision. `from` is the caller's AAD object id for a Microsoft Teams call.
|
|
25
|
+
*
|
|
26
|
+
* An unset or unknown policy REFUSES. Defaulting the other way would mean a
|
|
27
|
+
* config typo silently opens the agent to anyone who can reach the number.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isInboundCallAllowed(inboundPolicy: "disabled" | "allowlist" | "pairing" | "open" | undefined, allowFrom: string[] | undefined, from: string | undefined): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* An actionable log line for a caller the policy just refused.
|
|
32
|
+
*
|
|
33
|
+
* "pairing" is enforced as a plain allowlist here: no pairing codes, expirations
|
|
34
|
+
* or approval prompts are issued for calls, so the operator's fix is the same as
|
|
35
|
+
* under "allowlist".
|
|
36
|
+
*/
|
|
37
|
+
export declare function describeInboundRejection(inboundPolicy: "disabled" | "allowlist" | "pairing" | "open" | undefined, from: string | undefined): string;
|
|
38
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AAEH,+CAA+C;AAC/C,wBAAgB,oBAAoB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAK3D;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,GAC9B,OAAO,CAwBT;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,EACxE,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,EAC/B,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,OAAO,CAUT;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,aAAa,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,EACxE,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,MAAM,CAqBR"}
|
package/dist/policy.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Copyright (c) 2026 Komaa DigiTech
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* Who is allowed to call the agent.
|
|
5
|
+
*
|
|
6
|
+
* StandIn authenticates the SOCKET, not the human: the HMAC handshake proves the
|
|
7
|
+
* call came from StandIn, and `session.start` then names a caller. Deciding
|
|
8
|
+
* whether that caller may be answered is the deployment's business, so it lives
|
|
9
|
+
* here rather than in the SDK.
|
|
10
|
+
*
|
|
11
|
+
* Framework-neutral on purpose: nothing here imports OpenClaw, so the policy can
|
|
12
|
+
* be read, tested and reused without a gateway in the process.
|
|
13
|
+
*/
|
|
14
|
+
/** Normalize a phone number to digits only. */
|
|
15
|
+
export function normalizePhoneNumber(input) {
|
|
16
|
+
if (!input) {
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
return input.replace(/\D/g, "");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* True when the caller matches an allowlist entry - by phone number (digits
|
|
23
|
+
* only) OR by exact caller id, case-insensitive.
|
|
24
|
+
*
|
|
25
|
+
* The id match is what lets a Microsoft Teams caller be allowlisted at all: their id is an
|
|
26
|
+
* AAD object id, not a phone number, so phone normalization would reduce it to
|
|
27
|
+
* the empty string and match nothing.
|
|
28
|
+
*/
|
|
29
|
+
export function isAllowlistedCaller(from, allowFrom) {
|
|
30
|
+
const raw = from?.trim();
|
|
31
|
+
if (!raw) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const idFrom = raw.toLowerCase();
|
|
35
|
+
const normalizedFrom = normalizePhoneNumber(raw);
|
|
36
|
+
return (allowFrom ?? []).some((entry) => {
|
|
37
|
+
const trimmed = entry.trim();
|
|
38
|
+
if (!trimmed) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
// Exact caller-id match (e.g. a Microsoft Teams AAD object id), case-insensitive.
|
|
42
|
+
if (trimmed.toLowerCase() === idFrom) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
// Phone-number match (digits only).
|
|
46
|
+
const normalizedAllow = normalizePhoneNumber(trimmed);
|
|
47
|
+
return (normalizedAllow !== "" &&
|
|
48
|
+
normalizedFrom !== "" &&
|
|
49
|
+
normalizedAllow === normalizedFrom);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Inbound-policy decision. `from` is the caller's AAD object id for a Microsoft Teams call.
|
|
54
|
+
*
|
|
55
|
+
* An unset or unknown policy REFUSES. Defaulting the other way would mean a
|
|
56
|
+
* config typo silently opens the agent to anyone who can reach the number.
|
|
57
|
+
*/
|
|
58
|
+
export function isInboundCallAllowed(inboundPolicy, allowFrom, from) {
|
|
59
|
+
switch (inboundPolicy) {
|
|
60
|
+
case "open":
|
|
61
|
+
return true;
|
|
62
|
+
case "allowlist":
|
|
63
|
+
case "pairing":
|
|
64
|
+
return isAllowlistedCaller(from, allowFrom);
|
|
65
|
+
default:
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* An actionable log line for a caller the policy just refused.
|
|
71
|
+
*
|
|
72
|
+
* "pairing" is enforced as a plain allowlist here: no pairing codes, expirations
|
|
73
|
+
* or approval prompts are issued for calls, so the operator's fix is the same as
|
|
74
|
+
* under "allowlist".
|
|
75
|
+
*/
|
|
76
|
+
export function describeInboundRejection(inboundPolicy, from) {
|
|
77
|
+
const policy = inboundPolicy ?? "disabled";
|
|
78
|
+
const caller = from?.trim()
|
|
79
|
+
? `caller "${from.trim()}"`
|
|
80
|
+
: "caller with no caller id";
|
|
81
|
+
if (policy === "disabled" || policy === "open") {
|
|
82
|
+
// "open" never rejects; kept for exhaustiveness if callers reuse this.
|
|
83
|
+
return (`inbound call rejected by policy "${policy}": inbound calling is ` +
|
|
84
|
+
(policy === "disabled"
|
|
85
|
+
? 'disabled - set inboundPolicy to "allowlist" and add callers to allowFrom to accept calls'
|
|
86
|
+
: "open") +
|
|
87
|
+
` (${caller})`);
|
|
88
|
+
}
|
|
89
|
+
const pairingHint = policy === "pairing"
|
|
90
|
+
? ' Note: "pairing" currently enforces a plain allowlist (this plugin issues no pairing codes' +
|
|
91
|
+
" or approvals for calls) - add the caller's AAD object id to allowFrom."
|
|
92
|
+
: " Add the caller's AAD object id (or phone number) to allowFrom to accept them.";
|
|
93
|
+
return `inbound call rejected by policy "${policy}": ${caller} is not in allowFrom.${pairingHint}`;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,+BAA+B;AAE/B;;;;;;;;;;GAUG;AAEH,+CAA+C;AAC/C,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAwB,EACxB,SAA+B;IAE/B,MAAM,GAAG,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC;IACzB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IACjC,MAAM,cAAc,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;IACjD,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;QACtC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,KAAK,CAAC;QACf,CAAC;QACD,kFAAkF;QAClF,IAAI,OAAO,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,oCAAoC;QACpC,MAAM,eAAe,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACtD,OAAO,CACL,eAAe,KAAK,EAAE;YACtB,cAAc,KAAK,EAAE;YACrB,eAAe,KAAK,cAAc,CACnC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,aAAwE,EACxE,SAA+B,EAC/B,IAAwB;IAExB,QAAQ,aAAa,EAAE,CAAC;QACtB,KAAK,MAAM;YACT,OAAO,IAAI,CAAC;QACd,KAAK,WAAW,CAAC;QACjB,KAAK,SAAS;YACZ,OAAO,mBAAmB,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC9C;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACtC,aAAwE,EACxE,IAAwB;IAExB,MAAM,MAAM,GAAG,aAAa,IAAI,UAAU,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE;QACzB,CAAC,CAAC,WAAW,IAAI,CAAC,IAAI,EAAE,GAAG;QAC3B,CAAC,CAAC,0BAA0B,CAAC;IAC/B,IAAI,MAAM,KAAK,UAAU,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QAC/C,uEAAuE;QACvE,OAAO,CACL,oCAAoC,MAAM,wBAAwB;YAClE,CAAC,MAAM,KAAK,UAAU;gBACpB,CAAC,CAAC,0FAA0F;gBAC5F,CAAC,CAAC,MAAM,CAAC;YACX,KAAK,MAAM,GAAG,CACf,CAAC;IACJ,CAAC;IACD,MAAM,WAAW,GACf,MAAM,KAAK,SAAS;QAClB,CAAC,CAAC,4FAA4F;YAC5F,yEAAyE;QAC3E,CAAC,CAAC,gFAAgF,CAAC;IACvF,OAAO,oCAAoC,MAAM,MAAM,MAAM,wBAAwB,WAAW,EAAE,CAAC;AACrG,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export { contextSentences, decodePcm, parseMessage } from "./protocolRuntime.js";
|
|
2
|
+
export declare const SAMPLE_RATE_HZ = 16000;
|
|
3
|
+
export declare const NUM_CHANNELS = 1;
|
|
4
|
+
export declare const TYPE_SESSION_START = "session.start";
|
|
5
|
+
export declare const TYPE_SESSION_END = "session.end";
|
|
6
|
+
export declare const TYPE_RECORDING_STATUS = "recording.status";
|
|
7
|
+
export declare const TYPE_AUDIO_FRAME = "audio.frame";
|
|
8
|
+
export declare const TYPE_VIDEO_FRAME = "video.frame";
|
|
9
|
+
export declare const TYPE_PARTICIPANTS = "participants";
|
|
10
|
+
export declare const TYPE_DTMF = "dtmf";
|
|
11
|
+
export declare const TYPE_PING = "ping";
|
|
12
|
+
export declare const TYPE_ASSISTANT_SAY = "assistant.say";
|
|
13
|
+
export declare const TYPE_ASSISTANT_CANCEL = "assistant.cancel";
|
|
14
|
+
export declare const TYPE_EXPRESSION = "expression";
|
|
15
|
+
export declare const TYPE_SPEECH_MARKS = "speech.marks";
|
|
16
|
+
export declare const TYPE_DISPLAY_IMAGE = "display.image";
|
|
17
|
+
export declare const TYPE_DISPLAY_FRAME = "display.frame";
|
|
18
|
+
export declare const TYPE_PONG = "pong";
|
|
19
|
+
/** Caller identity; blank or absent values normalize to undefined. */
|
|
20
|
+
export interface Caller {
|
|
21
|
+
readonly aadId?: string;
|
|
22
|
+
readonly displayName?: string;
|
|
23
|
+
readonly tenantId?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Call context with the SDK's compatible defaults. */
|
|
26
|
+
export interface SessionStart {
|
|
27
|
+
readonly callId: string;
|
|
28
|
+
readonly threadId: string;
|
|
29
|
+
readonly caller: Caller;
|
|
30
|
+
readonly direction: "inbound" | "outbound";
|
|
31
|
+
readonly recordingStatus?: string;
|
|
32
|
+
readonly tenantId?: string;
|
|
33
|
+
}
|
|
34
|
+
/** Read call context; only callId lacks a safe default. */
|
|
35
|
+
export declare function parseSessionStart(msg: Record<string, unknown>): SessionStart;
|
|
36
|
+
/** Build an outbound `audio.frame` JSON frame. */
|
|
37
|
+
export declare function audioFrame(seq: number, timestampMs: number, pcm: Buffer): string;
|
|
38
|
+
/** Build an outbound `pong` JSON frame. */
|
|
39
|
+
export declare function pong(ts: unknown): string;
|
|
40
|
+
/** Build an outbound `assistant.cancel` JSON frame. */
|
|
41
|
+
export declare function assistantCancel(turnId: number): string;
|
|
42
|
+
/** Build an outbound `session.end` JSON frame. */
|
|
43
|
+
export declare function sessionEnd(reason: string): string;
|
|
44
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEjF,eAAO,MAAM,cAAc,QAAS,CAAC;AACrC,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAC9C,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AACxD,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAC9C,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAC9C,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAChD,eAAO,MAAM,SAAS,SAAS,CAAC;AAChC,eAAO,MAAM,SAAS,SAAS,CAAC;AAChC,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AACxD,eAAO,MAAM,eAAe,eAAe,CAAC;AAC5C,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAChD,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAClD,eAAO,MAAM,SAAS,SAAS,CAAC;AAEhC,sEAAsE;AACtE,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC;IAC3C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,2DAA2D;AAC3D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,CAoB5E;AAED,kDAAkD;AAClD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAOhF;AAED,2CAA2C;AAC3C,wBAAgB,IAAI,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CAKxC;AAED,uDAAuD;AACvD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED,kDAAkD;AAClD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKjD"}
|