opencode-feishu-plugin 0.2.12 → 0.2.14
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.en.md +18 -0
- package/README.md +18 -0
- package/dist/index.js +542 -46
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -22,6 +22,7 @@ Connect [OpenCode](https://opencode.ai) to Feishu/Lark: **one Feishu topic = one
|
|
|
22
22
|
| 📊 **Real-time visible** | "Thinking" receipt → live tool-call cards → streaming text updates; footer shows the current model |
|
|
23
23
|
| ⏹ **Controllable** | Every reply card has a "force stop" button; a watchdog auto-interrupts stuck sessions; native queue with `/steer` `/now` to cut in |
|
|
24
24
|
| 📎 **Images / files** | Images and files sent in Feishu are downloaded and attached to the session, so vision/file-capable models can see and read them |
|
|
25
|
+
| 🧭 **One-sentence session** | Send a task in the main chat — the AI judges intent, finds the right working directory, and creates + starts the session in one tap |
|
|
25
26
|
| 🖱 **Main-window menu** | Two fixed quick buttons above the chat input — "New session" and "Session list" (bot custom menu) — one tap to fire them |
|
|
26
27
|
| 🚫 **No ports** | Full long-connection; no inbound port needed on the server |
|
|
27
28
|
|
|
@@ -127,6 +128,22 @@ After configuring the menu (setup step 6), two quick buttons stay pinned **above
|
|
|
127
128
|
- After changing the menu, **re-publish the app version** (per the console: takes effect within ~5 minutes after publishing);
|
|
128
129
|
- Message the bot at least once before using the menu (the plugin remembers the p2p chat from messages; any normal usage satisfies this).
|
|
129
130
|
|
|
131
|
+
### One-sentence session creation (AI finds the directory)
|
|
132
|
+
|
|
133
|
+
When you send plain text in the main chat, the AI first judges the **intent** and picks the best-matching working directory from candidates (recent dirs + local session dirs), then replies with a proposal card:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
You: fix the zlib download bug
|
|
137
|
+
→ 🆕 Suggested new session
|
|
138
|
+
Task: fix download bug | Dir: `/Users/code/zlib`
|
|
139
|
+
[✅ Create & send] [❌ Cancel]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
"Create & send" = create the session + auto-open a topic + send your original message in to start working (permission defaults to "Editable"; adjust later with `/perm`).
|
|
143
|
+
|
|
144
|
+
- Chat/unrecognized/dir-not-in-candidates → falls back to the console hint card, **never creates a wrong session**; the model's path must match a candidate (anti-hallucination);
|
|
145
|
+
- Proposal cards are valid for 1 hour; set `quickNew: false` to go back to the console-only mode.
|
|
146
|
+
|
|
130
147
|
### Inside a topic (work)
|
|
131
148
|
|
|
132
149
|
A topic = a session; sending plain text is giving the AI a command.
|
|
@@ -174,6 +191,7 @@ When the agent asks via `question` or other form tools, the form becomes a Feish
|
|
|
174
191
|
| `logFile` | string \| boolean | — | `true` = write `<configDir>/plugins/feishu.log`; recommended in server mode |
|
|
175
192
|
| `approvalTtlMs` | number | `600000` | Approval token / card validity |
|
|
176
193
|
| `staleExecutionMs` | number | `300000` | Watchdog threshold (0–60 min; **0 = disabled**; sessions waiting on a form / pending approval are never killed) |
|
|
194
|
+
| `quickNew` | boolean | `true` | Main-chat "one-sentence session": AI judges intent + finds the directory, proposal card creates in one tap; `false` = console-only mode |
|
|
177
195
|
| `gatewayLocation` | string | — | Only start the gateway at this location (or its subdirectories); empty = any location |
|
|
178
196
|
|
|
179
197
|
Full config (including `cardMaxTables`, `topicStatus*`, `resumeSummary*`, `keepalive*`, `gatewayMatchGraceMs`) → [docs/advanced.en.md](./docs/advanced.en.md#full-configuration).
|
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
| 📊 **实时可见** | 「思考中」回执 → 工具调用实时上卡 → 文本流式更新,页脚显示当前模型 |
|
|
23
23
|
| ⏹ **可控可停** | 每张回复卡带「强制停止」;看门狗自动中断卡死会话;忙时原生排队,`/steer` `/now` 可插队 |
|
|
24
24
|
| 📎 **图片 / 文件** | 飞书里的图片 / 文件自动下载并挂进会话,支持视觉 / 文件的模型直接看图、读文件 |
|
|
25
|
+
| 🧭 **一句话建会话** | 主聊天流发任务文本,AI 自动判断意图、找好工作目录,一键建会话并开始处理 |
|
|
25
26
|
| 🖱 **主窗口菜单** | 机器人输入框上固定「新建会话」「会话列表」快捷按钮(机器人自定义菜单),一点即用 |
|
|
26
27
|
| 🚫 **无端口** | 全程长连接,服务器无需开放任何入站端口 |
|
|
27
28
|
|
|
@@ -127,6 +128,22 @@ tail -f ~/.config/opencode/plugins/feishu.log # 应看到「飞书长连接已
|
|
|
127
128
|
- 菜单配置修改后需**重新发布版本**(官方说明:发版成功后约 5 分钟内生效);
|
|
128
129
|
- 首次使用菜单前,先给机器人发过至少一条消息(插件据消息记住单聊会话;正常使用必然满足)。
|
|
129
130
|
|
|
131
|
+
### 一句话建会话(AI 找目录)
|
|
132
|
+
|
|
133
|
+
主聊天流发普通文本时,AI 会先判断**意图**,并从候选目录(最近使用 + 本机会话目录)里挑出最匹配的工作目录,回一张建议卡:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
你:帮我修一下 zlib 的下载 bug
|
|
137
|
+
→ 🆕 建议新建会话
|
|
138
|
+
任务:修下载 bug | 目录:`/Users/code/zlib`
|
|
139
|
+
[✅ 创建并发送] [❌ 取消]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
点「创建并发送」= 建会话 + 自动开话题 + 把原消息发进去开始处理(权限默认「可编辑」,进话题后可用 `/perm` 调整)。
|
|
143
|
+
|
|
144
|
+
- 闲聊 / 无法识别 / 目录不在候选中 → 回退为管理台提示卡,**不会误建会话**;模型给出的路径必须命中候选清单(防幻觉);
|
|
145
|
+
- 建议卡有效期 1 小时;不想要此功能可设 `quickNew: false` 回到纯管理台模式。
|
|
146
|
+
|
|
130
147
|
### 话题内(干活)
|
|
131
148
|
|
|
132
149
|
一个话题 = 一个会话,发普通文本就是给 AI 下指令。
|
|
@@ -174,6 +191,7 @@ agent 反问时表单会变成飞书卡片,**点按钮或在话题里直接发
|
|
|
174
191
|
| `logFile` | string \| boolean | — | `true` = 写 `<configDir>/plugins/feishu.log`,服务模式建议开启 |
|
|
175
192
|
| `approvalTtlMs` | number | `600000` | 审批 token / 卡片有效期 |
|
|
176
193
|
| `staleExecutionMs` | number | `300000` | 看门狗阈值(0–60 分钟;**0 = 关闭看门狗**;待答表单 / 未决审批期间不判卡死) |
|
|
194
|
+
| `quickNew` | boolean | `true` | 主聊天流「一句话建会话」:AI 判断意图 + 找目录,建议卡一键创建;`false` = 回到纯管理台模式 |
|
|
177
195
|
| `gatewayLocation` | string | — | 只在该 location(及其子目录)启动网关;留空 = 任意 location 生效 |
|
|
178
196
|
|
|
179
197
|
完整配置(含 `cardMaxTables`、`topicStatus*`、`resumeSummary*`、`keepalive*`、`gatewayMatchGraceMs` 等进阶项)见 [docs/advanced.md](./docs/advanced.md#完整配置项)。
|
package/dist/index.js
CHANGED
|
@@ -144037,6 +144037,7 @@ function resolveConfig2(raw, env = process.env, deps = {}) {
|
|
|
144037
144037
|
);
|
|
144038
144038
|
const attachmentTimeoutMs = clamp4(asNumber(merged.attachmentTimeoutMs, 3e4), 5e3, 12e4);
|
|
144039
144039
|
const attachmentsDirRaw = asString(merged.attachmentsDir).trim();
|
|
144040
|
+
const quickNew = asBoolean(merged.quickNew, true);
|
|
144040
144041
|
const keepaliveIntervalMs = clamp4(
|
|
144041
144042
|
asNumber(merged.keepaliveIntervalMs, 20 * 60 * 1e3),
|
|
144042
144043
|
5 * 60 * 1e3,
|
|
@@ -144101,7 +144102,8 @@ function resolveConfig2(raw, env = process.env, deps = {}) {
|
|
|
144101
144102
|
acceptAttachments,
|
|
144102
144103
|
attachmentMaxBytes,
|
|
144103
144104
|
attachmentTimeoutMs,
|
|
144104
|
-
...attachmentsDirRaw ? { attachmentsDir: attachmentsDirRaw } : {}
|
|
144105
|
+
...attachmentsDirRaw ? { attachmentsDir: attachmentsDirRaw } : {},
|
|
144106
|
+
quickNew
|
|
144105
144107
|
};
|
|
144106
144108
|
}
|
|
144107
144109
|
function loadConfigFile(env, deps, warnings) {
|
|
@@ -146078,6 +146080,14 @@ function withAssistantID(state, assistantMessageID) {
|
|
|
146078
146080
|
if (!assistantMessageID || assistantMessageID === state.assistantMessageID) return state;
|
|
146079
146081
|
return { ...state, assistantMessageID };
|
|
146080
146082
|
}
|
|
146083
|
+
function lastPrefixTextIndex(blocks, text) {
|
|
146084
|
+
for (let i = blocks.length - 1; i >= 0; i -= 1) {
|
|
146085
|
+
const block = blocks[i];
|
|
146086
|
+
if (block.kind !== "text") continue;
|
|
146087
|
+
if (block.content && text.startsWith(block.content)) return i;
|
|
146088
|
+
}
|
|
146089
|
+
return -1;
|
|
146090
|
+
}
|
|
146081
146091
|
function patchTool(state, id2, update2) {
|
|
146082
146092
|
let found = false;
|
|
146083
146093
|
const blocks = state.blocks.map((b) => {
|
|
@@ -146118,21 +146128,62 @@ function reduce6(state, event) {
|
|
|
146118
146128
|
if (!event.delta) return state;
|
|
146119
146129
|
const base2 = stepTransition(state, event.assistantMessageID);
|
|
146120
146130
|
const last = base2[base2.length - 1];
|
|
146121
|
-
const blocks = last && last.kind === "text" && last.streaming ? [...base2.slice(0, -1), { ...last, content: last.content + event.delta }] : [...base2, { kind: "text", content: event.delta, streaming: true }];
|
|
146131
|
+
const blocks = last && last.kind === "text" && last.streaming ? [...base2.slice(0, -1), { ...last, content: last.content + event.delta, ...event.assistantMessageID ? { msg: event.assistantMessageID } : {} }] : [...base2, { kind: "text", content: event.delta, streaming: true, ...event.assistantMessageID ? { msg: event.assistantMessageID } : {} }];
|
|
146122
146132
|
const next = withAssistantID(state, event.assistantMessageID);
|
|
146123
146133
|
return { ...next, blocks, footer: "streaming", terminal: "running" };
|
|
146124
146134
|
}
|
|
146125
146135
|
case "text.ended": {
|
|
146126
146136
|
const blocks = state.blocks;
|
|
146127
|
-
const
|
|
146128
|
-
|
|
146129
|
-
if (
|
|
146130
|
-
|
|
146131
|
-
|
|
146132
|
-
|
|
146133
|
-
|
|
146134
|
-
|
|
146137
|
+
const endedText = event.text;
|
|
146138
|
+
const endedMsg = event.assistantMessageID;
|
|
146139
|
+
if (!endedMsg) {
|
|
146140
|
+
const last = blocks[blocks.length - 1];
|
|
146141
|
+
let nextBlocks2;
|
|
146142
|
+
if (last && last.kind === "text" && last.streaming) {
|
|
146143
|
+
nextBlocks2 = [...blocks.slice(0, -1), { ...last, content: endedText ?? last.content, streaming: false }];
|
|
146144
|
+
} else if (endedText) {
|
|
146145
|
+
const idx = lastPrefixTextIndex(blocks, endedText);
|
|
146146
|
+
nextBlocks2 = idx >= 0 ? blocks.map((b, i) => i === idx && b.kind === "text" ? { ...b, content: endedText, streaming: false } : b) : [...closeStreamingText(blocks), { kind: "text", content: endedText, streaming: false }];
|
|
146147
|
+
} else {
|
|
146148
|
+
nextBlocks2 = closeStreamingText(blocks);
|
|
146149
|
+
}
|
|
146150
|
+
return { ...state, blocks: nextBlocks2, footer: "streaming" };
|
|
146135
146151
|
}
|
|
146152
|
+
let targetIdx = -1;
|
|
146153
|
+
for (let i = blocks.length - 1; i >= 0; i -= 1) {
|
|
146154
|
+
const b = blocks[i];
|
|
146155
|
+
if (b.kind === "text" && b.msg === endedMsg) {
|
|
146156
|
+
targetIdx = i;
|
|
146157
|
+
break;
|
|
146158
|
+
}
|
|
146159
|
+
}
|
|
146160
|
+
const tail = blocks[blocks.length - 1];
|
|
146161
|
+
if (targetIdx < 0 && tail && tail.kind === "text" && tail.streaming) targetIdx = blocks.length - 1;
|
|
146162
|
+
if (targetIdx < 0) {
|
|
146163
|
+
if (!endedText) return { ...state, blocks: closeStreamingText(blocks), footer: "streaming" };
|
|
146164
|
+
return {
|
|
146165
|
+
...state,
|
|
146166
|
+
blocks: [
|
|
146167
|
+
...closeStreamingText(blocks),
|
|
146168
|
+
{ kind: "text", content: endedText, streaming: false, msg: endedMsg }
|
|
146169
|
+
],
|
|
146170
|
+
footer: "streaming"
|
|
146171
|
+
};
|
|
146172
|
+
}
|
|
146173
|
+
const target = blocks[targetIdx];
|
|
146174
|
+
let earlierLen = 0;
|
|
146175
|
+
for (let i = 0; i < targetIdx; i += 1) {
|
|
146176
|
+
const b = blocks[i];
|
|
146177
|
+
if (b.kind === "text" && b.msg === endedMsg) earlierLen += b.content.length;
|
|
146178
|
+
}
|
|
146179
|
+
let content = target.kind === "text" ? target.content : "";
|
|
146180
|
+
if (endedText !== void 0 && endedText.length >= earlierLen) {
|
|
146181
|
+
const suffix = endedText.slice(earlierLen);
|
|
146182
|
+
if (suffix.length >= content.length) content = suffix;
|
|
146183
|
+
}
|
|
146184
|
+
const nextBlocks = blocks.map(
|
|
146185
|
+
(b, i) => i === targetIdx && b.kind === "text" ? { ...b, content, streaming: false } : b
|
|
146186
|
+
);
|
|
146136
146187
|
return { ...state, blocks: nextBlocks, footer: "streaming" };
|
|
146137
146188
|
}
|
|
146138
146189
|
case "tool.input.started": {
|
|
@@ -146625,13 +146676,49 @@ function trailingText(blocks) {
|
|
|
146625
146676
|
}
|
|
146626
146677
|
return parts.join("\n\n").trim();
|
|
146627
146678
|
}
|
|
146679
|
+
function isActivityEvent(type) {
|
|
146680
|
+
return type === "text.started" || type === "text.delta" || type === "text.ended" || type === "tool.input.started" || type === "tool.input.ended" || type === "tool.success" || type === "tool.error";
|
|
146681
|
+
}
|
|
146682
|
+
var QUEUED_DRAIN_GRACE_MS = 3e3;
|
|
146628
146683
|
function createRunController(deps) {
|
|
146629
146684
|
const sessions = /* @__PURE__ */ new Map();
|
|
146630
146685
|
const throttlers = /* @__PURE__ */ new Map();
|
|
146631
146686
|
const sessionModels = /* @__PURE__ */ new Map();
|
|
146632
146687
|
const notifiedQueued = /* @__PURE__ */ new Set();
|
|
146688
|
+
const drainTimers = /* @__PURE__ */ new Map();
|
|
146633
146689
|
const throttleMs = Math.max(400, deps.throttleMs);
|
|
146634
146690
|
let disposed = false;
|
|
146691
|
+
const cancelDrain = (sessionID) => {
|
|
146692
|
+
const timer = drainTimers.get(sessionID);
|
|
146693
|
+
if (timer) {
|
|
146694
|
+
clearTimeout(timer);
|
|
146695
|
+
drainTimers.delete(sessionID);
|
|
146696
|
+
}
|
|
146697
|
+
};
|
|
146698
|
+
const scheduleDrain = (sessionID, runs) => {
|
|
146699
|
+
if (runs.queued.length === 0) {
|
|
146700
|
+
cancelDrain(sessionID);
|
|
146701
|
+
return;
|
|
146702
|
+
}
|
|
146703
|
+
cancelDrain(sessionID);
|
|
146704
|
+
const timer = setTimeout(() => {
|
|
146705
|
+
drainTimers.delete(sessionID);
|
|
146706
|
+
if (disposed) return;
|
|
146707
|
+
const live = sessions.get(sessionID);
|
|
146708
|
+
if (!live || live.queued.length === 0) return;
|
|
146709
|
+
deps.log.info("\u6392\u961F\u5361\u6309\u300C\u5DF2\u968F\u672C\u8F6E\u5904\u7406\u300D\u6536\u5C3E\uFF08\u961F\u5217\u6D88\u606F\u5728\u5F53\u524D\u6267\u884C\u5185\u88AB\u6D88\u8D39\uFF09", {
|
|
146710
|
+
sessionID,
|
|
146711
|
+
count: live.queued.length
|
|
146712
|
+
});
|
|
146713
|
+
for (const card of live.queued) {
|
|
146714
|
+
card.state = reduce6(card.state, { type: "execution.succeeded" });
|
|
146715
|
+
finalize2(card);
|
|
146716
|
+
}
|
|
146717
|
+
live.queued.length = 0;
|
|
146718
|
+
}, QUEUED_DRAIN_GRACE_MS);
|
|
146719
|
+
timer.unref?.();
|
|
146720
|
+
drainTimers.set(sessionID, timer);
|
|
146721
|
+
};
|
|
146635
146722
|
const sessionRuns = (sessionID) => {
|
|
146636
146723
|
let runs = sessions.get(sessionID);
|
|
146637
146724
|
if (!runs) {
|
|
@@ -146739,6 +146826,7 @@ function createRunController(deps) {
|
|
|
146739
146826
|
if (!runs) return;
|
|
146740
146827
|
if (event.type === "execution.started") {
|
|
146741
146828
|
notifiedQueued.delete(sessionID);
|
|
146829
|
+
cancelDrain(sessionID);
|
|
146742
146830
|
if (runs.active) {
|
|
146743
146831
|
update2(runs.active, event, true);
|
|
146744
146832
|
return;
|
|
@@ -146746,41 +146834,62 @@ function createRunController(deps) {
|
|
|
146746
146834
|
const next = runs.queued.shift();
|
|
146747
146835
|
if (next) {
|
|
146748
146836
|
runs.active = next;
|
|
146837
|
+
deps.log.debug("\u6392\u961F\u5361\u664B\u5347\u4E3A\u8FD0\u884C\u5361", { sessionID, runID: next.runID });
|
|
146749
146838
|
update2(next, event, true);
|
|
146750
146839
|
}
|
|
146840
|
+
if (runs.queued.length > 0) scheduleDrain(sessionID, runs);
|
|
146751
146841
|
return;
|
|
146752
146842
|
}
|
|
146843
|
+
if (!runs.active && runs.queued.length > 0 && isActivityEvent(event.type)) {
|
|
146844
|
+
const next = runs.queued.shift();
|
|
146845
|
+
runs.active = next;
|
|
146846
|
+
deps.log.info("\u6536\u5230\u6D3B\u52A8\u4E8B\u4EF6\u4F46\u65E0\u8FD0\u884C\u5361\uFF1A\u664B\u5347\u961F\u9996\u6392\u961F\u5361", {
|
|
146847
|
+
sessionID,
|
|
146848
|
+
runID: next.runID,
|
|
146849
|
+
event: event.type
|
|
146850
|
+
});
|
|
146851
|
+
next.state = reduce6(next.state, { type: "execution.started" });
|
|
146852
|
+
}
|
|
146753
146853
|
const card = runs.active;
|
|
146754
|
-
if (
|
|
146755
|
-
|
|
146756
|
-
|
|
146757
|
-
|
|
146758
|
-
|
|
146759
|
-
|
|
146854
|
+
if (event.type === "execution.succeeded" || event.type === "execution.failed") {
|
|
146855
|
+
if (card) {
|
|
146856
|
+
if (event.type === "execution.succeeded") {
|
|
146857
|
+
const text = trailingText(card.state.blocks);
|
|
146858
|
+
const long = deps.sendFinalAnswer !== void 0 && deps.finalAnswer !== void 0 && text.length >= deps.finalAnswer.minChars;
|
|
146859
|
+
if (long) {
|
|
146860
|
+
card.state = reduce6(reduce6(card.state, { type: "final.separated" }), event);
|
|
146861
|
+
finalize2(card);
|
|
146862
|
+
runs.active = void 0;
|
|
146863
|
+
void deps.sendFinalAnswer({
|
|
146864
|
+
sessionID,
|
|
146865
|
+
chatId: card.chatId,
|
|
146866
|
+
...card.replyToMessageId ? { replyToMessageId: card.replyToMessageId } : {},
|
|
146867
|
+
text
|
|
146868
|
+
}).catch((err2) => {
|
|
146869
|
+
deps.log.warn("\u6700\u7EC8\u7B54\u6848\u53D1\u9001\u5931\u8D25", { sessionID, error: errorMessage(err2) });
|
|
146870
|
+
});
|
|
146871
|
+
scheduleDrain(sessionID, runs);
|
|
146872
|
+
return;
|
|
146873
|
+
}
|
|
146874
|
+
}
|
|
146875
|
+
update2(card, event, true);
|
|
146760
146876
|
finalize2(card);
|
|
146761
146877
|
runs.active = void 0;
|
|
146762
|
-
void deps.sendFinalAnswer({
|
|
146763
|
-
sessionID,
|
|
146764
|
-
chatId: card.chatId,
|
|
146765
|
-
...card.replyToMessageId ? { replyToMessageId: card.replyToMessageId } : {},
|
|
146766
|
-
text
|
|
146767
|
-
}).catch((err2) => {
|
|
146768
|
-
deps.log.warn("\u6700\u7EC8\u7B54\u6848\u53D1\u9001\u5931\u8D25", { sessionID, error: errorMessage(err2) });
|
|
146769
|
-
});
|
|
146770
|
-
return;
|
|
146771
146878
|
}
|
|
146772
|
-
|
|
146773
|
-
finalize2(card);
|
|
146774
|
-
runs.active = void 0;
|
|
146775
|
-
return;
|
|
146776
|
-
}
|
|
146777
|
-
if (event.type === "execution.failed") {
|
|
146778
|
-
update2(card, event, true);
|
|
146779
|
-
finalize2(card);
|
|
146780
|
-
runs.active = void 0;
|
|
146879
|
+
scheduleDrain(sessionID, runs);
|
|
146781
146880
|
return;
|
|
146782
146881
|
}
|
|
146882
|
+
if (!card) return;
|
|
146783
146883
|
update2(card, event, false);
|
|
146884
|
+
if (isActivityEvent(event.type)) {
|
|
146885
|
+
deps.log.debug("\u8FD0\u884C\u5361\u5757\u7ED3\u6784", {
|
|
146886
|
+
sessionID,
|
|
146887
|
+
event: event.type,
|
|
146888
|
+
blocks: card.state.blocks.map(
|
|
146889
|
+
(b) => b.kind === "text" ? `T${b.streaming ? "*" : ""}${b.content.length}` : `tool:${b.tool.name}`
|
|
146890
|
+
)
|
|
146891
|
+
});
|
|
146892
|
+
}
|
|
146784
146893
|
},
|
|
146785
146894
|
hasActive(sessionID) {
|
|
146786
146895
|
return sessions.get(sessionID)?.active !== void 0;
|
|
@@ -146818,6 +146927,8 @@ function createRunController(deps) {
|
|
|
146818
146927
|
disposed = true;
|
|
146819
146928
|
sessionModels.clear();
|
|
146820
146929
|
notifiedQueued.clear();
|
|
146930
|
+
for (const timer of drainTimers.values()) clearTimeout(timer);
|
|
146931
|
+
drainTimers.clear();
|
|
146821
146932
|
for (const throttler of throttlers.values()) throttler.cancel();
|
|
146822
146933
|
throttlers.clear();
|
|
146823
146934
|
for (const runs of sessions.values()) {
|
|
@@ -148451,6 +148562,128 @@ function parsePage(value3) {
|
|
|
148451
148562
|
const parsed = typeof value3 === "number" ? value3 : typeof value3 === "string" ? Number.parseInt(value3, 10) : Number.NaN;
|
|
148452
148563
|
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0;
|
|
148453
148564
|
}
|
|
148565
|
+
|
|
148566
|
+
// src/feishu/quick-new-cards.ts
|
|
148567
|
+
function button3(text, type, value3) {
|
|
148568
|
+
return {
|
|
148569
|
+
tag: "button",
|
|
148570
|
+
text: { tag: "plain_text", content: text },
|
|
148571
|
+
type,
|
|
148572
|
+
behaviors: [{ type: "callback", value: value3 }]
|
|
148573
|
+
};
|
|
148574
|
+
}
|
|
148575
|
+
function buildQuickNewThinkingCard() {
|
|
148576
|
+
return {
|
|
148577
|
+
schema: "2.0",
|
|
148578
|
+
config: { update_multi: true },
|
|
148579
|
+
header: { title: { tag: "plain_text", content: "\u{1F914} \u6B63\u5728\u8BC6\u522B\u610F\u56FE\u4E0E\u76EE\u5F55\u2026" }, template: "blue" },
|
|
148580
|
+
body: {
|
|
148581
|
+
elements: [
|
|
148582
|
+
{ tag: "markdown", content: "\u6B63\u5728\u5224\u65AD\u8FD9\u6761\u6D88\u606F\u662F\u5426\u9700\u8981\u65B0\u5EFA\u4F1A\u8BDD\uFF0C\u4EE5\u53CA\u6700\u5408\u9002\u7684\u5DE5\u4F5C\u76EE\u5F55\u2026" }
|
|
148583
|
+
]
|
|
148584
|
+
}
|
|
148585
|
+
};
|
|
148586
|
+
}
|
|
148587
|
+
function buildQuickNewProposalCard(input) {
|
|
148588
|
+
const preview = input.textPreview.trim().replace(/\n+/g, " ").slice(0, 120);
|
|
148589
|
+
const lines = [
|
|
148590
|
+
`**\u4EFB\u52A1**\uFF1A${input.title}`,
|
|
148591
|
+
`**\u76EE\u5F55**\uFF1A\`${input.directory}\``,
|
|
148592
|
+
...input.reason ? [`**\u4F9D\u636E**\uFF1A${input.reason}`] : [],
|
|
148593
|
+
"",
|
|
148594
|
+
`\u539F\u6D88\u606F\uFF1A${preview}`
|
|
148595
|
+
];
|
|
148596
|
+
const elements = [
|
|
148597
|
+
{ tag: "markdown", content: truncateCardContent(lines.join("\n")) },
|
|
148598
|
+
{
|
|
148599
|
+
tag: "markdown",
|
|
148600
|
+
content: "\u70B9\u300C\u521B\u5EFA\u5E76\u53D1\u9001\u300D= \u5EFA\u4F1A\u8BDD + \u81EA\u52A8\u5F00\u8BDD\u9898 + \u628A\u539F\u6D88\u606F\u53D1\u8FDB\u53BB\u5F00\u59CB\u5904\u7406\u3002",
|
|
148601
|
+
text_size: "notation"
|
|
148602
|
+
},
|
|
148603
|
+
button3("\u2705 \u521B\u5EFA\u5E76\u53D1\u9001", "primary", { cmd: "quicknew", op: "create", id: input.id }),
|
|
148604
|
+
button3("\u274C \u53D6\u6D88", "default", { cmd: "quicknew", op: "cancel", id: input.id })
|
|
148605
|
+
];
|
|
148606
|
+
return {
|
|
148607
|
+
schema: "2.0",
|
|
148608
|
+
config: { update_multi: true },
|
|
148609
|
+
header: { title: { tag: "plain_text", content: "\u{1F195} \u5EFA\u8BAE\u65B0\u5EFA\u4F1A\u8BDD" }, template: "turquoise" },
|
|
148610
|
+
body: { elements }
|
|
148611
|
+
};
|
|
148612
|
+
}
|
|
148613
|
+
function buildQuickNewResolvedCard(title, note2, template2 = "grey") {
|
|
148614
|
+
return {
|
|
148615
|
+
schema: "2.0",
|
|
148616
|
+
config: { update_multi: true },
|
|
148617
|
+
header: { title: { tag: "plain_text", content: title }, template: template2 },
|
|
148618
|
+
body: { elements: [{ tag: "markdown", content: truncateCardContent(note2) }] }
|
|
148619
|
+
};
|
|
148620
|
+
}
|
|
148621
|
+
function parseQuickNewActionValue(raw) {
|
|
148622
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
148623
|
+
const value3 = raw;
|
|
148624
|
+
if (value3.cmd !== "quicknew") return void 0;
|
|
148625
|
+
const op = value3.op === "create" || value3.op === "cancel" ? value3.op : void 0;
|
|
148626
|
+
const id2 = typeof value3.id === "string" ? value3.id.trim() : "";
|
|
148627
|
+
if (!op || !id2) return void 0;
|
|
148628
|
+
return { op, id: id2 };
|
|
148629
|
+
}
|
|
148630
|
+
|
|
148631
|
+
// src/session/quick-new.ts
|
|
148632
|
+
var QUICK_NEW_INSTRUCTION = [
|
|
148633
|
+
"\u4F60\u662F\u98DE\u4E66 AI \u52A9\u624B\u300C\u7BA1\u7406\u53F0\u300D\u7684\u610F\u56FE\u8BC6\u522B\u5668\u3002\u7528\u6237\u5728\u7BA1\u7406\u53F0\uFF08\u8FD8\u6CA1\u6709\u4F1A\u8BDD\uFF09\u53D1\u6765\u4E00\u6761\u6D88\u606F\u3002",
|
|
148634
|
+
"\u8BF7\u5224\u65AD\u5B83\u662F\u4E0D\u662F\u4E00\u4E2A\u9700\u8981\u65B0\u5EFA\u4F1A\u8BDD\u53BB\u6267\u884C\u7684\u5F00\u53D1/\u64CD\u4F5C\u4EFB\u52A1\uFF0C\u5E76\u4ECE\u5019\u9009\u76EE\u5F55\u4E2D\u9009\u51FA\u6700\u5408\u9002\u7684\u5DE5\u4F5C\u76EE\u5F55\u3002",
|
|
148635
|
+
"\u53EA\u8F93\u51FA\u4E00\u4E2A JSON \u5BF9\u8C61\uFF0C\u4E0D\u8981\u4EFB\u4F55\u5176\u4ED6\u6587\u5B57\uFF1A",
|
|
148636
|
+
'{"intent":"task|chat","dir":"<\u7EDD\u5BF9\u8DEF\u5F84\u6216\u7A7A\u5B57\u7B26\u4E32>","title":"<\u4E0D\u8D85\u8FC720\u5B57\u7684\u4F1A\u8BDD\u6807\u9898>","reason":"<\u4E00\u53E5\u8BDD\u7406\u7531>"}',
|
|
148637
|
+
"\u89C4\u5219\uFF1A",
|
|
148638
|
+
"- \u95F2\u804A\u3001\u95EE\u5019\u3001\u8BE2\u95EE\u7528\u6CD5 \u2192 intent=chat\uFF0Cdir \u7559\u7A7A\u3002",
|
|
148639
|
+
"- \u660E\u786E\u7684\u5F00\u53D1/\u64CD\u4F5C\u4EFB\u52A1\uFF08\u6539 bug\u3001\u5199\u4EE3\u7801\u3001\u6574\u7406\u6587\u4EF6\u3001\u8DD1\u811A\u672C\u7B49\uFF09\u2192 intent=task\u3002",
|
|
148640
|
+
"- dir \u53EA\u80FD\u4ECE\u5019\u9009\u76EE\u5F55\u4E2D\u9009\u62E9\uFF08\u8BED\u4E49\u6700\u5339\u914D\u7684\u90A3\u4E2A\uFF09\uFF1B\u627E\u4E0D\u5230\u5408\u9002\u7684\u5C31\u7559\u7A7A\u5B57\u7B26\u4E32\u3002"
|
|
148641
|
+
].join("\n");
|
|
148642
|
+
function buildQuickNewPrompt(text, candidates) {
|
|
148643
|
+
const list = candidates.slice(0, 40).map((c) => `- ${c.path}${c.label ? `\uFF08${c.label}\uFF09` : ""}`).join("\n");
|
|
148644
|
+
return [
|
|
148645
|
+
QUICK_NEW_INSTRUCTION,
|
|
148646
|
+
"",
|
|
148647
|
+
"\u5019\u9009\u76EE\u5F55\uFF1A",
|
|
148648
|
+
list || "\uFF08\u65E0\uFF09",
|
|
148649
|
+
"",
|
|
148650
|
+
"\u7528\u6237\u6D88\u606F\uFF1A",
|
|
148651
|
+
text.slice(0, 2e3)
|
|
148652
|
+
].join("\n");
|
|
148653
|
+
}
|
|
148654
|
+
function parseQuickNewDecision(raw) {
|
|
148655
|
+
if (!raw) return void 0;
|
|
148656
|
+
const text = raw.trim();
|
|
148657
|
+
const start2 = text.indexOf("{");
|
|
148658
|
+
const end = text.lastIndexOf("}");
|
|
148659
|
+
if (start2 < 0 || end <= start2) return void 0;
|
|
148660
|
+
let parsed;
|
|
148661
|
+
try {
|
|
148662
|
+
parsed = JSON.parse(text.slice(start2, end + 1));
|
|
148663
|
+
} catch {
|
|
148664
|
+
return void 0;
|
|
148665
|
+
}
|
|
148666
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
|
|
148667
|
+
const obj = parsed;
|
|
148668
|
+
const intent = obj.intent === "task" ? "task" : obj.intent === "chat" ? "chat" : void 0;
|
|
148669
|
+
if (!intent) return void 0;
|
|
148670
|
+
const dir = typeof obj.dir === "string" ? obj.dir.trim() : typeof obj.directory === "string" ? obj.directory.trim() : "";
|
|
148671
|
+
const title = typeof obj.title === "string" ? obj.title.trim().slice(0, 30) : "";
|
|
148672
|
+
const reason = typeof obj.reason === "string" ? obj.reason.trim().slice(0, 200) : "";
|
|
148673
|
+
return {
|
|
148674
|
+
intent,
|
|
148675
|
+
...dir ? { directory: dir } : {},
|
|
148676
|
+
...title ? { title } : {},
|
|
148677
|
+
...reason ? { reason } : {}
|
|
148678
|
+
};
|
|
148679
|
+
}
|
|
148680
|
+
function matchCandidateDirectory(directory, candidates) {
|
|
148681
|
+
if (!directory) return void 0;
|
|
148682
|
+
const normalize2 = (p) => p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
|
|
148683
|
+
const target = normalize2(directory);
|
|
148684
|
+
const hit = candidates.find((c) => normalize2(c.path) === target);
|
|
148685
|
+
return hit?.path;
|
|
148686
|
+
}
|
|
148454
148687
|
var SYSTEM_DIRS = [
|
|
148455
148688
|
"/etc",
|
|
148456
148689
|
"/usr",
|
|
@@ -149202,7 +149435,7 @@ var SETUP_FORM_FIELDS = {
|
|
|
149202
149435
|
var SETUP_FORM_MAX_MODELS = 15;
|
|
149203
149436
|
var SETUP_FORM_DEFAULT_PERM = "edit";
|
|
149204
149437
|
var SETUP_FORM_DIR_CUSTOM = "__custom__";
|
|
149205
|
-
function
|
|
149438
|
+
function button4(text, type, value3) {
|
|
149206
149439
|
return {
|
|
149207
149440
|
tag: "button",
|
|
149208
149441
|
text: { tag: "plain_text", content: text },
|
|
@@ -149245,28 +149478,28 @@ function buildModelCard(input) {
|
|
|
149245
149478
|
if (input.page <= 0 && recent.length > 0) {
|
|
149246
149479
|
for (const model of recent) {
|
|
149247
149480
|
const isCurrent = input.current && refKey(input.current) === refKey(model);
|
|
149248
|
-
elements.push(
|
|
149481
|
+
elements.push(button4(`\u{1F9E0} ${shorten(modelLabel(model), 30)}`, isCurrent ? "primary" : "default", modelValue(model, input.sid)));
|
|
149249
149482
|
}
|
|
149250
149483
|
if (input.models.length > recent.length) {
|
|
149251
|
-
elements.push(
|
|
149484
|
+
elements.push(button4("\u66F4\u591A \u25B8", "default", moreValue(1, input.sid)));
|
|
149252
149485
|
}
|
|
149253
149486
|
} else {
|
|
149254
149487
|
const pageNum = input.page <= 0 ? 1 : input.page;
|
|
149255
149488
|
const slice = input.models.slice((pageNum - 1) * pageSize, pageNum * pageSize);
|
|
149256
149489
|
if (slice.length === 0 && recent.length > 0) {
|
|
149257
149490
|
for (const model of recent) {
|
|
149258
|
-
elements.push(
|
|
149491
|
+
elements.push(button4(`\u{1F9E0} ${shorten(modelLabel(model), 30)}`, "default", modelValue(model, input.sid)));
|
|
149259
149492
|
}
|
|
149260
149493
|
} else {
|
|
149261
149494
|
for (const model of slice) {
|
|
149262
149495
|
const isCurrent = input.current && refKey(input.current) === refKey(model);
|
|
149263
149496
|
elements.push(
|
|
149264
|
-
|
|
149497
|
+
button4(`\u{1F9E0} ${shorten(modelLabel(model), 30)}`, isCurrent ? "primary" : "default", modelValue(model, input.sid))
|
|
149265
149498
|
);
|
|
149266
149499
|
}
|
|
149267
|
-
if (pageNum > 1) elements.push(
|
|
149500
|
+
if (pageNum > 1) elements.push(button4("\u25C2 \u4E0A\u4E00\u9875", "default", moreValue(pageNum - 1, input.sid)));
|
|
149268
149501
|
if (pageNum * pageSize < input.models.length) {
|
|
149269
|
-
elements.push(
|
|
149502
|
+
elements.push(button4("\u4E0B\u4E00\u9875 \u25B8", "default", moreValue(pageNum + 1, input.sid)));
|
|
149270
149503
|
}
|
|
149271
149504
|
}
|
|
149272
149505
|
}
|
|
@@ -149280,7 +149513,7 @@ function buildPermCard(input) {
|
|
|
149280
149513
|
}
|
|
149281
149514
|
const elements = [{ tag: "markdown", content: truncateCardContent(lines.join("\n")) }];
|
|
149282
149515
|
for (const info of PERMISSION_PRESETS) {
|
|
149283
|
-
elements.push(
|
|
149516
|
+
elements.push(button4(presetLabel(info.id), input.current === info.id ? "primary" : "default", permValue(info.id, input.sid)));
|
|
149284
149517
|
}
|
|
149285
149518
|
return headerCard("\u{1F510} \u9009\u62E9\u6743\u9650\u6863\u4F4D", "orange", elements);
|
|
149286
149519
|
}
|
|
@@ -149296,8 +149529,8 @@ function buildConfirmCard(input) {
|
|
|
149296
149529
|
];
|
|
149297
149530
|
return headerCard("\u2705 \u786E\u8BA4\u521B\u5EFA\u4F1A\u8BDD", "green", [
|
|
149298
149531
|
{ tag: "markdown", content: truncateCardContent(lines.join("\n")) },
|
|
149299
|
-
|
|
149300
|
-
|
|
149532
|
+
button4("\u2705 \u521B\u5EFA", "primary", { wizard: "confirm" }),
|
|
149533
|
+
button4("\u2716\uFE0F \u53D6\u6D88", "default", { wizard: "cancel" })
|
|
149301
149534
|
]);
|
|
149302
149535
|
}
|
|
149303
149536
|
function buildSetupDoneCard(title, lines, template2 = "grey") {
|
|
@@ -150857,6 +151090,7 @@ async function routeEvent(event, deps) {
|
|
|
150857
151090
|
break;
|
|
150858
151091
|
case "session.text.started": {
|
|
150859
151092
|
const data = event.data;
|
|
151093
|
+
deps.log.debug("text.started", { sessionID: data.sessionID, msg: data.assistantMessageID });
|
|
150860
151094
|
deps.applyRun(data.sessionID, {
|
|
150861
151095
|
type: "text.started",
|
|
150862
151096
|
...data.assistantMessageID ? { assistantMessageID: data.assistantMessageID } : {}
|
|
@@ -150865,6 +151099,11 @@ async function routeEvent(event, deps) {
|
|
|
150865
151099
|
}
|
|
150866
151100
|
case "session.text.delta": {
|
|
150867
151101
|
const data = event.data;
|
|
151102
|
+
deps.log.debug("text.delta", {
|
|
151103
|
+
sessionID: data.sessionID,
|
|
151104
|
+
msg: data.assistantMessageID,
|
|
151105
|
+
len: typeof data.delta === "string" ? data.delta.length : 0
|
|
151106
|
+
});
|
|
150868
151107
|
deps.applyRun(data.sessionID, {
|
|
150869
151108
|
type: "text.delta",
|
|
150870
151109
|
delta: data.delta,
|
|
@@ -150874,6 +151113,12 @@ async function routeEvent(event, deps) {
|
|
|
150874
151113
|
}
|
|
150875
151114
|
case "session.text.ended": {
|
|
150876
151115
|
const data = event.data;
|
|
151116
|
+
deps.log.debug("text.ended", {
|
|
151117
|
+
sessionID: data.sessionID,
|
|
151118
|
+
msg: data.assistantMessageID,
|
|
151119
|
+
len: typeof data.text === "string" ? data.text.length : -1,
|
|
151120
|
+
hasText: typeof data.text === "string"
|
|
151121
|
+
});
|
|
150877
151122
|
deps.applyRun(data.sessionID, {
|
|
150878
151123
|
type: "text.ended",
|
|
150879
151124
|
...data.text ? { text: data.text } : {},
|
|
@@ -150916,18 +151161,21 @@ async function routeEvent(event, deps) {
|
|
|
150916
151161
|
}
|
|
150917
151162
|
case "session.execution.started": {
|
|
150918
151163
|
const data = event.data;
|
|
151164
|
+
deps.log.debug("execution.started", { sessionID: data.sessionID });
|
|
150919
151165
|
deps.markStarted(data.sessionID);
|
|
150920
151166
|
deps.applyRun(data.sessionID, { type: "execution.started" });
|
|
150921
151167
|
break;
|
|
150922
151168
|
}
|
|
150923
151169
|
case "session.execution.succeeded": {
|
|
150924
151170
|
const data = event.data;
|
|
151171
|
+
deps.log.debug("execution.succeeded", { sessionID: data.sessionID });
|
|
150925
151172
|
deps.markEnded(data.sessionID);
|
|
150926
151173
|
deps.applyRun(data.sessionID, { type: "execution.succeeded" });
|
|
150927
151174
|
break;
|
|
150928
151175
|
}
|
|
150929
151176
|
case "session.execution.failed": {
|
|
150930
151177
|
const data = event.data;
|
|
151178
|
+
deps.log.debug("execution.failed", { sessionID: data.sessionID });
|
|
150931
151179
|
deps.markEnded(data.sessionID);
|
|
150932
151180
|
deps.applyRun(data.sessionID, { type: "execution.failed", error: extractErrorText(data.error) });
|
|
150933
151181
|
void deps.notifyFailure(data.sessionID, data.error);
|
|
@@ -150935,6 +151183,7 @@ async function routeEvent(event, deps) {
|
|
|
150935
151183
|
}
|
|
150936
151184
|
case "session.execution.interrupted": {
|
|
150937
151185
|
const data = event.data;
|
|
151186
|
+
deps.log.debug("execution.interrupted", { sessionID: data.sessionID, reason: data.reason });
|
|
150938
151187
|
deps.markEnded(data.sessionID);
|
|
150939
151188
|
deps.applyRun(data.sessionID, { type: "execution.failed", error: `\u5DF2\u4E2D\u65AD\uFF08${data.reason ?? "unknown"}\uFF09` });
|
|
150940
151189
|
break;
|
|
@@ -150942,6 +151191,7 @@ async function routeEvent(event, deps) {
|
|
|
150942
151191
|
case "session.status": {
|
|
150943
151192
|
const data = event.data;
|
|
150944
151193
|
const statusType = data.status?.type;
|
|
151194
|
+
deps.log.debug("session.status", { sessionID: data.sessionID, status: statusType });
|
|
150945
151195
|
if (statusType === "idle") {
|
|
150946
151196
|
deps.markEnded(data.sessionID);
|
|
150947
151197
|
deps.applyRun(data.sessionID, { type: "execution.succeeded" });
|
|
@@ -150952,6 +151202,7 @@ async function routeEvent(event, deps) {
|
|
|
150952
151202
|
}
|
|
150953
151203
|
case "session.idle": {
|
|
150954
151204
|
const data = event.data;
|
|
151205
|
+
deps.log.debug("session.idle", { sessionID: data.sessionID });
|
|
150955
151206
|
deps.markEnded(data.sessionID);
|
|
150956
151207
|
deps.applyRun(data.sessionID, { type: "execution.succeeded" });
|
|
150957
151208
|
break;
|
|
@@ -151269,7 +151520,8 @@ function routeCardAction(action, deps) {
|
|
|
151269
151520
|
return deps.handleCompact(action);
|
|
151270
151521
|
}
|
|
151271
151522
|
const hasForm = action.formValue !== void 0;
|
|
151272
|
-
const routed = hasForm || isSetupFormAction(value3) || Boolean(parseSessionCardValue(value3)) || Boolean(parseSetupCardValue(value3))
|
|
151523
|
+
const routed = hasForm || isSetupFormAction(value3) || Boolean(parseSessionCardValue(value3)) || Boolean(parseSetupCardValue(value3)) || // 「一句话建会话」建议卡按钮(issue #2)。
|
|
151524
|
+
Boolean(parseQuickNewActionValue(value3));
|
|
151273
151525
|
deps.log.debug("\u5361\u7247\u56DE\u8C03\u8DEF\u7531", {
|
|
151274
151526
|
hasForm,
|
|
151275
151527
|
hasValue: value3 !== void 0,
|
|
@@ -152031,6 +152283,10 @@ async function start(ctx, config, log2, logSink) {
|
|
|
152031
152283
|
rootKnown: Boolean(rootLink)
|
|
152032
152284
|
});
|
|
152033
152285
|
if (decision.kind === "main-hint") {
|
|
152286
|
+
if (config.quickNew) {
|
|
152287
|
+
await handleQuickNew(message);
|
|
152288
|
+
return;
|
|
152289
|
+
}
|
|
152034
152290
|
const res = await sender.sendCard(message.chatId, buildConsoleHintCard());
|
|
152035
152291
|
if (!res.ok) log2.warn("\u7BA1\u7406\u53F0\u63D0\u793A\u5361\u53D1\u9001\u5931\u8D25", { error: res.error ?? "unknown" });
|
|
152036
152292
|
return;
|
|
@@ -152126,6 +152382,242 @@ async function start(ctx, config, log2, logSink) {
|
|
|
152126
152382
|
log2.info("\u673A\u5668\u4EBA\u83DC\u5355\u70B9\u51FB \u2192 \u6267\u884C\u547D\u4EE4", { eventKey: click.eventKey, cmd: entry.cmd, label: entry.label });
|
|
152127
152383
|
await handleMessage(synthetic);
|
|
152128
152384
|
}
|
|
152385
|
+
const QUICK_NEW_KEY_PREFIX = "feishu:v2:quicknew:";
|
|
152386
|
+
const QUICK_NEW_TTL_MS = 60 * 60 * 1e3;
|
|
152387
|
+
function parseQuickNewEntry(raw) {
|
|
152388
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
152389
|
+
const v = raw;
|
|
152390
|
+
const text = typeof v.text === "string" ? v.text : "";
|
|
152391
|
+
const dir = typeof v.dir === "string" ? v.dir : "";
|
|
152392
|
+
const chatId = typeof v.chatId === "string" ? v.chatId : "";
|
|
152393
|
+
const openId = typeof v.openId === "string" ? v.openId : "";
|
|
152394
|
+
const anchorMessageId = typeof v.anchorMessageId === "string" ? v.anchorMessageId : "";
|
|
152395
|
+
if (!text || !dir || !chatId || !openId || !anchorMessageId) return void 0;
|
|
152396
|
+
return {
|
|
152397
|
+
text,
|
|
152398
|
+
title: typeof v.title === "string" ? v.title : "",
|
|
152399
|
+
dir,
|
|
152400
|
+
chatId,
|
|
152401
|
+
openId,
|
|
152402
|
+
anchorMessageId,
|
|
152403
|
+
createdAt: typeof v.createdAt === "number" ? v.createdAt : 0
|
|
152404
|
+
};
|
|
152405
|
+
}
|
|
152406
|
+
async function handleQuickNew(message) {
|
|
152407
|
+
const hint = async () => {
|
|
152408
|
+
const res = await sender.sendCard(message.chatId, buildConsoleHintCard());
|
|
152409
|
+
if (!res.ok) log2.warn("\u7BA1\u7406\u53F0\u63D0\u793A\u5361\u53D1\u9001\u5931\u8D25", { error: res.error ?? "unknown" });
|
|
152410
|
+
};
|
|
152411
|
+
try {
|
|
152412
|
+
const candidates = [];
|
|
152413
|
+
const seen = /* @__PURE__ */ new Set();
|
|
152414
|
+
for (const dir2 of await recent.listDirs()) {
|
|
152415
|
+
if (!seen.has(dir2)) {
|
|
152416
|
+
seen.add(dir2);
|
|
152417
|
+
candidates.push({ path: dir2 });
|
|
152418
|
+
}
|
|
152419
|
+
}
|
|
152420
|
+
let entries3 = [];
|
|
152421
|
+
try {
|
|
152422
|
+
const raw = await listAllSessionsRaw() ?? await listSessionsOverHttp({}, { log: log2 });
|
|
152423
|
+
entries3 = normalizeSessionList(raw) ?? [];
|
|
152424
|
+
} catch (err2) {
|
|
152425
|
+
log2.debug("quick-new \u4F1A\u8BDD\u5217\u8868\u8BFB\u53D6\u5931\u8D25", { error: errorMessage(err2) });
|
|
152426
|
+
}
|
|
152427
|
+
for (const entry2 of entries3) {
|
|
152428
|
+
if (entry2.directory && !seen.has(entry2.directory)) {
|
|
152429
|
+
seen.add(entry2.directory);
|
|
152430
|
+
candidates.push({ path: entry2.directory, ...entry2.title ? { label: entry2.title } : {} });
|
|
152431
|
+
}
|
|
152432
|
+
}
|
|
152433
|
+
const routingSessionID = entries3[0]?.sessionID;
|
|
152434
|
+
if (candidates.length === 0 || !routingSessionID) {
|
|
152435
|
+
log2.debug("quick-new \u65E0\u5019\u9009\u76EE\u5F55\u6216\u4F1A\u8BDD\uFF0C\u56DE\u9000\u63D0\u793A\u5361", {
|
|
152436
|
+
candidates: candidates.length,
|
|
152437
|
+
hasRouting: Boolean(routingSessionID)
|
|
152438
|
+
});
|
|
152439
|
+
await hint();
|
|
152440
|
+
return;
|
|
152441
|
+
}
|
|
152442
|
+
const ack = await sender.sendCard(message.chatId, buildQuickNewThinkingCard());
|
|
152443
|
+
if (!ack.ok || !ack.messageId) {
|
|
152444
|
+
await hint();
|
|
152445
|
+
return;
|
|
152446
|
+
}
|
|
152447
|
+
const generateText = async (prompt, requestOptions) => {
|
|
152448
|
+
const api = ctx.generate.text;
|
|
152449
|
+
if (typeof api !== "function") throw new Error("generate.text unavailable");
|
|
152450
|
+
return api({ prompt }, requestOptions);
|
|
152451
|
+
};
|
|
152452
|
+
const outcome = await quickGenerateWithSession(
|
|
152453
|
+
{ log: log2, generateText },
|
|
152454
|
+
{ prompt: buildQuickNewPrompt(message.text, candidates), sessionID: routingSessionID }
|
|
152455
|
+
);
|
|
152456
|
+
const decision = parseQuickNewDecision(extractGeneratedText(outcome.result));
|
|
152457
|
+
const dir = decision?.intent === "task" ? matchCandidateDirectory(decision.directory, candidates) : void 0;
|
|
152458
|
+
const validation = dir ? validateDir(dir) : void 0;
|
|
152459
|
+
if (!decision || decision.intent !== "task" || !dir || !validation?.ok) {
|
|
152460
|
+
log2.debug("quick-new \u5224\u5B9A\u4E3A\u975E\u4EFB\u52A1\u6216\u672A\u5339\u914D\u5230\u76EE\u5F55", {
|
|
152461
|
+
intent: decision?.intent,
|
|
152462
|
+
dir: decision?.directory,
|
|
152463
|
+
reason: decision?.reason
|
|
152464
|
+
});
|
|
152465
|
+
await sender.patchCard(ack.messageId, buildConsoleHintCard());
|
|
152466
|
+
return;
|
|
152467
|
+
}
|
|
152468
|
+
const id2 = `qn_${message.messageId}`;
|
|
152469
|
+
const title = decision.title || topicTitle(message.text);
|
|
152470
|
+
const entry = {
|
|
152471
|
+
text: message.text,
|
|
152472
|
+
title,
|
|
152473
|
+
dir: validation.path,
|
|
152474
|
+
chatId: message.chatId,
|
|
152475
|
+
openId: message.senderOpenId,
|
|
152476
|
+
anchorMessageId: message.messageId,
|
|
152477
|
+
createdAt: Date.now()
|
|
152478
|
+
};
|
|
152479
|
+
await ctx.storage.set(
|
|
152480
|
+
QUICK_NEW_KEY_PREFIX + id2,
|
|
152481
|
+
entry
|
|
152482
|
+
);
|
|
152483
|
+
await sender.patchCard(
|
|
152484
|
+
ack.messageId,
|
|
152485
|
+
buildQuickNewProposalCard({
|
|
152486
|
+
id: id2,
|
|
152487
|
+
title,
|
|
152488
|
+
directory: validation.path,
|
|
152489
|
+
textPreview: message.text,
|
|
152490
|
+
...decision.reason ? { reason: decision.reason } : {}
|
|
152491
|
+
})
|
|
152492
|
+
);
|
|
152493
|
+
log2.info("quick-new \u5EFA\u8BAE\u5361\u5DF2\u53D1\u9001", { dir: validation.path, title, reason: decision.reason });
|
|
152494
|
+
} catch (err2) {
|
|
152495
|
+
log2.warn("quick-new \u5904\u7406\u5931\u8D25\uFF0C\u56DE\u9000\u7BA1\u7406\u53F0\u63D0\u793A", { error: errorMessage(err2) });
|
|
152496
|
+
await hint();
|
|
152497
|
+
}
|
|
152498
|
+
}
|
|
152499
|
+
async function handleQuickNewCardAction(action, value3) {
|
|
152500
|
+
if (!owner.isAllowed(action.operatorOpenId)) {
|
|
152501
|
+
return { toast: { type: "error", content: "\u65E0\u64CD\u4F5C\u6743\u9650" } };
|
|
152502
|
+
}
|
|
152503
|
+
const key = QUICK_NEW_KEY_PREFIX + value3.id;
|
|
152504
|
+
let raw;
|
|
152505
|
+
try {
|
|
152506
|
+
raw = await ctx.storage.get(key);
|
|
152507
|
+
} catch {
|
|
152508
|
+
raw = void 0;
|
|
152509
|
+
}
|
|
152510
|
+
const entry = parseQuickNewEntry(raw);
|
|
152511
|
+
if (!entry || Date.now() - entry.createdAt > QUICK_NEW_TTL_MS) {
|
|
152512
|
+
try {
|
|
152513
|
+
await ctx.storage.remove(key);
|
|
152514
|
+
} catch {
|
|
152515
|
+
}
|
|
152516
|
+
if (action.messageId) {
|
|
152517
|
+
await sender.patchCard(
|
|
152518
|
+
action.messageId,
|
|
152519
|
+
buildQuickNewResolvedCard("\u231B\uFE0F \u5EFA\u8BAE\u5DF2\u8FC7\u671F", "\u8BF7\u91CD\u65B0\u53D1\u9001\u6D88\u606F\u3002", "orange")
|
|
152520
|
+
);
|
|
152521
|
+
}
|
|
152522
|
+
return { toast: { type: "warning", content: "\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u53D1\u6D88\u606F" } };
|
|
152523
|
+
}
|
|
152524
|
+
if (value3.op === "cancel") {
|
|
152525
|
+
try {
|
|
152526
|
+
await ctx.storage.remove(key);
|
|
152527
|
+
} catch {
|
|
152528
|
+
}
|
|
152529
|
+
if (action.messageId) {
|
|
152530
|
+
await sender.patchCard(action.messageId, buildQuickNewResolvedCard("\u274C \u5DF2\u53D6\u6D88", "\u672A\u521B\u5EFA\u4F1A\u8BDD\u3002"));
|
|
152531
|
+
}
|
|
152532
|
+
return { toast: { type: "info", content: "\u5DF2\u53D6\u6D88" } };
|
|
152533
|
+
}
|
|
152534
|
+
const validation = validateDir(entry.dir);
|
|
152535
|
+
if (!validation.ok) {
|
|
152536
|
+
if (action.messageId) {
|
|
152537
|
+
await sender.patchCard(
|
|
152538
|
+
action.messageId,
|
|
152539
|
+
buildQuickNewResolvedCard("\u26A0\uFE0F \u76EE\u5F55\u4E0D\u53EF\u7528", `${entry.dir}\uFF1A${validation.message}`, "red")
|
|
152540
|
+
);
|
|
152541
|
+
}
|
|
152542
|
+
return { toast: { type: "error", content: "\u76EE\u5F55\u4E0D\u53EF\u7528" } };
|
|
152543
|
+
}
|
|
152544
|
+
try {
|
|
152545
|
+
await ctx.storage.remove(key);
|
|
152546
|
+
} catch {
|
|
152547
|
+
}
|
|
152548
|
+
const perm = "edit";
|
|
152549
|
+
const title = entry.title || topicTitle(entry.text);
|
|
152550
|
+
void (async () => {
|
|
152551
|
+
const created = await createSessionInternal({
|
|
152552
|
+
title,
|
|
152553
|
+
chatId: entry.chatId,
|
|
152554
|
+
openId: entry.openId,
|
|
152555
|
+
setActive: false,
|
|
152556
|
+
directory: validation.path,
|
|
152557
|
+
permissions: presetToRuleset(perm),
|
|
152558
|
+
perm,
|
|
152559
|
+
gateMode: presetGateMode(perm)
|
|
152560
|
+
});
|
|
152561
|
+
await recent.addDir(validation.path);
|
|
152562
|
+
const readyCard = buildSessionReadyCard({
|
|
152563
|
+
title,
|
|
152564
|
+
sessionID: created.id,
|
|
152565
|
+
dir: validation.path,
|
|
152566
|
+
perm: presetLabel(perm)
|
|
152567
|
+
});
|
|
152568
|
+
const res = await sender.replyCard(entry.anchorMessageId, readyCard, { replyInThread: true });
|
|
152569
|
+
if (res.ok && res.messageId) {
|
|
152570
|
+
await sessionMap.bindRoot(entry.anchorMessageId, created.id);
|
|
152571
|
+
const meta = res.threadId ? void 0 : await sender.getMessageMeta(res.messageId);
|
|
152572
|
+
const threadId = res.threadId ?? meta?.threadId;
|
|
152573
|
+
if (threadId) {
|
|
152574
|
+
await sessionMap.bindThread(threadId, created.id, entry.chatId, entry.openId, entry.anchorMessageId);
|
|
152575
|
+
} else {
|
|
152576
|
+
log2.warn("quick-new \u5F00\u8BDD\u9898\u540E\u672A\u8BFB\u5230 thread_id", { sessionID: created.id });
|
|
152577
|
+
}
|
|
152578
|
+
await sessionMap.setRootCard(created.id, {
|
|
152579
|
+
style: "created",
|
|
152580
|
+
sessionID: created.id,
|
|
152581
|
+
title,
|
|
152582
|
+
dir: validation.path,
|
|
152583
|
+
perm: presetLabel(perm)
|
|
152584
|
+
});
|
|
152585
|
+
} else {
|
|
152586
|
+
log2.warn("quick-new \u5F00\u8BDD\u9898\u5931\u8D25", { sessionID: created.id, error: res.error ?? "unknown" });
|
|
152587
|
+
}
|
|
152588
|
+
await runInSession(
|
|
152589
|
+
{
|
|
152590
|
+
eventId: `quicknew_${value3.id}`,
|
|
152591
|
+
messageId: entry.anchorMessageId,
|
|
152592
|
+
chatId: entry.chatId,
|
|
152593
|
+
text: entry.text,
|
|
152594
|
+
senderOpenId: entry.openId
|
|
152595
|
+
},
|
|
152596
|
+
created.id,
|
|
152597
|
+
entry.anchorMessageId
|
|
152598
|
+
);
|
|
152599
|
+
if (action.messageId) {
|
|
152600
|
+
await sender.patchCard(
|
|
152601
|
+
action.messageId,
|
|
152602
|
+
buildQuickNewResolvedCard(
|
|
152603
|
+
"\u2705 \u5DF2\u521B\u5EFA",
|
|
152604
|
+
`\u76EE\u5F55\uFF1A\`${validation.path}\`
|
|
152605
|
+
\u5DF2\u5F00\u8BDD\u9898\u5E76\u5F00\u59CB\u5904\u7406\u4F60\u7684\u6D88\u606F\u3002`,
|
|
152606
|
+
"green"
|
|
152607
|
+
)
|
|
152608
|
+
);
|
|
152609
|
+
}
|
|
152610
|
+
})().catch((err2) => {
|
|
152611
|
+
log2.warn("quick-new \u5EFA\u4F1A\u8BDD\u5931\u8D25", { error: errorMessage(err2) });
|
|
152612
|
+
if (action.messageId) {
|
|
152613
|
+
void sender.patchCard(
|
|
152614
|
+
action.messageId,
|
|
152615
|
+
buildQuickNewResolvedCard("\u26A0\uFE0F \u521B\u5EFA\u5931\u8D25", errorMessage(err2).slice(0, 160), "red")
|
|
152616
|
+
);
|
|
152617
|
+
}
|
|
152618
|
+
});
|
|
152619
|
+
return { toast: { type: "success", content: "\u5DF2\u521B\u5EFA\uFF0C\u6B63\u5728\u6253\u5F00\u8BDD\u9898\u2026" } };
|
|
152620
|
+
}
|
|
152129
152621
|
async function runInActiveSession(message) {
|
|
152130
152622
|
let active = await sessionMap.getActive(message.chatId);
|
|
152131
152623
|
if (!active) {
|
|
@@ -152200,7 +152692,11 @@ ${downloadedAttachmentPrompt(message.attachment, outcome)}`;
|
|
|
152200
152692
|
handleForm: (a) => formRelay.handleCardAction(a),
|
|
152201
152693
|
handleStop: (a) => stop.handleCardAction(a),
|
|
152202
152694
|
handleCompact: (a) => compact2.handleCardAction(a),
|
|
152203
|
-
handleCommands: (a) =>
|
|
152695
|
+
handleCommands: (a) => {
|
|
152696
|
+
const quick = parseQuickNewActionValue(a.rawValue);
|
|
152697
|
+
if (quick) return handleQuickNewCardAction(a, quick);
|
|
152698
|
+
return commands.handleCardAction(a);
|
|
152699
|
+
},
|
|
152204
152700
|
handleApprovals: (a) => approvals.handleCardAction(a)
|
|
152205
152701
|
})
|
|
152206
152702
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-feishu-plugin",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.14",
|
|
4
4
|
"description": "Feishu/Lark plugin for OpenCode V2 — single-user topics as sessions, in-card permission approvals, streaming cards. Minimal permissions, no public endpoint.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|