@jesonliu/lark-claudecode-bridge 0.18.0 → 0.19.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 -3
- package/assets/web/css/main.css +2 -0
- package/assets/web/js/main.js +3 -1
- package/assets/web/js/pages/mcp.js +229 -0
- package/assets/web/js/pages/skills.js +169 -0
- package/assets/web/js/pages/workspaces.js +23 -2
- package/dist/bin/lcb.js +5 -0
- package/dist/bin/lcb.js.map +1 -1
- package/dist/config.js +33 -7
- package/dist/config.js.map +1 -1
- package/dist/executor/notify-server.d.ts +13 -0
- package/dist/executor/notify-server.js +60 -4
- package/dist/executor/notify-server.js.map +1 -1
- package/dist/executor/plugin-discovery.d.ts +6 -0
- package/dist/executor/plugin-discovery.js +14 -0
- package/dist/executor/plugin-discovery.js.map +1 -1
- package/dist/gateway/card-builder.d.ts +24 -26
- package/dist/gateway/card-builder.js +79 -109
- package/dist/gateway/card-builder.js.map +1 -1
- package/dist/gateway/feishu-gateway.d.ts +10 -0
- package/dist/gateway/feishu-gateway.js +48 -0
- package/dist/gateway/feishu-gateway.js.map +1 -1
- package/dist/gateway/progress-card.d.ts +26 -0
- package/dist/gateway/progress-card.js +68 -4
- package/dist/gateway/progress-card.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +174 -71
- package/dist/index.js.map +1 -1
- package/dist/notify-sop.d.ts +1 -0
- package/dist/notify-sop.js +30 -0
- package/dist/notify-sop.js.map +1 -0
- package/dist/types.d.ts +14 -2
- package/dist/util/log-tee.d.ts +4 -0
- package/dist/util/log-tee.js +99 -0
- package/dist/util/log-tee.js.map +1 -0
- package/dist/util/runtime-dirs.d.ts +4 -0
- package/dist/util/runtime-dirs.js +27 -0
- package/dist/util/runtime-dirs.js.map +1 -0
- package/dist/web/lifecycle.d.ts +4 -1
- package/dist/web/lifecycle.js +11 -25
- package/dist/web/lifecycle.js.map +1 -1
- package/dist/web/server.js +311 -11
- package/dist/web/server.js.map +1 -1
- package/dist/web/skills-mcp-api.d.ts +113 -0
- package/dist/web/skills-mcp-api.js +312 -0
- package/dist/web/skills-mcp-api.js.map +1 -0
- package/package.json +3 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"progress-card.js","sourceRoot":"","sources":["../../src/gateway/progress-card.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAsB,MAAM,mBAAmB,CAAC;AAU1E,MAAM,WAAW,GAAG,GAAG,CAAC;AAExB,MAAM,OAAO,YAAY;IAgBb,MAAM;IAEN,IAAI;IAjBN,SAAS,CAAU;IACnB,MAAM,GAAG,EAAE,CAAC;IACZ,KAAK,CAAgB;IACrB,UAAU,CAAkB;IAC5B,cAAc,CAAkB;IAChC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC5B,IAAI,GAAG,KAAK,CAAC;IACrB,oDAAoD;IACpD,2CAA2C;IAC3C,iCAAiC;IACzB,UAAU,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAC9C,KAAK,GAAG,KAAK,CAAC;IACd,QAAQ,GAAG,KAAK,CAAC;IAEzB,YACU,MAAkB,EAC1B,KAAa,EACL,IAAI,
|
|
1
|
+
{"version":3,"file":"progress-card.js","sourceRoot":"","sources":["../../src/gateway/progress-card.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -27,7 +27,13 @@ deps: BridgeDeps, opts?: {
|
|
|
27
27
|
confirmTimeoutMs?: number;
|
|
28
28
|
cardSendTimeoutMs?: number;
|
|
29
29
|
reloadConfig?: () => void;
|
|
30
|
-
}): GatewayHandlers
|
|
30
|
+
}): GatewayHandlers & {
|
|
31
|
+
/** 测试/诊断入口:返回通道当前挂起的内嵌 plan / question requestId */
|
|
32
|
+
_pendingAsk(channelKey: string): {
|
|
33
|
+
planId?: string;
|
|
34
|
+
questionId?: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
31
37
|
/**
|
|
32
38
|
* 配置热重载器:lcb ws add/remove 写盘后,运行实例下一条消息即见新工作区
|
|
33
39
|
* (与 access.reload 同款模式——长驻进程不重读会持旧配置直至重启)。
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// 装配主流程:消息 → 访问控制 → 命令 → 通道队列 → 执行 → 确认卡片 → 回传
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { existsSync } from 'node:fs';
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { basename, join, resolve } from 'node:path';
|
|
5
5
|
import { CONFIG_DIR, CONFIG_PATH, DEFAULT_CONTEXT_REMIND_TOKENS, loadConfig, sameApps } from './config.js';
|
|
6
6
|
import { DEFAULT_CLAUDE_DIR, resolveClaudeDir } from './claude-config.js';
|
|
@@ -11,12 +11,14 @@ import { clearPidFile, writePidFile, stopExistingBridgeAndWait } from './web/lif
|
|
|
11
11
|
import { AccessControl } from './access/access-control.js';
|
|
12
12
|
import { FeishuGateway } from './gateway/feishu-gateway.js';
|
|
13
13
|
import { ProgressCard } from './gateway/progress-card.js';
|
|
14
|
-
import { DECISION_TEXT,
|
|
14
|
+
import { DECISION_TEXT, PROGRESS_TAIL_CHARS, } from './gateway/card-builder.js';
|
|
15
15
|
import { buildDiffSummaryCards } from './gateway/diff-card.js';
|
|
16
16
|
import { runTask } from './executor/claude-executor.js';
|
|
17
17
|
import { PermissionGate, DEFAULT_ALLOW_TOOLS_LIST, DEFAULT_DANGEROUS_COMMANDS, } from './executor/permission-gate.js';
|
|
18
18
|
import { discoverPlugins, resolvePluginPaths } from './executor/plugin-discovery.js';
|
|
19
19
|
import { createGatewaySender, createNotifyServer, NOTIFY_SERVER_NAME } from './executor/notify-server.js';
|
|
20
|
+
import { BRIDGE_MCP_JSON, readMcpServersFromJsonFile } from './web/skills-mcp-api.js';
|
|
21
|
+
import { FEISHU_NOTIFY_SOP_PROMPT } from './notify-sop.js';
|
|
20
22
|
import { collectWorkspaceDiff } from './util/workspace-diff.js';
|
|
21
23
|
import { isImageFile } from './util/file-types.js';
|
|
22
24
|
import { FileTracker } from './util/file-tracker.js';
|
|
@@ -53,6 +55,8 @@ const PLAN_DECISION_TEXT = {
|
|
|
53
55
|
'plan-approve': '✅ 计划已批准,开始执行(后续写操作免确认)',
|
|
54
56
|
'plan-revise': '📝 修改意见已提交',
|
|
55
57
|
'plan-reject': '❌ 计划已放弃',
|
|
58
|
+
// plan-view-file 不消费挂起项,仅 toast「正在发送方案文件…」——文案不在此出现,留口供编译期完整性
|
|
59
|
+
'plan-view-file': '',
|
|
56
60
|
};
|
|
57
61
|
export function createBridge(config, app, // 本 bridge 绑定的飞书应用:并发/默认工作区/Claude Code 环境/人格均 per-app
|
|
58
62
|
deps, opts = {}) {
|
|
@@ -85,9 +89,11 @@ deps, opts = {}) {
|
|
|
85
89
|
// 用于实现「默认不发非图片文件,用户主动点名才发」的回传规则
|
|
86
90
|
const fileTracker = new FileTracker();
|
|
87
91
|
const confirmPending = new Map();
|
|
88
|
-
// plan 确认挂起项:requestId → 等待中的计划决策(plan-approve/revise/reject
|
|
92
|
+
// plan 确认挂起项:requestId → 等待中的计划决策(plan-approve/revise/reject)。
|
|
93
|
+
// 嵌入主卡片后回调侧无 cardId(主卡 messageId 由 ProgressCard 内部管理),改带 channelKey 定位活跃进度卡
|
|
89
94
|
const planPending = new Map();
|
|
90
|
-
// 提问挂起项:requestId → 等待中的问题答案(qa-pick 逐项收集,qa-submit 一次性 resolve
|
|
95
|
+
// 提问挂起项:requestId → 等待中的问题答案(qa-pick 逐项收集,qa-submit 一次性 resolve)。
|
|
96
|
+
// 嵌入主卡片后无 cardId,改带 channelKey 定位活跃进度卡
|
|
91
97
|
const qaPending = new Map();
|
|
92
98
|
// 通道当前任务的进度卡:gate 的 ask/planAsk/askQuestion 闭包随 gate 通道级复用,
|
|
93
99
|
// 不能捕获任务级 progress 实例(会是首个任务的旧卡)——经此间接引用每次任务的新卡
|
|
@@ -98,18 +104,28 @@ deps, opts = {}) {
|
|
|
98
104
|
function workspacePath(name) {
|
|
99
105
|
return config.workspaces.find((w) => w.name === name)?.path ?? config.workspaces[0].path;
|
|
100
106
|
}
|
|
107
|
+
/** 配置页管理的 MCP(#12):bridge 自管 mcp/servers.json 每任务现读注入 SDK(改配置即热生效,
|
|
108
|
+
* 无须重启)。notify 名固定后展开防被用户配置同名覆盖;文件缺失/损坏返回空(配置页写坏
|
|
109
|
+
* 不应拖垮任务执行) */
|
|
110
|
+
function loadBridgeMcpServers() {
|
|
111
|
+
return readMcpServersFromJsonFile(BRIDGE_MCP_JSON).servers;
|
|
112
|
+
}
|
|
101
113
|
/** 通道内串行入队(每 key 一条 Promise 链),全局并发由 Semaphore 限制。
|
|
102
114
|
* 有任务在跑时新消息静默 FIFO 排队——不提示的话用户不知道消息已被接收,会反复重发或误以为丢失。
|
|
103
|
-
* queuedCount 计「等待中」消息数:enqueue 一律自增(含首位),executeTask
|
|
115
|
+
* queuedCount 计「等待中」消息数:enqueue 一律自增(含首位),executeTask 启动时递减(该条离开队列)。
|
|
116
|
+
* /new 通过 epoch 代际丢弃旧排队消息:enqueue 捕获当前 epoch 传入 executeTask,不匹配则 no-op */
|
|
104
117
|
function enqueue(key, msg, prompt, wsName) {
|
|
105
118
|
const rt = runtimes.get(key) ?? { queue: Promise.resolve() };
|
|
119
|
+
if (rt.epoch === undefined)
|
|
120
|
+
rt.epoch = 0; // 旧状态无 epoch 字段的运行期兜底
|
|
106
121
|
runtimes.set(key, rt);
|
|
107
122
|
rt.queuedCount = (rt.queuedCount ?? 0) + 1;
|
|
108
123
|
if (rt.busy) {
|
|
109
124
|
void deps.gateway.sendTextTo(msg.chatId, `⏳ 当前有任务在运行,本条消息已排队(第 ${rt.queuedCount} 位),完成后自动开始`)
|
|
110
125
|
.catch(() => { }); // 提示失败静默:不影响任务本身入队
|
|
111
126
|
}
|
|
112
|
-
|
|
127
|
+
const myEpoch = rt.epoch; // 闭包捕获本条消息入队时的代际;executeTask 据此判断是否被 /new 作废
|
|
128
|
+
rt.queue = rt.queue.then(() => executeTask(key, msg, prompt, wsName, myEpoch)).catch((e) => {
|
|
113
129
|
console.error(tag, '[任务异常]', e);
|
|
114
130
|
});
|
|
115
131
|
}
|
|
@@ -215,17 +231,55 @@ deps, opts = {}) {
|
|
|
215
231
|
if (cmd.handled) {
|
|
216
232
|
if (cmd.reply)
|
|
217
233
|
await deps.gateway.sendTextTo(msg.chatId, cmd.reply);
|
|
218
|
-
// /new
|
|
219
|
-
//
|
|
220
|
-
|
|
234
|
+
// /new 开启新会话:
|
|
235
|
+
// 1) 清空旧代际排队消息(epoch 自增,旧任务入 executeTask 即 no-op)
|
|
236
|
+
// 2) abort 正在执行的任务
|
|
237
|
+
// 3) 结算本用户挂起的 confirm/plan/qa 条目(按取消/拒绝处理:对话继续推进或被放弃)
|
|
238
|
+
// 4) 通道级权限闸 reset(spec:「本次会话不再询问」的记忆跨任务生效,直至 /new)
|
|
239
|
+
// 5) 汇总已取消的排队消息数(用户能看到新建会话的真实影响面)
|
|
240
|
+
// ——handleCommand 无权访问 wiring 的 runtime/pending map,统一在 wiring 侧做
|
|
241
|
+
if (/^\/new(?:\s|$)/.test(msg.text.trim())) {
|
|
242
|
+
const rt = runtimes.get(key);
|
|
243
|
+
const cancelled = rt?.queuedCount ?? 0;
|
|
244
|
+
if (rt) {
|
|
245
|
+
rt.epoch = (rt.epoch ?? 0) + 1;
|
|
246
|
+
rt.queuedCount = 0; // 旧代际消息在 executeTask 入口 no-op,不会再 decrement;此处一次性清零即可
|
|
247
|
+
if (rt.abort)
|
|
248
|
+
rt.abort.abort();
|
|
249
|
+
}
|
|
250
|
+
for (const [id, p] of confirmPending) {
|
|
251
|
+
if (p.channelKey !== key)
|
|
252
|
+
continue;
|
|
253
|
+
confirmPending.delete(id);
|
|
254
|
+
p.resolve('deny');
|
|
255
|
+
}
|
|
256
|
+
for (const [id, p] of planPending) {
|
|
257
|
+
if (p.channelKey !== key)
|
|
258
|
+
continue;
|
|
259
|
+
planPending.delete(id);
|
|
260
|
+
p.resolve({ action: 'reject' });
|
|
261
|
+
}
|
|
262
|
+
for (const [id, p] of qaPending) {
|
|
263
|
+
if (p.channelKey !== key)
|
|
264
|
+
continue;
|
|
265
|
+
qaPending.delete(id);
|
|
266
|
+
p.resolve({});
|
|
267
|
+
}
|
|
221
268
|
gates.get(key)?.reset();
|
|
269
|
+
if (cancelled > 0) {
|
|
270
|
+
await deps.gateway.sendTextTo(msg.chatId, `🗑️ 已因新建会话取消 ${cancelled} 条排队消息`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
222
273
|
return;
|
|
223
274
|
}
|
|
224
275
|
// 3. 普通文本:通道内串行入队(每 key 一条 Promise 链),全局并发由 Semaphore 限制
|
|
225
276
|
enqueue(key, msg, cmd.taskText ?? msg.text, currentWorkspace);
|
|
226
277
|
}
|
|
227
|
-
async function executeTask(key, msg, prompt, wsName) {
|
|
278
|
+
async function executeTask(key, msg, prompt, wsName, myEpoch) {
|
|
228
279
|
const rt = runtimes.get(key);
|
|
280
|
+
// /new 丢弃旧代际:静默 no-op(不入队列、不 decrement、不建进度卡;汇总条数已在 /new 时回)
|
|
281
|
+
if (myEpoch !== (rt.epoch ?? 0))
|
|
282
|
+
return;
|
|
229
283
|
rt.busy = true; // enqueue 据此给后续消息发「已排队」提示(sem 等待期同样算占用)
|
|
230
284
|
rt.queuedCount = Math.max(0, (rt.queuedCount ?? 1) - 1); // 本条离开等待队列开始执行
|
|
231
285
|
const release = await sem.acquire();
|
|
@@ -237,7 +291,9 @@ deps, opts = {}) {
|
|
|
237
291
|
deleteCard: deps.gateway.deleteCard
|
|
238
292
|
? (id) => deps.gateway.deleteCard(id)
|
|
239
293
|
: undefined,
|
|
240
|
-
}, `任务 · ${wsName}
|
|
294
|
+
}, `任务 · ${wsName}`,
|
|
295
|
+
// card.width 热生效:每任务现读 config(与同款「同对象 mutate 即生效」语义一致)
|
|
296
|
+
{ cardWidthMode: config.card?.width ?? 'default' });
|
|
241
297
|
const confirmTimeoutMs = opts.confirmTimeoutMs ?? CONFIRM_TIMEOUT_MS;
|
|
242
298
|
const cardSendTimeoutMs = opts.cardSendTimeoutMs ?? CARD_SEND_TIMEOUT_MS;
|
|
243
299
|
// 通道级复用:已存在则沿用(allow-session 记忆跨任务),不存在才建。
|
|
@@ -279,30 +335,43 @@ deps, opts = {}) {
|
|
|
279
335
|
},
|
|
280
336
|
req,
|
|
281
337
|
ownerId: msg.userId,
|
|
338
|
+
channelKey: key,
|
|
282
339
|
onSettled: settle,
|
|
283
340
|
});
|
|
284
341
|
});
|
|
285
342
|
},
|
|
286
|
-
// plan
|
|
287
|
-
//
|
|
343
|
+
// plan 确认(#3+#6):不再单独发计划卡片组。计划正文先落盘到 ~/.lark-claudecode-bridge/claude/plans/
|
|
344
|
+
// (slug + 时间戳,方便后续 review 与 SOP 要求的 plan 本地归档),然后嵌入当前任务的进度卡:
|
|
345
|
+
// 卡片正文仅露 600 字预览 + 「查看完整方案」按钮(callback send_file 发送原 md 文件,不塞卡片正文
|
|
346
|
+
// ——飞书单卡 30KB 上限与可读性都不允许长方案铺在卡片里);批准/放弃/按意见修改三按钮与意见输入框
|
|
347
|
+
// 仍在主卡片的内嵌 form 内。超时由本闭包自管:到点 clearPlan + resolve reject
|
|
288
348
|
planAsk: async (req) => {
|
|
289
349
|
const planReq = { requestId: randomUUID(), plan: req.plan, workspaceName: req.workspaceName };
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
void deps.gateway.sendTextTo(msg.chatId, '⚠️ 计划卡发送失败,本次计划已自动放弃;模型重新提交计划时会再次弹出')
|
|
295
|
-
.catch(() => { });
|
|
296
|
-
return { action: 'reject' };
|
|
297
|
-
}
|
|
298
|
-
ids.push(id);
|
|
350
|
+
// 落盘计划原文:路径走 ensureRuntimeDirs 已建的 claude/plans 目录,文件名加时间戳避免并发冲突
|
|
351
|
+
const plansDir = join(CONFIG_DIR, 'claude', 'plans');
|
|
352
|
+
try {
|
|
353
|
+
mkdirSync(plansDir, { recursive: true });
|
|
299
354
|
}
|
|
300
|
-
|
|
301
|
-
|
|
355
|
+
catch { /* 已存在 */ }
|
|
356
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
357
|
+
const slug = (req.workspaceName || 'plan').replace(/[^\w-]+/g, '_').slice(0, 32);
|
|
358
|
+
const planFilePath = join(plansDir, `${ts}-${slug}.md`);
|
|
359
|
+
try {
|
|
360
|
+
writeFileSync(planFilePath, `# 执行计划 · ${req.workspaceName} · ${new Date().toLocaleString('zh-CN', { hour12: false })}\n\n${req.plan}\n`, 'utf8');
|
|
361
|
+
}
|
|
362
|
+
catch (e) {
|
|
363
|
+
console.error(tag, '[计划落盘失败](不影响流程,仅「查看完整方案」可能不可用):', e);
|
|
364
|
+
}
|
|
365
|
+
const progress = activeProgress.get(key);
|
|
366
|
+
if (!progress)
|
|
367
|
+
return { action: 'reject' }; // 进度卡不在 = 任务已结束/被 abort,按放弃处理
|
|
368
|
+
progress.setPlan({ requestId: planReq.requestId, plan: req.plan, workspaceName: req.workspaceName, planFilePath });
|
|
369
|
+
// 不再 sinkToBottom:plan 区嵌入主进度卡,flush 自然 PATCH,删旧卡重发只会让用户看到计划区跳一下
|
|
302
370
|
return new Promise((resolve) => {
|
|
303
371
|
const gc = setTimeout(() => {
|
|
304
372
|
planPending.delete(planReq.requestId);
|
|
305
|
-
|
|
373
|
+
progress.setPlan(undefined);
|
|
374
|
+
progress.setStatus(`⏰ 计划确认超时(${Math.round(confirmTimeoutMs / 60000)} 分钟未确认),已自动放弃;模型重新提交计划时会再次弹出`);
|
|
306
375
|
resolve({ action: 'reject' });
|
|
307
376
|
}, confirmTimeoutMs);
|
|
308
377
|
gc.unref();
|
|
@@ -313,12 +382,14 @@ deps, opts = {}) {
|
|
|
313
382
|
},
|
|
314
383
|
req: planReq,
|
|
315
384
|
ownerId: msg.userId,
|
|
316
|
-
|
|
385
|
+
channelKey: key,
|
|
386
|
+
chatId: msg.chatId,
|
|
387
|
+
planFilePath,
|
|
317
388
|
});
|
|
318
389
|
});
|
|
319
390
|
},
|
|
320
|
-
//
|
|
321
|
-
//
|
|
391
|
+
// 提问确认(#3):AskUserQuestion 的选项 + 提交按钮全部嵌入主进度卡(#3 决策:彻底替换独立提问卡)。
|
|
392
|
+
// qa-pick 仅 PATCH 选中态(卡片不刷新整卡,仅更新 answer map),qa-submit 一次性 resolve 并 clearQuestion
|
|
322
393
|
askQuestion: async (req) => {
|
|
323
394
|
const qaReq = {
|
|
324
395
|
requestId: randomUUID(),
|
|
@@ -326,18 +397,16 @@ deps, opts = {}) {
|
|
|
326
397
|
workspaceName: req.workspaceName,
|
|
327
398
|
};
|
|
328
399
|
const answers = {};
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
return {};
|
|
335
|
-
}
|
|
336
|
-
void activeProgress.get(key)?.sinkToBottom();
|
|
400
|
+
const progress = activeProgress.get(key);
|
|
401
|
+
if (!progress)
|
|
402
|
+
return {}; // 进度卡不在 = 任务已结束/被 abort,按未作答处理
|
|
403
|
+
progress.setQuestion({ requestId: qaReq.requestId, questions: req.questions, workspaceName: req.workspaceName, answers });
|
|
404
|
+
// 不再 sinkToBottom:提问区嵌入主进度卡后,flush 自然 PATCH(理由同 planAsk 上方注释)
|
|
337
405
|
return new Promise((resolve) => {
|
|
338
406
|
const gc = setTimeout(() => {
|
|
339
407
|
qaPending.delete(qaReq.requestId);
|
|
340
|
-
|
|
408
|
+
progress.setQuestion(undefined);
|
|
409
|
+
progress.setStatus(`⏰ 提问超时(${Math.round(confirmTimeoutMs / 60000)} 分钟未作答),已按未作答处理;模型自行决策或停下等待你的回复`);
|
|
341
410
|
resolve({});
|
|
342
411
|
}, confirmTimeoutMs);
|
|
343
412
|
gc.unref();
|
|
@@ -348,7 +417,8 @@ deps, opts = {}) {
|
|
|
348
417
|
},
|
|
349
418
|
req: qaReq,
|
|
350
419
|
ownerId: msg.userId,
|
|
351
|
-
|
|
420
|
+
channelKey: key,
|
|
421
|
+
chatId: msg.chatId,
|
|
352
422
|
answers,
|
|
353
423
|
});
|
|
354
424
|
});
|
|
@@ -363,6 +433,8 @@ deps, opts = {}) {
|
|
|
363
433
|
// lcb-notify 发送能力:chatId 硬绑定当前任务(权限闸直通的安全前提);
|
|
364
434
|
// sentPaths 记录中途已推送的文件,任务收尾的产出回传据此去重(用户已收过的不重发)。
|
|
365
435
|
// 每次中途推送后进度卡沉底一次(删旧卡重发),保持「任务是否还在跑」始终可见于会话底部
|
|
436
|
+
// SOP 开关:本任务级现读 config.session.notifySop(缺省 true),决定后续 sendText 是否被强制降级
|
|
437
|
+
const sopEnabled = config.session?.notifySop !== false;
|
|
366
438
|
const sentPaths = new Set();
|
|
367
439
|
const sink = async () => { await activeProgress.get(key)?.sinkToBottom(); };
|
|
368
440
|
const notifySender = createGatewaySender({
|
|
@@ -384,6 +456,10 @@ deps, opts = {}) {
|
|
|
384
456
|
await deps.gateway.uploadAndSendFile(c, p);
|
|
385
457
|
await sink();
|
|
386
458
|
},
|
|
459
|
+
}, {
|
|
460
|
+
enabled: sopEnabled,
|
|
461
|
+
// #11 SOP 提示文案前缀(用户看到的「为什么自动转附件」提示)
|
|
462
|
+
sopTag: '⚠️ 推送上限',
|
|
387
463
|
});
|
|
388
464
|
activeProgress.set(key, progress);
|
|
389
465
|
try {
|
|
@@ -405,15 +481,22 @@ deps, opts = {}) {
|
|
|
405
481
|
resumeSessionId: resumeId,
|
|
406
482
|
signal: abort.signal,
|
|
407
483
|
env: appEnv,
|
|
408
|
-
|
|
484
|
+
// #11 SOP 软约束:app.appendSystemPrompt 后追加 SOP 摘要(关闭时仅传 app 原有部分)
|
|
485
|
+
appendSystemPrompt: (() => {
|
|
486
|
+
const base = app.appendSystemPrompt?.trim() ?? '';
|
|
487
|
+
if (!sopEnabled)
|
|
488
|
+
return base || undefined;
|
|
489
|
+
return base ? `${base}\n\n${FEISHU_NOTIFY_SOP_PROMPT}` : FEISHU_NOTIFY_SOP_PROMPT;
|
|
490
|
+
})(),
|
|
409
491
|
// code-dev 工作区统一 plan mode:模型先出计划(ExitPlanMode → planAsk 飞书卡片),
|
|
410
492
|
// 用户批准后 SDK 自动切回可编辑模式继续执行
|
|
411
493
|
...(config.workspaces.find((w) => w.name === wsName)?.type === 'code-dev' ? { permissionMode: 'plan' } : {}),
|
|
412
494
|
// 通道级模型覆盖(/model 设置,每任务现读——改完下一条消息即生效);未设 = 跟随 ~/.claude 全局
|
|
413
495
|
...(state?.model ? { model: state.model } : {}),
|
|
414
496
|
// 进程内通知工具(send_text/send_image/send_file):中间产物实时推给当前聊天。
|
|
415
|
-
// server 实例按任务构造,chatId 在 sender
|
|
416
|
-
|
|
497
|
+
// server 实例按任务构造,chatId 在 sender 闭包内硬绑定——模型无法选择接收者。
|
|
498
|
+
// 配置页 MCP(#12)一并注入:bridge 自管 servers.json 每任务现读(热生效),notify 后展开保名
|
|
499
|
+
mcpServers: { ...loadBridgeMcpServers(), [NOTIFY_SERVER_NAME]: createNotifyServer(notifySender) },
|
|
417
500
|
// 插件:config.yaml 显式配置(开发期指源码目录)+ Claude 配置目录已启用的 marketplace
|
|
418
501
|
// 插件自动发现,同名显式优先;SDK 对无效路径静默跳过,实际加载以 init 清单(/plugins 命令)为准。
|
|
419
502
|
// managed 模式合并发现两处目录(自管 + 本机 ~/.claude):按绝对 installPath 传 SDK,
|
|
@@ -610,10 +693,17 @@ deps, opts = {}) {
|
|
|
610
693
|
const plan = planPending.get(action.value.requestId);
|
|
611
694
|
if (plan) {
|
|
612
695
|
if (action.operatorId !== plan.ownerId) {
|
|
613
|
-
console.log(tag, `[卡片回调] 非发起人 ${action.operatorId}
|
|
696
|
+
console.log(tag, `[卡片回调] 非发起人 ${action.operatorId} 点击计划区 ${action.value.requestId},已忽略`);
|
|
614
697
|
return { toast: { type: 'info', content: '仅任务发起人可操作计划' } };
|
|
615
698
|
}
|
|
616
699
|
const decision = action.value.decision;
|
|
700
|
+
// 「查看完整方案」(#6):callback 触发 send_file 直接发原 md 文件,不消耗挂起项
|
|
701
|
+
if (decision === 'plan-view-file') {
|
|
702
|
+
// 文件在 planAsk 时已落盘;这里直接 send_file(feishuGateway 走 file_type:stream,md 等任意后缀可发)
|
|
703
|
+
void deps.gateway.uploadAndSendFile(plan.chatId, plan.planFilePath)
|
|
704
|
+
.catch((e) => console.error('[查看方案发送失败]', action.value.requestId, e));
|
|
705
|
+
return { toast: { type: 'info', content: '正在发送方案文件…' } };
|
|
706
|
+
}
|
|
617
707
|
if (decision !== 'plan-approve' && decision !== 'plan-revise' && decision !== 'plan-reject') {
|
|
618
708
|
return { toast: { type: 'info', content: '该计划确认已被处理或已过期' } };
|
|
619
709
|
}
|
|
@@ -626,19 +716,20 @@ deps, opts = {}) {
|
|
|
626
716
|
plan.resolve(decision === 'plan-approve' ? { action: 'approve' }
|
|
627
717
|
: decision === 'plan-revise' ? { action: 'revise', feedback }
|
|
628
718
|
: { action: 'reject' });
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
};
|
|
719
|
+
// 清除主进度卡的内嵌计划区(按钮随 setPlan(undefined) 的 flush 消失)
|
|
720
|
+
const prog = activeProgress.get(plan.channelKey);
|
|
721
|
+
if (prog) {
|
|
722
|
+
prog.setPlan(undefined);
|
|
723
|
+
prog.setStatus(PLAN_DECISION_TEXT[decision]);
|
|
724
|
+
}
|
|
725
|
+
return { toast: { type: 'success', content: PLAN_DECISION_TEXT[decision] } };
|
|
636
726
|
}
|
|
637
|
-
//
|
|
727
|
+
// 提问回调分流(#3):qa-pick 仅 PATCH 选中态(不消耗挂起项,等所有题答完才 submit);
|
|
728
|
+
// qa-submit 全部作答后一次性 resolve 并 clearQuestion。主进度卡的 PATCH 由 ProgressCard 内部 flush 串行化
|
|
638
729
|
const qa = qaPending.get(action.value.requestId);
|
|
639
730
|
if (qa) {
|
|
640
731
|
if (action.operatorId !== qa.ownerId) {
|
|
641
|
-
console.log(tag, `[卡片回调] 非发起人 ${action.operatorId}
|
|
732
|
+
console.log(tag, `[卡片回调] 非发起人 ${action.operatorId} 点击提问区 ${action.value.requestId},已忽略`);
|
|
642
733
|
return { toast: { type: 'info', content: '仅任务发起人可作答' } };
|
|
643
734
|
}
|
|
644
735
|
const { decision } = action.value;
|
|
@@ -648,18 +739,16 @@ deps, opts = {}) {
|
|
|
648
739
|
if (!q || !label || !q.options.some((o) => o.label === label)) {
|
|
649
740
|
return { toast: { type: 'info', content: '该选项无效' } };
|
|
650
741
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
void deps.gateway.updateCard(qa.cardId, buildQuestionCard(qa.req, qa.answers)).catch(() => { });
|
|
662
|
-
return { toast: { type: 'success', content: `已选:${label}` } };
|
|
742
|
+
// 切换选中态由 ProgressCard.updateQuestionAnswer 唯一维护(toggle / 至少保留一项);本处只调 PATCH
|
|
743
|
+
const prog = activeProgress.get(qa.channelKey);
|
|
744
|
+
const before = prog?.getQuestionAnswer(action.value.qIndex);
|
|
745
|
+
const willUnpick = !!q.multiSelect && Array.isArray(before) && before.includes(label) && before.length > 1;
|
|
746
|
+
prog?.updateQuestionAnswer(action.value.qIndex, label, !!q.multiSelect);
|
|
747
|
+
// 把 progress 的最新答案同步回 qa.answers 供 submit 校验,避免双 mutate(toggle 重复计数)
|
|
748
|
+
if (prog)
|
|
749
|
+
qa.answers[action.value.qIndex] = prog.getQuestionAnswer(action.value.qIndex)
|
|
750
|
+
?? (q.multiSelect ? [] : label);
|
|
751
|
+
return { toast: { type: 'success', content: willUnpick ? `已取消:${label}` : `已选:${label}` } };
|
|
663
752
|
}
|
|
664
753
|
if (decision === 'qa-submit') {
|
|
665
754
|
const missing = qa.req.questions
|
|
@@ -671,13 +760,13 @@ deps, opts = {}) {
|
|
|
671
760
|
const result = {};
|
|
672
761
|
qa.req.questions.forEach((q, i) => { result[q.question] = qa.answers[i]; });
|
|
673
762
|
qa.resolve(result);
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
};
|
|
763
|
+
// 清除主进度卡的内嵌提问区(按钮随 setQuestion(undefined) 的 flush 消失)
|
|
764
|
+
const prog = activeProgress.get(qa.channelKey);
|
|
765
|
+
if (prog) {
|
|
766
|
+
prog.setQuestion(undefined);
|
|
767
|
+
prog.setStatus('✅ 答案已提交,继续执行…');
|
|
768
|
+
}
|
|
769
|
+
return { toast: { type: 'success', content: '✅ 答案已提交' } };
|
|
681
770
|
}
|
|
682
771
|
return { toast: { type: 'info', content: '该提问已被处理或已过期' } };
|
|
683
772
|
}
|
|
@@ -709,7 +798,21 @@ deps, opts = {}) {
|
|
|
709
798
|
return { toast: { type: 'error', content: retryable ? '处理失败,请重试' : '处理出现异常,决策可能已生效,请勿盲目重试' } };
|
|
710
799
|
}
|
|
711
800
|
}
|
|
712
|
-
return {
|
|
801
|
+
return {
|
|
802
|
+
onMessage: handleIncoming,
|
|
803
|
+
onCardAction: handleCardAction,
|
|
804
|
+
/**
|
|
805
|
+
* 测试/诊断入口:返回通道当前挂起的内嵌 plan / question requestId。
|
|
806
|
+
* 仅用于测试断言与生产期诊断,不影响业务流。channelKey 取 chatId + userId。
|
|
807
|
+
* 内嵌挂起项被 settle/超时清空时对应字段变 undefined
|
|
808
|
+
*/
|
|
809
|
+
_pendingAsk(channelKey) {
|
|
810
|
+
const prog = activeProgress.get(channelKey);
|
|
811
|
+
if (!prog)
|
|
812
|
+
return {};
|
|
813
|
+
return prog.pendingAsk();
|
|
814
|
+
},
|
|
815
|
+
};
|
|
713
816
|
}
|
|
714
817
|
/**
|
|
715
818
|
* 配置热重载器:lcb ws add/remove 写盘后,运行实例下一条消息即见新工作区
|