From 24850b5cc4ce69ca55a7bcc7dcacfeaa4b5c061e Mon Sep 17 00:00:00 2001 From: huntharo Date: Sun, 15 Mar 2026 11:31:35 -0400 Subject: [PATCH] Telegram: persist detached plugin bindings --- .../telegram/src/thread-bindings.test.ts | 36 +++++++++++++++++++ extensions/telegram/src/thread-bindings.ts | 8 ++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/extensions/telegram/src/thread-bindings.test.ts b/extensions/telegram/src/thread-bindings.test.ts index 3b05f50ac9b..39b9c63338b 100644 --- a/extensions/telegram/src/thread-bindings.test.ts +++ b/extensions/telegram/src/thread-bindings.test.ts @@ -211,4 +211,40 @@ describe("telegram thread bindings", () => { ); expect(fs.existsSync(statePath)).toBe(false); }); + + it("persists unbinds before restart so removed bindings do not come back", async () => { + stateDirOverride = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-telegram-bindings-")); + process.env.OPENCLAW_STATE_DIR = stateDirOverride; + + createTelegramThreadBindingManager({ + accountId: "default", + persist: true, + enableSweeper: false, + }); + + const bound = await getSessionBindingService().bind({ + targetSessionKey: "plugin-binding:openclaw-codex-app-server:abc123", + targetKind: "session", + conversation: { + channel: "telegram", + accountId: "default", + conversationId: "8460800771", + }, + }); + + await getSessionBindingService().unbind({ + bindingId: bound.bindingId, + reason: "test-detach", + }); + + __testing.resetTelegramThreadBindingsForTests(); + + const reloaded = createTelegramThreadBindingManager({ + accountId: "default", + persist: true, + enableSweeper: false, + }); + + expect(reloaded.getByConversationId("8460800771")).toBeUndefined(); + }); }); diff --git a/extensions/telegram/src/thread-bindings.ts b/extensions/telegram/src/thread-bindings.ts index 4c4784f8846..d10fef7f72c 100644 --- a/extensions/telegram/src/thread-bindings.ts +++ b/extensions/telegram/src/thread-bindings.ts @@ -544,7 +544,7 @@ export function createTelegramThreadBindingManager( resolveBindingKey({ accountId, conversationId }), record, ); - void persistBindingsToDisk({ accountId, persist: manager.shouldPersistMutations() }); + await persistBindingsToDisk({ accountId, persist: manager.shouldPersistMutations() }); logVerbose( `telegram: bound conversation ${conversationId} -> ${targetSessionKey} (${summarizeLifecycleForLog( record, @@ -604,6 +604,9 @@ export function createTelegramThreadBindingManager( reason: input.reason, sendFarewell: false, }); + if (removed.length > 0) { + await persistBindingsToDisk({ accountId, persist: manager.shouldPersistMutations() }); + } return removed.map((entry) => toSessionBindingRecord(entry, { idleTimeoutMs, @@ -623,6 +626,9 @@ export function createTelegramThreadBindingManager( reason: input.reason, sendFarewell: false, }); + if (removed) { + await persistBindingsToDisk({ accountId, persist: manager.shouldPersistMutations() }); + } return removed ? [ toSessionBindingRecord(removed, {