diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json b/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json index 3d2a400d6e49..5aa72232d5c6 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-harness-runtime.json @@ -1 +1 @@ -{"contentHash":"db186d810ff456142338c36af49cf272d4d58063d341a9d2f93885acc1c0a168","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"} +{"contentHash":"39d48898b10f6d4ef6b2dbb1fbb3ae8e096d0ee76a212d31d91e4de0ea398566","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/agent-harness.json b/docs/.generated/plugin-sdk-api-baseline/agent-harness.json index 849ceca4c967..373c542b10e7 100644 --- a/docs/.generated/plugin-sdk-api-baseline/agent-harness.json +++ b/docs/.generated/plugin-sdk-api-baseline/agent-harness.json @@ -1 +1 @@ -{"contentHash":"30bff907731a9c485e5ebf857d2bccb22f64733d6db696f65630ed24ffb0ab9d","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"} +{"contentHash":"caba2cdff925b864544cfa9ad30a50d36aa2e3d02eec2a3b82329fe80abe73c4","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-core.json b/docs/.generated/plugin-sdk-api-baseline/channel-core.json index 605ed60d34d3..f4ef803e012a 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-core.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-core.json @@ -1 +1 @@ -{"contentHash":"354bdde4f8741dc12786458920d1c1f251d6771f86e1b35455388fcffad7412e","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"} +{"contentHash":"bd0ca8f5fceab0afd12595510947e8e03836a987131808d41968648ca27ca0a7","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json b/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json index b404b5cb2caa..6a892441416d 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-entry-contract.json @@ -1 +1 @@ -{"contentHash":"e758c281a2468ed7b6692d7aac41532aa392199205d34c21425885650fe5002a","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"} +{"contentHash":"7de6b8aa7aaa563081de41601fa5b1dbafdcf1db4f0619efdfc0e665a18200bf","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-message.json b/docs/.generated/plugin-sdk-api-baseline/channel-message.json index 1d836262f76f..32a2aef0f6d5 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-message.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-message.json @@ -1 +1 @@ -{"contentHash":"ffdbd5eee1c1d4c3e3a906e9ef11cac1b67594c3f28698fbb91c9b4e9cefef01","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"} +{"contentHash":"878b20eb69916c437adb781ea65c01ebb1368a2b588392848f31339c44640ec6","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json b/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json index b13ce727b9f0..19ef1c18958a 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-outbound.json @@ -1 +1 @@ -{"contentHash":"e32f1483f2008c9ba937a6190e632ed3d47c59f9403770cad5ec4079c66f6e67","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"} +{"contentHash":"9ccff6b92eb45510ddbd80647ba47c965ef40928c566bbca8931f49942a85b42","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"} diff --git a/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json b/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json index ae5e7c472a20..ece6d508a826 100644 --- a/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json +++ b/docs/.generated/plugin-sdk-api-baseline/channel-plugin-common.json @@ -1 +1 @@ -{"contentHash":"4b024e2a19bf93fbe9c910d84d9960751b89c287e69f7d56fc85ccae4e39e3ba","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"} +{"contentHash":"9cddc6986f6241e736d9db7429d55201d3476aea67a9790499e7d4ae1c55f595","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"} diff --git a/docs/.generated/plugin-sdk-api-baseline/core.json b/docs/.generated/plugin-sdk-api-baseline/core.json index 2123f1354917..f840dd2abaf3 100644 --- a/docs/.generated/plugin-sdk-api-baseline/core.json +++ b/docs/.generated/plugin-sdk-api-baseline/core.json @@ -1 +1 @@ -{"contentHash":"a12c5a0cb965b393e90521aeb1ce23d9939d9468935a33d8c7c3e717f822d10a","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"} +{"contentHash":"ea89b6eb15a02e04a68c487c0d0de783cf2081291168ac3fc208487288912071","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"} diff --git a/docs/.generated/plugin-sdk-api-baseline/discord.json b/docs/.generated/plugin-sdk-api-baseline/discord.json index 70a7d939b0ef..eb75520c8067 100644 --- a/docs/.generated/plugin-sdk-api-baseline/discord.json +++ b/docs/.generated/plugin-sdk-api-baseline/discord.json @@ -1 +1 @@ -{"contentHash":"18eac1e06d9ac32918f30178967692e13a47d7272b30c4386d12b74366192a47","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"} +{"contentHash":"acdda0f0f91c57963a22b378bcade2eeaea8c783ef37e4659a70331aed54e5ee","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"} diff --git a/docs/.generated/plugin-sdk-api-baseline/gateway-runtime.json b/docs/.generated/plugin-sdk-api-baseline/gateway-runtime.json index ad270f9e06da..2c10e04fb68d 100644 --- a/docs/.generated/plugin-sdk-api-baseline/gateway-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/gateway-runtime.json @@ -1 +1 @@ -{"contentHash":"d47afcf53c6c5d9ad1cc99772d09ae60f447824583ab66e0a521f8230fec7bf3","entrypoint":"gateway-runtime","importSpecifier":"openclaw/plugin-sdk/gateway-runtime"} +{"contentHash":"76f94d3629cc7d90118003f29ed95e541ea86919e23eb242eb2b3358a0d4551e","entrypoint":"gateway-runtime","importSpecifier":"openclaw/plugin-sdk/gateway-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json b/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json index aec0c0d5c2c7..d02c64377e20 100644 --- a/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json +++ b/docs/.generated/plugin-sdk-api-baseline/inbound-reply-dispatch.json @@ -1 +1 @@ -{"contentHash":"6949fac4c91bb514789686bf0808d4255c1c9af27e0b880c65be2f25e20a4ec1","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"} +{"contentHash":"b54392fac15cfcd68e1484b8de2d99cf3958bc9114dfd6dc7da9ed9978e877b3","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"} diff --git a/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json b/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json index 023d21b4ba1d..ed5a9768b24c 100644 --- a/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/meeting-runtime.json @@ -1 +1 @@ -{"contentHash":"57a1b7494090393e3c88294bf717a91ce894a059af4ef940cffe110ed4567a47","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"} +{"contentHash":"cd6714e5cb63e6efe2ca4d22d80f6e13e7abb9309712c002f9309c4bbf82c7bb","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json b/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json index 035c3d81ad07..0909494b7675 100644 --- a/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json +++ b/docs/.generated/plugin-sdk-api-baseline/plugin-entry.json @@ -1 +1 @@ -{"contentHash":"606c0eefefe6c30667c337de3a7222bbd1f03ab2c87703445407aa48e651895a","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"} +{"contentHash":"71c3623042aeb01cba967f3f81d1eeb456e2a7e83f2626cdd4ac504c533785a7","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"} diff --git a/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json b/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json index 8e2dd444e761..2c6fc6420bb5 100644 --- a/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/plugin-runtime.json @@ -1 +1 @@ -{"contentHash":"ef25098b086e8959a75cd35962a000ab2e564aa4c57d0987cb991532e623b509","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"} +{"contentHash":"737a29703c4f0d005399a7526856f467c6072416362bcac76ef706d9f960961e","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json b/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json index 208436ebafc0..b8df50cd773e 100644 --- a/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json +++ b/docs/.generated/plugin-sdk-api-baseline/provider-catalog-runtime.json @@ -1 +1 @@ -{"contentHash":"d2642457b2c68f9a0c7b4b46da3ad1f960b1358f235e07da83ec2b121c4897f3","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"} +{"contentHash":"273bbe50ef416560d01e717ad0593c5e513ad6a1cfb9378edd986f6403bb2ded","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"} diff --git a/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json b/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json index 7e6ba611df2d..68b7452e7e03 100644 --- a/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json +++ b/docs/.generated/plugin-sdk-api-baseline/tool-plugin.json @@ -1 +1 @@ -{"contentHash":"56b152bbedcc3c35b9f8ab1e2d44d999931aa24f88b1b902948803f08c8899fe","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"} +{"contentHash":"81dbaf3b8ab62e6b7a132ff01aeb2ec9ab1fcda22b0e089d28fe681d6129c77c","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"} diff --git a/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json b/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json index 0d529a68f5f2..1e1981f2e7af 100644 --- a/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json +++ b/docs/.generated/plugin-sdk-api-baseline/webhook-ingress.json @@ -1 +1 @@ -{"contentHash":"1e5dfd14f428c5181b3f59a6d793d3688cefa04ed4fbde992fdfd7ee9c2bb84c","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"} +{"contentHash":"fe5816b43499ea9edb0687f1e133fdb6986d39b8eaf4d6aa08febfa871a839fe","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"} diff --git a/docs/plugins/sdk-provider-plugins.md b/docs/plugins/sdk-provider-plugins.md index 7af27c77406b..58da18cc672c 100644 --- a/docs/plugins/sdk-provider-plugins.md +++ b/docs/plugins/sdk-provider-plugins.md @@ -865,6 +865,11 @@ catalog, API-key auth, and dynamic model resolution. clients. Implement `handleBargeIn` when a transport can detect that a human is interrupting assistant playback and the provider supports truncating or clearing the active audio response. + `handleBargeIn(options)` may provide a request-scoped + `options.onClearAudio` callback for the exact output being cancelled. + Use that override for the current call; when it is absent, fall back to + the session's `onClearAudio` callback supplied when the bridge was + created. `submitToolResult` may return `void` for synchronous submission, or a `Promise` for an asynchronous completion boundary the provider bridge can expose. Gateway relay sessions wait for that promise before diff --git a/src/talk/realtime-session-harness.ts b/src/talk/realtime-session-harness.ts index cfefc7b94a3b..644a0ac59c96 100644 --- a/src/talk/realtime-session-harness.ts +++ b/src/talk/realtime-session-harness.ts @@ -127,7 +127,7 @@ type RealtimeVoiceOutputAudioEvents = { turn: TalkEnsureTurnResult; }; -type RealtimeVoiceSessionHarnessBase = { +export type RealtimeVoiceSessionHarness = { readonly forcedConsults: RealtimeVoiceForcedConsultCoordinator; readonly outputActivity: RealtimeVoiceOutputActivityTracker; readonly talk: TalkSessionController; @@ -136,7 +136,10 @@ type RealtimeVoiceSessionHarnessBase = { close(): void; createBridge(params: RealtimeVoiceBridgeSessionParams): RealtimeVoiceBridgeSession; emit(input: TalkEventInput): TalkEvent; + ensureTurn(): string; + endTurn(reason?: string): void; finishResponse(outcome: RealtimeVoiceResponseOutcome): TalkTurnResult; + finishOutputAudio(reason: string): void; flushOutput(flush: () => void): void; getHealth(params: { providerConnected: boolean; @@ -145,15 +148,9 @@ type RealtimeVoiceSessionHarnessBase = { handleBargeIn(options: RealtimeVoiceBargeInOptions, flushOutput: () => void): void; isLikelyAssistantEchoTranscript(text: string): boolean; isOutputPlaybackWindowActive(): boolean; - recordTranscript(role: RealtimeVoiceRole, text: string): RealtimeVoiceTranscriptEntry; -}; - -type RealtimeVoiceSessionHarnessMethods = { - ensureTurn(): string; - endTurn(reason?: string): void; - finishOutputAudio(reason: string): void; recordInputAudio(audio: Buffer): boolean; recordOutputAudio(audio: Buffer, activity?: RealtimeVoiceOutputActivityDelta): void; + recordTranscript(role: RealtimeVoiceRole, text: string): RealtimeVoiceTranscriptEntry; }; type RealtimeVoiceEventCapturingSessionHarnessMethods = { @@ -170,27 +167,28 @@ type RealtimeVoiceEventCapturingSessionHarnessMethods = { ): RealtimeVoiceOutputAudioEvents; }; -export type RealtimeVoiceSessionHarness = - RealtimeVoiceSessionHarnessBase & RealtimeVoiceSessionHarnessMethods; +export type RealtimeVoiceEventCapturingSessionHarness = Omit< + RealtimeVoiceSessionHarness, + "ensureTurn" | "endTurn" | "finishOutputAudio" | "recordInputAudio" | "recordOutputAudio" +> & + RealtimeVoiceEventCapturingSessionHarnessMethods; -export type RealtimeVoiceEventCapturingSessionHarness = - RealtimeVoiceSessionHarnessBase & - RealtimeVoiceEventCapturingSessionHarnessMethods; - -type RealtimeVoiceSessionHarnessImplementation = - RealtimeVoiceSessionHarnessBase & { - ensureTurn(): string | TalkEnsureTurnResult; - endTurn(reason?: string): TalkTurnResult | undefined; - finishOutputAudio( - reason: string, - details?: RealtimeVoiceOutputAudioDoneDetails, - ): TalkEvent | undefined; - recordInputAudio(audio: Buffer): boolean | RealtimeVoiceInputAudioEvents | undefined; - recordOutputAudio( - audio: Buffer, - activity?: RealtimeVoiceOutputActivityDelta, - ): RealtimeVoiceOutputAudioEvents | undefined; - }; +type RealtimeVoiceSessionHarnessImplementation = Omit< + RealtimeVoiceSessionHarness, + "ensureTurn" | "endTurn" | "finishOutputAudio" | "recordInputAudio" | "recordOutputAudio" +> & { + ensureTurn(): string | TalkEnsureTurnResult; + endTurn(reason?: string): TalkTurnResult | undefined; + finishOutputAudio( + reason: string, + details?: RealtimeVoiceOutputAudioDoneDetails, + ): TalkEvent | undefined; + recordInputAudio(audio: Buffer): boolean | RealtimeVoiceInputAudioEvents | undefined; + recordOutputAudio( + audio: Buffer, + activity?: RealtimeVoiceOutputActivityDelta, + ): RealtimeVoiceOutputAudioEvents | undefined; +}; type RealtimeVoiceSessionHarnessParams = { talk: TalkSessionControllerParams; @@ -210,9 +208,16 @@ type RealtimeVoiceEventCapturingSessionHarnessParams = Omit< talkPayloads: RealtimeVoiceEventCapturingTalkPayloads; }; -export function createRealtimeVoiceSessionHarness( - params: RealtimeVoiceSessionHarnessParams, -): RealtimeVoiceSessionHarness { +export function createRealtimeVoiceSessionHarness(params: { + talk: TalkSessionControllerParams; + talkPayloads: RealtimeVoiceSessionHarnessTalkPayloads; + onTalkEvent?: (event: TalkEvent) => void; + talkback?: Omit; + forcedConsults?: RealtimeVoiceForcedConsultCoordinatorOptions; + echoSuppression?: RealtimeVoiceSessionHarnessEchoSuppression; + transcriptLookbackMs?: number; + captureBridgeEvents?: boolean; +}): RealtimeVoiceSessionHarness { return createRealtimeVoiceSessionHarnessImplementation( params, false,