opencode-feishu 0.7.14 → 0.7.15
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/dist/index.js +156 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -99175,6 +99175,12 @@ async function updateMessage(client, messageId, text2) {
|
|
|
99175
99175
|
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
99176
99176
|
}
|
|
99177
99177
|
}
|
|
99178
|
+
async function deleteMessage(client, messageId) {
|
|
99179
|
+
try {
|
|
99180
|
+
await client.im.message.delete({ path: { message_id: messageId } });
|
|
99181
|
+
} catch {
|
|
99182
|
+
}
|
|
99183
|
+
}
|
|
99178
99184
|
|
|
99179
99185
|
// src/handler/event.ts
|
|
99180
99186
|
var pendingBySession = /* @__PURE__ */ new Map();
|
|
@@ -99230,7 +99236,7 @@ function setSessionError(sessionId, message, fields) {
|
|
|
99230
99236
|
sessionErrorTimeouts.set(sessionId, timeoutId);
|
|
99231
99237
|
}
|
|
99232
99238
|
function registerPending(sessionId, payload) {
|
|
99233
|
-
pendingBySession.set(sessionId, { ...payload, textBuffer: "" });
|
|
99239
|
+
pendingBySession.set(sessionId, { ...payload, textBuffer: "", expectedMessageId: void 0 });
|
|
99234
99240
|
}
|
|
99235
99241
|
function unregisterPending(sessionId) {
|
|
99236
99242
|
pendingBySession.delete(sessionId);
|
|
@@ -99284,6 +99290,16 @@ async function handleEvent(event, deps) {
|
|
|
99284
99290
|
if (!sessionId) break;
|
|
99285
99291
|
const payload = pendingBySession.get(sessionId);
|
|
99286
99292
|
if (!payload) break;
|
|
99293
|
+
const messageId = part.messageID;
|
|
99294
|
+
if (messageId) {
|
|
99295
|
+
if (!payload.expectedMessageId) {
|
|
99296
|
+
payload.expectedMessageId = messageId;
|
|
99297
|
+
} else if (payload.expectedMessageId !== messageId) {
|
|
99298
|
+
break;
|
|
99299
|
+
}
|
|
99300
|
+
} else if (payload.expectedMessageId) {
|
|
99301
|
+
break;
|
|
99302
|
+
}
|
|
99287
99303
|
const delta = event.properties.delta;
|
|
99288
99304
|
if (delta) {
|
|
99289
99305
|
payload.textBuffer += delta;
|
|
@@ -99343,6 +99359,9 @@ function buildSessionKey(chatType, id) {
|
|
|
99343
99359
|
function generateSessionTitle(sessionKey) {
|
|
99344
99360
|
return `${TITLE_PREFIX}-${sessionKey}-${Date.now()}`;
|
|
99345
99361
|
}
|
|
99362
|
+
function getCachedSession(sessionKey) {
|
|
99363
|
+
return sessionCache.get(sessionKey);
|
|
99364
|
+
}
|
|
99346
99365
|
async function getOrCreateSession(client, sessionKey, directory) {
|
|
99347
99366
|
const cached = sessionCache.get(sessionKey);
|
|
99348
99367
|
if (cached) return cached;
|
|
@@ -99381,9 +99400,8 @@ async function getOrCreateSession(client, sessionKey, directory) {
|
|
|
99381
99400
|
}
|
|
99382
99401
|
|
|
99383
99402
|
// src/handler/chat.ts
|
|
99384
|
-
var SSE_RACE_WAIT_MS = 100;
|
|
99385
99403
|
var activeAutoPrompts = /* @__PURE__ */ new Map();
|
|
99386
|
-
async function handleChat(ctx, deps) {
|
|
99404
|
+
async function handleChat(ctx, deps, signal) {
|
|
99387
99405
|
const { content, chatId, chatType, senderId, shouldReply, messageType, rawContent, messageId } = ctx;
|
|
99388
99406
|
if (!content.trim() && messageType === "text") return;
|
|
99389
99407
|
const { config, client, feishuClient, log, directory } = deps;
|
|
@@ -99411,7 +99429,7 @@ async function handleChat(ctx, deps) {
|
|
|
99411
99429
|
const baseBody = { parts };
|
|
99412
99430
|
if (!shouldReply) {
|
|
99413
99431
|
try {
|
|
99414
|
-
await client.session.
|
|
99432
|
+
await client.session.promptAsync({
|
|
99415
99433
|
path: { id: session.id },
|
|
99416
99434
|
query,
|
|
99417
99435
|
body: { ...baseBody, noReply: true }
|
|
@@ -99488,12 +99506,12 @@ async function handleChat(ctx, deps) {
|
|
|
99488
99506
|
}
|
|
99489
99507
|
try {
|
|
99490
99508
|
clearSessionError(session.id);
|
|
99491
|
-
await client.session.
|
|
99509
|
+
await client.session.promptAsync({
|
|
99492
99510
|
path: { id: session.id },
|
|
99493
99511
|
query,
|
|
99494
99512
|
body: baseBody
|
|
99495
99513
|
});
|
|
99496
|
-
const finalText = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query });
|
|
99514
|
+
const finalText = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query, signal });
|
|
99497
99515
|
log("info", "\u6A21\u578B\u54CD\u5E94\u5B8C\u6210", {
|
|
99498
99516
|
sessionKey,
|
|
99499
99517
|
sessionId: session.id,
|
|
@@ -99503,21 +99521,21 @@ async function handleChat(ctx, deps) {
|
|
|
99503
99521
|
await replyOrUpdate(feishuClient, chatId, placeholderId, finalText || "\u26A0\uFE0F \u54CD\u5E94\u8D85\u65F6");
|
|
99504
99522
|
await runAutoPromptLoop(session.id);
|
|
99505
99523
|
} catch (err) {
|
|
99524
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
99525
|
+
log("info", "\u5904\u7406\u88AB\u4E2D\u65AD", { sessionKey, sessionId: session.id });
|
|
99526
|
+
if (placeholderId) {
|
|
99527
|
+
await deleteMessage(feishuClient, placeholderId).catch(() => {
|
|
99528
|
+
});
|
|
99529
|
+
}
|
|
99530
|
+
return;
|
|
99531
|
+
}
|
|
99506
99532
|
let sessionError;
|
|
99507
99533
|
if (err instanceof SessionErrorDetected) {
|
|
99508
99534
|
sessionError = err.sessionError;
|
|
99509
99535
|
clearSessionError(session.id);
|
|
99510
99536
|
} else {
|
|
99511
|
-
await new Promise((r) => setTimeout(r, SSE_RACE_WAIT_MS));
|
|
99512
99537
|
sessionError = getSessionError(session.id);
|
|
99513
99538
|
clearSessionError(session.id);
|
|
99514
|
-
if (!sessionError) {
|
|
99515
|
-
const thrownFields = extractErrorFields(err);
|
|
99516
|
-
if (isModelError(thrownFields)) {
|
|
99517
|
-
const thrownMsg = err instanceof Error ? err.message : String(err);
|
|
99518
|
-
sessionError = { message: thrownMsg, fields: thrownFields };
|
|
99519
|
-
}
|
|
99520
|
-
}
|
|
99521
99539
|
}
|
|
99522
99540
|
if (sessionError) {
|
|
99523
99541
|
log("info", "\u9519\u8BEF\u5B57\u6BB5\u68C0\u67E5", {
|
|
@@ -99549,12 +99567,12 @@ async function handleChat(ctx, deps) {
|
|
|
99549
99567
|
modelID: modelOverride.modelID
|
|
99550
99568
|
});
|
|
99551
99569
|
clearSessionError(session.id);
|
|
99552
|
-
await client.session.
|
|
99570
|
+
await client.session.promptAsync({
|
|
99553
99571
|
path: { id: session.id },
|
|
99554
99572
|
query,
|
|
99555
99573
|
body: { ...baseBody, model: modelOverride }
|
|
99556
99574
|
});
|
|
99557
|
-
const finalText = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query });
|
|
99575
|
+
const finalText = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query, signal });
|
|
99558
99576
|
log("info", "\u6A21\u578B\u6062\u590D\u540E\u54CD\u5E94\u5B8C\u6210", {
|
|
99559
99577
|
sessionKey,
|
|
99560
99578
|
sessionId: session.id,
|
|
@@ -99572,19 +99590,25 @@ async function handleChat(ctx, deps) {
|
|
|
99572
99590
|
return;
|
|
99573
99591
|
}
|
|
99574
99592
|
} catch (recoveryErr) {
|
|
99593
|
+
if (recoveryErr instanceof Error && recoveryErr.name === "AbortError") {
|
|
99594
|
+
log("info", "\u6A21\u578B\u6062\u590D\u88AB\u4E2D\u65AD", { sessionKey });
|
|
99595
|
+
if (placeholderId) {
|
|
99596
|
+
await deleteMessage(feishuClient, placeholderId).catch(() => {
|
|
99597
|
+
});
|
|
99598
|
+
}
|
|
99599
|
+
return;
|
|
99600
|
+
}
|
|
99575
99601
|
const errMsg = recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr);
|
|
99576
99602
|
if (recoveryErr instanceof SessionErrorDetected) {
|
|
99577
99603
|
sessionError = recoveryErr.sessionError;
|
|
99578
99604
|
clearSessionError(session.id);
|
|
99579
99605
|
} else {
|
|
99580
|
-
await new Promise((r) => setTimeout(r, SSE_RACE_WAIT_MS));
|
|
99581
99606
|
const sseError = getSessionError(session.id);
|
|
99582
99607
|
if (sseError) {
|
|
99583
99608
|
sessionError = sseError;
|
|
99584
99609
|
clearSessionError(session.id);
|
|
99585
99610
|
} else {
|
|
99586
|
-
|
|
99587
|
-
sessionError = { message: errMsg, fields: thrownFields };
|
|
99611
|
+
sessionError = { message: errMsg, fields: [] };
|
|
99588
99612
|
}
|
|
99589
99613
|
}
|
|
99590
99614
|
log("error", "\u6A21\u578B\u6062\u590D\u5931\u8D25", {
|
|
@@ -99607,7 +99631,7 @@ async function handleChat(ctx, deps) {
|
|
|
99607
99631
|
sessionKey,
|
|
99608
99632
|
chatType,
|
|
99609
99633
|
error: thrownError,
|
|
99610
|
-
...sessionError ? { sessionError: sessionError.message } : {
|
|
99634
|
+
...sessionError ? { sessionError: sessionError.message } : {}
|
|
99611
99635
|
});
|
|
99612
99636
|
const msg = "\u274C " + errorMessage;
|
|
99613
99637
|
await replyOrUpdate(feishuClient, chatId, placeholderId, msg);
|
|
@@ -99718,6 +99742,113 @@ function extractLastAssistantText(messages) {
|
|
|
99718
99742
|
return parts.filter((p) => p.type === "text").map((p) => p.text ?? "").join("\n").trim();
|
|
99719
99743
|
}
|
|
99720
99744
|
|
|
99745
|
+
// src/handler/session-queue.ts
|
|
99746
|
+
var states = /* @__PURE__ */ new Map();
|
|
99747
|
+
function getOrCreateState(sessionKey) {
|
|
99748
|
+
const existing = states.get(sessionKey);
|
|
99749
|
+
if (existing) return existing;
|
|
99750
|
+
const state = {
|
|
99751
|
+
controller: null,
|
|
99752
|
+
currentTask: null,
|
|
99753
|
+
queue: [],
|
|
99754
|
+
processing: false
|
|
99755
|
+
};
|
|
99756
|
+
states.set(sessionKey, state);
|
|
99757
|
+
return state;
|
|
99758
|
+
}
|
|
99759
|
+
function cleanupStateIfIdle(sessionKey, state) {
|
|
99760
|
+
if (!state.processing && state.queue.length === 0) {
|
|
99761
|
+
states.delete(sessionKey);
|
|
99762
|
+
}
|
|
99763
|
+
}
|
|
99764
|
+
async function enqueueMessage(ctx, deps) {
|
|
99765
|
+
if (!ctx.shouldReply) {
|
|
99766
|
+
await handleChat(ctx, deps);
|
|
99767
|
+
return;
|
|
99768
|
+
}
|
|
99769
|
+
const sessionKey = buildSessionKey(
|
|
99770
|
+
ctx.chatType,
|
|
99771
|
+
ctx.chatType === "p2p" ? ctx.senderId : ctx.chatId
|
|
99772
|
+
);
|
|
99773
|
+
if (ctx.chatType === "p2p") {
|
|
99774
|
+
await handleP2PMessage(sessionKey, ctx, deps);
|
|
99775
|
+
} else {
|
|
99776
|
+
await handleGroupMessage(sessionKey, ctx, deps);
|
|
99777
|
+
}
|
|
99778
|
+
}
|
|
99779
|
+
async function handleP2PMessage(sessionKey, ctx, deps) {
|
|
99780
|
+
const state = getOrCreateState(sessionKey);
|
|
99781
|
+
while (state.processing) {
|
|
99782
|
+
if (state.controller) {
|
|
99783
|
+
state.controller.abort();
|
|
99784
|
+
await abortServerSession(sessionKey, deps);
|
|
99785
|
+
}
|
|
99786
|
+
if (state.currentTask) {
|
|
99787
|
+
await state.currentTask.catch(() => {
|
|
99788
|
+
});
|
|
99789
|
+
}
|
|
99790
|
+
}
|
|
99791
|
+
const controller = new AbortController();
|
|
99792
|
+
state.controller = controller;
|
|
99793
|
+
state.processing = true;
|
|
99794
|
+
const task = processMessage(ctx, deps, controller.signal).finally(() => {
|
|
99795
|
+
state.processing = false;
|
|
99796
|
+
state.controller = null;
|
|
99797
|
+
state.currentTask = null;
|
|
99798
|
+
cleanupStateIfIdle(sessionKey, state);
|
|
99799
|
+
});
|
|
99800
|
+
state.currentTask = task;
|
|
99801
|
+
await task;
|
|
99802
|
+
}
|
|
99803
|
+
async function handleGroupMessage(sessionKey, ctx, deps) {
|
|
99804
|
+
const state = getOrCreateState(sessionKey);
|
|
99805
|
+
state.queue.push({ ctx, deps });
|
|
99806
|
+
if (state.processing) return;
|
|
99807
|
+
await drainLoop(sessionKey, state);
|
|
99808
|
+
}
|
|
99809
|
+
async function drainLoop(sessionKey, state) {
|
|
99810
|
+
state.processing = true;
|
|
99811
|
+
try {
|
|
99812
|
+
while (state.queue.length > 0) {
|
|
99813
|
+
const item = state.queue.shift();
|
|
99814
|
+
if (!item) break;
|
|
99815
|
+
const controller = new AbortController();
|
|
99816
|
+
state.controller = controller;
|
|
99817
|
+
try {
|
|
99818
|
+
await processMessage(item.ctx, item.deps, controller.signal);
|
|
99819
|
+
} catch (err) {
|
|
99820
|
+
item.deps.log("error", "\u7FA4\u804A\u961F\u5217\u6D88\u606F\u5904\u7406\u5931\u8D25", {
|
|
99821
|
+
sessionKey,
|
|
99822
|
+
error: err instanceof Error ? err.message : String(err)
|
|
99823
|
+
});
|
|
99824
|
+
} finally {
|
|
99825
|
+
state.controller = null;
|
|
99826
|
+
}
|
|
99827
|
+
}
|
|
99828
|
+
} finally {
|
|
99829
|
+
state.processing = false;
|
|
99830
|
+
cleanupStateIfIdle(sessionKey, state);
|
|
99831
|
+
}
|
|
99832
|
+
}
|
|
99833
|
+
async function processMessage(ctx, deps, signal) {
|
|
99834
|
+
await handleChat(ctx, deps, signal);
|
|
99835
|
+
}
|
|
99836
|
+
async function abortServerSession(sessionKey, deps) {
|
|
99837
|
+
const { client, log, directory } = deps;
|
|
99838
|
+
const query = directory ? { directory } : void 0;
|
|
99839
|
+
const cached = getCachedSession(sessionKey);
|
|
99840
|
+
if (!cached) return;
|
|
99841
|
+
try {
|
|
99842
|
+
await client.session.abort({ path: { id: cached.id }, query });
|
|
99843
|
+
log("info", "\u5DF2\u4E2D\u65AD\u5F53\u524D\u4F1A\u8BDD\u5904\u7406", { sessionKey, sessionId: cached.id });
|
|
99844
|
+
} catch (err) {
|
|
99845
|
+
log("warn", "\u4E2D\u65AD\u4F1A\u8BDD\u5931\u8D25", {
|
|
99846
|
+
sessionKey,
|
|
99847
|
+
error: err instanceof Error ? err.message : String(err)
|
|
99848
|
+
});
|
|
99849
|
+
}
|
|
99850
|
+
}
|
|
99851
|
+
|
|
99721
99852
|
// src/feishu/history.ts
|
|
99722
99853
|
var DEFAULT_PAGE_SIZE = 50;
|
|
99723
99854
|
async function ingestGroupHistory(feishuClient, opencodeClient, chatId, options) {
|
|
@@ -99802,6 +99933,7 @@ ${body}`;
|
|
|
99802
99933
|
|
|
99803
99934
|
// src/index.ts
|
|
99804
99935
|
var SERVICE_NAME = "opencode-feishu";
|
|
99936
|
+
var LOG_PREFIX = "[feishu]";
|
|
99805
99937
|
var isDebug = !!process.env.FEISHU_DEBUG;
|
|
99806
99938
|
var DEFAULT_CONFIG = {
|
|
99807
99939
|
timeout: 12e4,
|
|
@@ -99823,14 +99955,15 @@ var FeishuPlugin = async (ctx) => {
|
|
|
99823
99955
|
const { client } = ctx;
|
|
99824
99956
|
let gateway = null;
|
|
99825
99957
|
const log = (level, message, extra) => {
|
|
99958
|
+
const prefixed = `${LOG_PREFIX} ${message}`;
|
|
99826
99959
|
if (isDebug) {
|
|
99827
|
-
console.error(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), service: SERVICE_NAME, level, message, ...extra }));
|
|
99960
|
+
console.error(JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), service: SERVICE_NAME, level, message: prefixed, ...extra }));
|
|
99828
99961
|
}
|
|
99829
99962
|
client.app.log({
|
|
99830
99963
|
body: {
|
|
99831
99964
|
service: SERVICE_NAME,
|
|
99832
99965
|
level,
|
|
99833
|
-
message,
|
|
99966
|
+
message: prefixed,
|
|
99834
99967
|
extra
|
|
99835
99968
|
}
|
|
99836
99969
|
}).catch(() => {
|
|
@@ -99884,7 +100017,7 @@ var FeishuPlugin = async (ctx) => {
|
|
|
99884
100017
|
botOpenId,
|
|
99885
100018
|
onMessage: async (msgCtx) => {
|
|
99886
100019
|
if (!msgCtx.content.trim() || !gateway) return;
|
|
99887
|
-
await
|
|
100020
|
+
await enqueueMessage(msgCtx, {
|
|
99888
100021
|
config: resolvedConfig,
|
|
99889
100022
|
client,
|
|
99890
100023
|
feishuClient: gateway.client,
|