From 9ce2b09f710ed577eb1973abdefd9c25a7ed8ed6 Mon Sep 17 00:00:00 2001 From: "Alice (OpenClaw)" Date: Thu, 14 May 2026 08:37:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20add=20OpenClaw=20interactive-handler=20?= =?UTF-8?q?adapter=20/=20=E6=96=B0=E5=A2=9E=20OpenClaw=20interactive-handl?= =?UTF-8?q?er=20adapter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/adapters/openclaw-interactive-handler.ts | 40 ++++++++++++++ tests/openclaw-interactive-handler.test.mjs | 58 ++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 src/adapters/openclaw-interactive-handler.ts create mode 100644 tests/openclaw-interactive-handler.test.mjs diff --git a/src/adapters/openclaw-interactive-handler.ts b/src/adapters/openclaw-interactive-handler.ts new file mode 100644 index 0000000..828ff06 --- /dev/null +++ b/src/adapters/openclaw-interactive-handler.ts @@ -0,0 +1,40 @@ +import { persistOpenClawReplyEndState } from "./openclaw-state-file.ts" +import { buildTelegramCallbackResolution } from "../runtime/openclaw-telegram-callback-actions.ts" +import { normalizeTelegramCallback } from "../core/callback-contract.ts" + +export type OpenClawTelegramInteractiveContext = { + callback: { + data: string + messageId: string + chatId: string + } + conversationId: string + sessionKey: string | null + callbackId: string + respond: { + editButtons: (input: { buttons: { text: string; callback_data: string }[][] }) => Promise | void + reply: (input: { text: string }) => Promise | void + } +} + +export async function handleOpenClawTelegramReplyEndInteraction(baseDir: string, ctx: OpenClawTelegramInteractiveContext): Promise { + const normalized = normalizeTelegramCallback({ + callbackData: ctx.callback.data, + conversationId: ctx.conversationId, + sessionKey: ctx.sessionKey, + sourceMessageId: ctx.callback.messageId, + sourceCallbackId: ctx.callbackId, + timestamp: new Date().toISOString(), + }) + + if (!normalized) { + return false + } + + persistOpenClawReplyEndState(baseDir, normalized) + + const resolution = buildTelegramCallbackResolution(normalized.choice) + await ctx.respond.editButtons({ buttons: resolution.buttons }) + await ctx.respond.reply({ text: resolution.acknowledgement }) + return true +} diff --git a/tests/openclaw-interactive-handler.test.mjs b/tests/openclaw-interactive-handler.test.mjs new file mode 100644 index 0000000..73075dc --- /dev/null +++ b/tests/openclaw-interactive-handler.test.mjs @@ -0,0 +1,58 @@ +import test from "node:test" +import assert from "node:assert/strict" +import fs from "node:fs" +import os from "node:os" +import path from "node:path" + +import { handleOpenClawTelegramReplyEndInteraction } from "../src/adapters/openclaw-interactive-handler.ts" + +test("openclaw interactive handler persists state and emits response actions", async () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "reply-end-interactive-")) + const calls = [] + + const handled = await handleOpenClawTelegramReplyEndInteraction(tempDir, { + callback: { + data: "rec:continue", + messageId: "77", + chatId: "864811879", + }, + conversationId: "864811879", + sessionKey: "agent:main:main", + callbackId: "cb-77", + respond: { + editButtons: async (input) => { + calls.push(["editButtons", input]) + }, + reply: async (input) => { + calls.push(["reply", input]) + }, + }, + }) + + assert.equal(handled, true) + const statePath = path.join(tempDir, "reply-end-controls.json") + const stored = JSON.parse(fs.readFileSync(statePath, "utf-8")) + assert.equal(stored["864811879"].lastChoice, "continue") + assert.equal(calls[0][0], "editButtons") + assert.equal(calls[1][0], "reply") +}) + +test("openclaw interactive handler ignores unrelated callback data", async () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "reply-end-interactive-")) + const handled = await handleOpenClawTelegramReplyEndInteraction(tempDir, { + callback: { + data: "other:value", + messageId: "78", + chatId: "864811879", + }, + conversationId: "864811879", + sessionKey: "agent:main:main", + callbackId: "cb-78", + respond: { + editButtons: async () => {}, + reply: async () => {}, + }, + }) + + assert.equal(handled, false) +})