fix(feishu): reattach active turns on restart
This commit is contained in:
@@ -57,6 +57,10 @@ class ThreadStore {
|
||||
return this.data.chats[chatId] || null;
|
||||
}
|
||||
|
||||
listChats() {
|
||||
return Object.entries(this.data.chats || {});
|
||||
}
|
||||
|
||||
async setChat(chatId, state) {
|
||||
this.data.chats[chatId] = state;
|
||||
await this.save();
|
||||
@@ -133,6 +137,9 @@ if (!config.allowlist.length && !config.allowUnlisted) {
|
||||
}
|
||||
|
||||
wsClient.start({ eventDispatcher: dispatcher });
|
||||
void reattachActiveTurns().catch((error) => {
|
||||
console.error("failed to reattach active Feishu bridge turns", error);
|
||||
});
|
||||
|
||||
async function handleIncomingMessage(event) {
|
||||
const identity = incomingIdentity(event);
|
||||
@@ -293,6 +300,43 @@ async function runPrompt(chatId, prompt) {
|
||||
}
|
||||
}
|
||||
|
||||
async function reattachActiveTurns() {
|
||||
for (const [chatId, state] of threadStore.listChats()) {
|
||||
if (!state?.threadId || !state.activeTurnId) continue;
|
||||
|
||||
const detail = await runtimeJson(`/v1/threads/${encodeURIComponent(state.threadId)}`);
|
||||
const runningTurn = latestRunningTurn(detail);
|
||||
if (!runningTurn) {
|
||||
await threadStore.patchChat(chatId, {
|
||||
activeTurnId: null,
|
||||
lastSeq: Number(detail.latest_seq || state.lastSeq || 0),
|
||||
updatedAt: new Date().toISOString()
|
||||
});
|
||||
await sendText(chatId, `Bridge restarted. No active turn remains for ${state.threadId}.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const turnId = runningTurn.id || state.activeTurnId;
|
||||
const sinceSeq = Number(state.lastSeq || 0);
|
||||
await threadStore.patchChat(chatId, {
|
||||
activeTurnId: turnId,
|
||||
updatedAt: new Date().toISOString()
|
||||
});
|
||||
await sendText(
|
||||
chatId,
|
||||
`Bridge restarted. Reattaching to active turn ${turnId} from seq ${sinceSeq}.`
|
||||
);
|
||||
try {
|
||||
await streamTurnEvents(chatId, state.threadId, turnId, sinceSeq);
|
||||
} finally {
|
||||
await threadStore.patchChat(chatId, {
|
||||
activeTurnId: null,
|
||||
updatedAt: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function streamTurnEvents(chatId, threadId, turnId, sinceSeq) {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), config.turnTimeoutMs);
|
||||
|
||||
@@ -157,11 +157,12 @@ export function commandAction(command) {
|
||||
|
||||
export function splitMessage(text, maxChars = 3500) {
|
||||
const value = String(text || "");
|
||||
if (value.length <= maxChars) return value ? [value] : [];
|
||||
const chars = Array.from(value);
|
||||
if (chars.length <= maxChars) return value ? [value] : [];
|
||||
const chunks = [];
|
||||
let cursor = 0;
|
||||
while (cursor < value.length) {
|
||||
chunks.push(value.slice(cursor, cursor + maxChars));
|
||||
while (cursor < chars.length) {
|
||||
chunks.push(chars.slice(cursor, cursor + maxChars).join(""));
|
||||
cursor += maxChars;
|
||||
}
|
||||
return chunks;
|
||||
|
||||
@@ -145,6 +145,10 @@ test("splitMessage chunks long text", () => {
|
||||
assert.deepEqual(splitMessage("abcdef", 2), ["ab", "cd", "ef"]);
|
||||
});
|
||||
|
||||
test("splitMessage does not split surrogate pairs", () => {
|
||||
assert.deepEqual(splitMessage("a🧪b", 2), ["a🧪", "b"]);
|
||||
});
|
||||
|
||||
test("validateBridgeConfig accepts locked-down whalebro DM config", () => {
|
||||
const result = validateBridgeConfig(
|
||||
{
|
||||
|
||||
@@ -10,8 +10,13 @@ test("ThreadStore is initialized before bridge startup opens it", async () => {
|
||||
const source = await fs.readFile(path.join(__dirname, "../src/index.mjs"), "utf8");
|
||||
const declaration = source.indexOf("class ThreadStore");
|
||||
const startupUse = source.indexOf("await ThreadStore.open");
|
||||
const wsStart = source.indexOf("wsClient.start");
|
||||
const reattachCall = source.indexOf("reattachActiveTurns().catch");
|
||||
|
||||
assert.notEqual(declaration, -1);
|
||||
assert.notEqual(startupUse, -1);
|
||||
assert.notEqual(wsStart, -1);
|
||||
assert.notEqual(reattachCall, -1);
|
||||
assert.ok(declaration < startupUse);
|
||||
assert.ok(wsStart < reattachCall);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user