opencode-feishu 0.4.2 → 0.5.0
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/README.md +5 -0
- package/dist/index.js +97 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,6 +72,10 @@ opencode
|
|
|
72
72
|
| `stablePolls` | number | 否 | `3` | 连续几次轮询内容不变视为回复完成 |
|
|
73
73
|
| `dedupTtl` | number | 否 | `600000` | 消息去重缓存过期时间(毫秒) |
|
|
74
74
|
| `directory` | string | 否 | `""` | 默认工作目录,支持 `~` 和 `${ENV_VAR}` 展开 |
|
|
75
|
+
| `autoPrompt.enabled` | boolean | 否 | `false` | 启用自动提示(响应完成后自动发送"继续") |
|
|
76
|
+
| `autoPrompt.intervalSeconds` | number | 否 | `30` | 响应完成后等待秒数 |
|
|
77
|
+
| `autoPrompt.maxIterations` | number | 否 | `10` | 单轮对话最大自动提示次数 |
|
|
78
|
+
| `autoPrompt.message` | string | 否 | `"请同步当前进度,如需帮助请说明"` | 自动发送的提示内容 |
|
|
75
79
|
|
|
76
80
|
## 特性
|
|
77
81
|
|
|
@@ -81,6 +85,7 @@ opencode
|
|
|
81
85
|
- **入群自动摄入历史消息**
|
|
82
86
|
- **代理支持** — `HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY`
|
|
83
87
|
- **消息去重** — 可配置 TTL
|
|
88
|
+
- **自动提示** — 响应完成后自动发送"继续",推动 OpenCode 持续工作;用户发新消息自动中断
|
|
84
89
|
|
|
85
90
|
## 群聊行为
|
|
86
91
|
|
package/dist/index.js
CHANGED
|
@@ -99267,12 +99267,19 @@ async function getOrCreateSession(client, sessionKey, directory) {
|
|
|
99267
99267
|
}
|
|
99268
99268
|
|
|
99269
99269
|
// src/handler/chat.ts
|
|
99270
|
+
var activeAutoPrompts = /* @__PURE__ */ new Map();
|
|
99270
99271
|
async function handleChat(ctx, deps) {
|
|
99271
99272
|
const { content, chatId, chatType, senderId, shouldReply, messageType, rawContent, messageId } = ctx;
|
|
99272
99273
|
if (!content.trim() && messageType === "text") return;
|
|
99273
99274
|
const { config, client, feishuClient, log, directory } = deps;
|
|
99274
99275
|
const query = directory ? { directory } : void 0;
|
|
99275
99276
|
const sessionKey = buildSessionKey(chatType, chatType === "p2p" ? senderId : chatId);
|
|
99277
|
+
const existing = activeAutoPrompts.get(sessionKey);
|
|
99278
|
+
if (existing) {
|
|
99279
|
+
existing.abort();
|
|
99280
|
+
activeAutoPrompts.delete(sessionKey);
|
|
99281
|
+
log("info", "\u7528\u6237\u4ECB\u5165\uFF0C\u81EA\u52A8\u63D0\u793A\u5DF2\u4E2D\u65AD", { sessionKey });
|
|
99282
|
+
}
|
|
99276
99283
|
const session = await getOrCreateSession(client, sessionKey, directory);
|
|
99277
99284
|
const parts = await buildPromptParts(feishuClient, messageId, messageType, rawContent, content, chatType, senderId, log);
|
|
99278
99285
|
if (!parts.length) return;
|
|
@@ -99323,24 +99330,41 @@ async function handleChat(ctx, deps) {
|
|
|
99323
99330
|
parts
|
|
99324
99331
|
}
|
|
99325
99332
|
});
|
|
99326
|
-
const
|
|
99327
|
-
|
|
99328
|
-
|
|
99329
|
-
|
|
99330
|
-
|
|
99331
|
-
|
|
99332
|
-
|
|
99333
|
-
|
|
99334
|
-
|
|
99335
|
-
|
|
99336
|
-
|
|
99337
|
-
|
|
99338
|
-
|
|
99333
|
+
const finalText = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query });
|
|
99334
|
+
await replyOrUpdate(feishuClient, chatId, placeholderId, finalText || "\u26A0\uFE0F \u54CD\u5E94\u8D85\u65F6");
|
|
99335
|
+
const { autoPrompt } = config;
|
|
99336
|
+
if (autoPrompt.enabled && shouldReply) {
|
|
99337
|
+
const ac = new AbortController();
|
|
99338
|
+
activeAutoPrompts.set(sessionKey, ac);
|
|
99339
|
+
log("info", "\u542F\u52A8\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF", { sessionKey, maxIterations: autoPrompt.maxIterations });
|
|
99340
|
+
try {
|
|
99341
|
+
for (let i = 0; i < autoPrompt.maxIterations; i++) {
|
|
99342
|
+
await abortableSleep(autoPrompt.intervalSeconds * 1e3, ac.signal);
|
|
99343
|
+
log("info", "\u53D1\u9001\u81EA\u52A8\u63D0\u793A", { sessionKey, iteration: i + 1 });
|
|
99344
|
+
await client.session.prompt({
|
|
99345
|
+
path: { id: session.id },
|
|
99346
|
+
query,
|
|
99347
|
+
body: { parts: [{ type: "text", text: autoPrompt.message }] }
|
|
99348
|
+
});
|
|
99349
|
+
const text2 = await pollForResponse(client, session.id, { timeout, pollInterval, stablePolls, query, signal: ac.signal });
|
|
99350
|
+
if (text2) {
|
|
99351
|
+
await sendTextMessage(feishuClient, chatId, text2);
|
|
99352
|
+
}
|
|
99353
|
+
}
|
|
99354
|
+
log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u7ED3\u675F\uFF08\u8FBE\u5230\u6700\u5927\u6B21\u6570\uFF09", { sessionKey });
|
|
99355
|
+
} catch (err) {
|
|
99356
|
+
if (err.name === "AbortError") {
|
|
99357
|
+
log("info", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u88AB\u4E2D\u65AD", { sessionKey });
|
|
99358
|
+
} else {
|
|
99359
|
+
log("error", "\u81EA\u52A8\u63D0\u793A\u5FAA\u73AF\u5F02\u5E38", {
|
|
99360
|
+
sessionKey,
|
|
99361
|
+
error: err instanceof Error ? err.message : String(err)
|
|
99362
|
+
});
|
|
99363
|
+
}
|
|
99364
|
+
} finally {
|
|
99365
|
+
activeAutoPrompts.delete(sessionKey);
|
|
99339
99366
|
}
|
|
99340
99367
|
}
|
|
99341
|
-
const { data: finalMessages } = await client.session.messages({ path: { id: session.id }, query });
|
|
99342
|
-
const finalText = extractLastAssistantText(finalMessages ?? []) || lastText || (Date.now() - start >= timeout ? "\u26A0\uFE0F \u54CD\u5E94\u8D85\u65F6" : "[\u65E0\u56DE\u590D]");
|
|
99343
|
-
await replyOrUpdate(feishuClient, chatId, placeholderId, finalText);
|
|
99344
99368
|
} catch (err) {
|
|
99345
99369
|
log("error", "\u5BF9\u8BDD\u5904\u7406\u5931\u8D25", {
|
|
99346
99370
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -99367,6 +99391,30 @@ async function buildPromptParts(feishuClient, messageId, messageType, rawContent
|
|
|
99367
99391
|
}
|
|
99368
99392
|
return parts;
|
|
99369
99393
|
}
|
|
99394
|
+
async function pollForResponse(client, sessionId, opts) {
|
|
99395
|
+
const { timeout, pollInterval, stablePolls, query, signal } = opts;
|
|
99396
|
+
const start = Date.now();
|
|
99397
|
+
let lastText = "";
|
|
99398
|
+
let sameCount = 0;
|
|
99399
|
+
while (Date.now() - start < timeout) {
|
|
99400
|
+
if (signal) {
|
|
99401
|
+
await abortableSleep(pollInterval, signal);
|
|
99402
|
+
} else {
|
|
99403
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
99404
|
+
}
|
|
99405
|
+
const { data: messages } = await client.session.messages({ path: { id: sessionId }, query });
|
|
99406
|
+
const text2 = extractLastAssistantText(messages ?? []);
|
|
99407
|
+
if (text2 && text2 !== lastText) {
|
|
99408
|
+
lastText = text2;
|
|
99409
|
+
sameCount = 0;
|
|
99410
|
+
} else if (text2 && text2.length > 0) {
|
|
99411
|
+
sameCount++;
|
|
99412
|
+
if (sameCount >= stablePolls) break;
|
|
99413
|
+
}
|
|
99414
|
+
}
|
|
99415
|
+
const { data: finalMessages } = await client.session.messages({ path: { id: sessionId }, query });
|
|
99416
|
+
return extractLastAssistantText(finalMessages ?? []) || lastText;
|
|
99417
|
+
}
|
|
99370
99418
|
async function replyOrUpdate(feishuClient, chatId, placeholderId, text2) {
|
|
99371
99419
|
if (placeholderId) {
|
|
99372
99420
|
const res = await updateMessage(feishuClient, placeholderId, text2);
|
|
@@ -99377,6 +99425,27 @@ async function replyOrUpdate(feishuClient, chatId, placeholderId, text2) {
|
|
|
99377
99425
|
await sendTextMessage(feishuClient, chatId, text2);
|
|
99378
99426
|
}
|
|
99379
99427
|
}
|
|
99428
|
+
function abortableSleep(ms, signal) {
|
|
99429
|
+
return new Promise((resolve, reject) => {
|
|
99430
|
+
if (signal.aborted) {
|
|
99431
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
99432
|
+
return;
|
|
99433
|
+
}
|
|
99434
|
+
const onDone = () => {
|
|
99435
|
+
clearTimeout(timer);
|
|
99436
|
+
signal.removeEventListener("abort", onAbort);
|
|
99437
|
+
};
|
|
99438
|
+
const onAbort = () => {
|
|
99439
|
+
onDone();
|
|
99440
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
99441
|
+
};
|
|
99442
|
+
const timer = setTimeout(() => {
|
|
99443
|
+
onDone();
|
|
99444
|
+
resolve();
|
|
99445
|
+
}, ms);
|
|
99446
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
99447
|
+
});
|
|
99448
|
+
}
|
|
99380
99449
|
function extractLastAssistantText(messages) {
|
|
99381
99450
|
const assistant = messages.filter((m) => m.info?.role === "assistant").pop();
|
|
99382
99451
|
const parts = assistant?.parts ?? [];
|
|
@@ -99476,7 +99545,13 @@ var DEFAULT_CONFIG = {
|
|
|
99476
99545
|
pollInterval: 1e3,
|
|
99477
99546
|
stablePolls: 3,
|
|
99478
99547
|
dedupTtl: 10 * 60 * 1e3,
|
|
99479
|
-
directory: ""
|
|
99548
|
+
directory: "",
|
|
99549
|
+
autoPrompt: {
|
|
99550
|
+
enabled: false,
|
|
99551
|
+
intervalSeconds: 30,
|
|
99552
|
+
maxIterations: 10,
|
|
99553
|
+
message: "\u8BF7\u540C\u6B65\u5F53\u524D\u8FDB\u5EA6\uFF0C\u5982\u9700\u5E2E\u52A9\u8BF7\u8BF4\u660E"
|
|
99554
|
+
}
|
|
99480
99555
|
};
|
|
99481
99556
|
var FeishuPlugin = async (ctx) => {
|
|
99482
99557
|
const { client } = ctx;
|
|
@@ -99530,7 +99605,11 @@ var FeishuPlugin = async (ctx) => {
|
|
|
99530
99605
|
pollInterval: feishuRaw.pollInterval ?? DEFAULT_CONFIG.pollInterval,
|
|
99531
99606
|
stablePolls: feishuRaw.stablePolls ?? DEFAULT_CONFIG.stablePolls,
|
|
99532
99607
|
dedupTtl: feishuRaw.dedupTtl ?? DEFAULT_CONFIG.dedupTtl,
|
|
99533
|
-
directory: expandDirectoryPath(feishuRaw.directory ?? ctx.directory ?? DEFAULT_CONFIG.directory)
|
|
99608
|
+
directory: expandDirectoryPath(feishuRaw.directory ?? ctx.directory ?? DEFAULT_CONFIG.directory),
|
|
99609
|
+
autoPrompt: {
|
|
99610
|
+
...DEFAULT_CONFIG.autoPrompt,
|
|
99611
|
+
...feishuRaw.autoPrompt ?? {}
|
|
99612
|
+
}
|
|
99534
99613
|
};
|
|
99535
99614
|
initDedup(resolvedConfig.dedupTtl);
|
|
99536
99615
|
const botOpenId = await fetchBotOpenId(resolvedConfig.appId, resolvedConfig.appSecret, log);
|