@vibe-lark/larkpal 0.1.24 → 0.1.26
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/cli.mjs +3 -3
- package/dist/{lark-cli-provider-CdgwmqSz.mjs → lark-cli-provider-CR0eLl0q.mjs} +1 -12
- package/dist/main.mjs +746 -401
- package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
- package/dist/tool-registry-consumer-DrklfqGF.mjs +286 -0
- package/package.json +1 -1
- package/dist/preview-proxy-KMPQK_j4.mjs +0 -505
- /package/dist/{interactive-init-CEVq3afY.mjs → interactive-init-dZe9f9Ne.mjs} +0 -0
package/dist/main.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { t as LarkCliCredentialProvider } from "./lark-cli-provider-CdgwmqSz.mjs";
|
|
2
1
|
import { n as getLarkRuntime, r as setLarkRuntime, t as larkLogger } from "./lark-logger-D7_pEVQc.mjs";
|
|
2
|
+
import { t as LarkCliCredentialProvider } from "./lark-cli-provider-CR0eLl0q.mjs";
|
|
3
|
+
import { t as createToolInvokeProxyRouter } from "./tool-registry-consumer-DrklfqGF.mjs";
|
|
3
4
|
import * as fs from "node:fs";
|
|
4
5
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
6
|
import * as path$1 from "node:path";
|
|
@@ -36,7 +37,7 @@ import { Readable } from "node:stream";
|
|
|
36
37
|
*
|
|
37
38
|
* 本模块在启动时检测它们是否已安装,缺失时给出明确的安装指引。
|
|
38
39
|
*/
|
|
39
|
-
const log$
|
|
40
|
+
const log$31 = larkLogger("preflight");
|
|
40
41
|
function detectCli(name) {
|
|
41
42
|
try {
|
|
42
43
|
const cliPath = execSync(`which ${name}`, {
|
|
@@ -74,12 +75,12 @@ function detectCli(name) {
|
|
|
74
75
|
* 缺失任一依赖时打印安装指引并抛出错误,阻止启动。
|
|
75
76
|
*/
|
|
76
77
|
function preflight() {
|
|
77
|
-
log$
|
|
78
|
+
log$31.info("开始前置环境检查...");
|
|
78
79
|
const claude = detectCli("claude");
|
|
79
80
|
const larkCli = detectCli("lark-cli");
|
|
80
81
|
const status = (dep) => dep.installed ? `✅ ${dep.name} (${dep.version ?? "unknown"}) → ${dep.path}` : `❌ ${dep.name} 未安装`;
|
|
81
|
-
log$
|
|
82
|
-
log$
|
|
82
|
+
log$31.info(` ${status(claude)}`);
|
|
83
|
+
log$31.info(` ${status(larkCli)}`);
|
|
83
84
|
const missing = [];
|
|
84
85
|
if (!claude.installed) missing.push([
|
|
85
86
|
"❌ 未找到 Claude Code (claude)",
|
|
@@ -109,10 +110,10 @@ function preflight() {
|
|
|
109
110
|
"",
|
|
110
111
|
"请安装缺失的依赖后重新启动 LarkPal。"
|
|
111
112
|
].join("\n");
|
|
112
|
-
log$
|
|
113
|
+
log$31.error(msg);
|
|
113
114
|
throw new Error("前置环境检查未通过,缺少必要依赖");
|
|
114
115
|
}
|
|
115
|
-
log$
|
|
116
|
+
log$31.info("前置环境检查通过 ✓");
|
|
116
117
|
return {
|
|
117
118
|
claude,
|
|
118
119
|
larkCli,
|
|
@@ -136,7 +137,7 @@ function preflight() {
|
|
|
136
137
|
* 同时生成新版 (~/.lark-cli/config.json) 和旧版 (~/.config/lark/config.json)
|
|
137
138
|
* 格式的配置文件,保证 lark-cli 无论通过哪种路径读取都能正常工作。
|
|
138
139
|
*/
|
|
139
|
-
const log$
|
|
140
|
+
const log$30 = larkLogger("config/ensure-lark-cli");
|
|
140
141
|
/** 新版 lark-cli 配置路径 */
|
|
141
142
|
const NEW_CONFIG_DIR = join(homedir(), ".lark-cli");
|
|
142
143
|
const NEW_CONFIG_PATH = join(NEW_CONFIG_DIR, "config.json");
|
|
@@ -153,19 +154,19 @@ function ensureLarkCliConfig() {
|
|
|
153
154
|
const appId = process.env.LARK_APP_ID;
|
|
154
155
|
const appSecret = process.env.LARK_APP_SECRET;
|
|
155
156
|
if (!appId || !appSecret) {
|
|
156
|
-
log$
|
|
157
|
+
log$30.info("环境变量凭证未设置,跳过 lark-cli 配置同步");
|
|
157
158
|
return;
|
|
158
159
|
}
|
|
159
160
|
const hasNewConfig = existsSync(NEW_CONFIG_PATH);
|
|
160
161
|
const hasLegacyConfig = existsSync(LEGACY_CONFIG_PATH);
|
|
161
162
|
if (hasNewConfig && hasLegacyConfig) {
|
|
162
|
-
log$
|
|
163
|
+
log$30.info("lark-cli 配置文件已存在,跳过同步", {
|
|
163
164
|
newConfig: NEW_CONFIG_PATH,
|
|
164
165
|
legacyConfig: LEGACY_CONFIG_PATH
|
|
165
166
|
});
|
|
166
167
|
return;
|
|
167
168
|
}
|
|
168
|
-
log$
|
|
169
|
+
log$30.info("检测到环境变量凭证但 lark-cli 配置文件缺失,开始同步", {
|
|
169
170
|
appId,
|
|
170
171
|
hasNewConfig,
|
|
171
172
|
hasLegacyConfig
|
|
@@ -184,9 +185,9 @@ function ensureLarkCliConfig() {
|
|
|
184
185
|
encoding: "utf-8",
|
|
185
186
|
mode: 384
|
|
186
187
|
});
|
|
187
|
-
log$
|
|
188
|
+
log$30.info("已生成新版 lark-cli 配置文件", { path: NEW_CONFIG_PATH });
|
|
188
189
|
} catch (err) {
|
|
189
|
-
log$
|
|
190
|
+
log$30.warn("生成新版 lark-cli 配置文件失败", {
|
|
190
191
|
path: NEW_CONFIG_PATH,
|
|
191
192
|
error: err instanceof Error ? err.message : String(err)
|
|
192
193
|
});
|
|
@@ -205,9 +206,9 @@ function ensureLarkCliConfig() {
|
|
|
205
206
|
encoding: "utf-8",
|
|
206
207
|
mode: 384
|
|
207
208
|
});
|
|
208
|
-
log$
|
|
209
|
+
log$30.info("已生成旧版 lark-cli 配置文件", { path: LEGACY_CONFIG_PATH });
|
|
209
210
|
} catch (err) {
|
|
210
|
-
log$
|
|
211
|
+
log$30.warn("生成旧版 lark-cli 配置文件失败", {
|
|
211
212
|
path: LEGACY_CONFIG_PATH,
|
|
212
213
|
error: err instanceof Error ? err.message : String(err)
|
|
213
214
|
});
|
|
@@ -221,7 +222,7 @@ function ensureLarkCliConfig() {
|
|
|
221
222
|
* 首次启动时检测并生成 ~/.claude/settings.json 和 ~/.claude/CLAUDE.md,
|
|
222
223
|
* 以及确保会话工作目录结构完整。
|
|
223
224
|
*/
|
|
224
|
-
const logger$
|
|
225
|
+
const logger$8 = larkLogger("config/defaults");
|
|
225
226
|
const DEFAULT_SETTINGS = {
|
|
226
227
|
permissions: {
|
|
227
228
|
allow: [
|
|
@@ -344,13 +345,13 @@ async function ensureDefaults() {
|
|
|
344
345
|
const settingsPath = join(claudeDir, "settings.json");
|
|
345
346
|
const claudeMdPath = join(claudeDir, "CLAUDE.md");
|
|
346
347
|
await mkdir(claudeDir, { recursive: true });
|
|
347
|
-
logger$
|
|
348
|
+
logger$8.info("确保 ~/.claude 目录存在", { path: claudeDir });
|
|
348
349
|
await writeFile(settingsPath, JSON.stringify(DEFAULT_SETTINGS, null, 2), "utf-8");
|
|
349
|
-
logger$
|
|
350
|
-
if (await fileExists(claudeMdPath)) logger$
|
|
350
|
+
logger$8.info("settings.json 已同步(强制覆盖)", { path: settingsPath });
|
|
351
|
+
if (await fileExists(claudeMdPath)) logger$8.info("CLAUDE.md 已存在,跳过生成", { path: claudeMdPath });
|
|
351
352
|
else {
|
|
352
353
|
await writeFile(claudeMdPath, DEFAULT_CLAUDE_MD, "utf-8");
|
|
353
|
-
logger$
|
|
354
|
+
logger$8.info("已生成默认 CLAUDE.md", { path: claudeMdPath });
|
|
354
355
|
}
|
|
355
356
|
}
|
|
356
357
|
//#endregion
|
|
@@ -397,7 +398,7 @@ function getToolDisplayName(toolName) {
|
|
|
397
398
|
* - result(data) — 最终结果
|
|
398
399
|
* - parseError(error, rawLine) — 解析错误(不中断流)
|
|
399
400
|
*/
|
|
400
|
-
const log$
|
|
401
|
+
const log$29 = larkLogger("cc-runtime/stream-parser");
|
|
401
402
|
var CCStreamParser = class extends EventEmitter {
|
|
402
403
|
/**
|
|
403
404
|
* 解析一行 NDJSON 文本
|
|
@@ -411,7 +412,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
411
412
|
try {
|
|
412
413
|
msg = JSON.parse(trimmed);
|
|
413
414
|
} catch (err) {
|
|
414
|
-
log$
|
|
415
|
+
log$29.warn("NDJSON 解析失败,跳过该行", {
|
|
415
416
|
error: String(err),
|
|
416
417
|
rawLine: trimmed.slice(0, 200)
|
|
417
418
|
});
|
|
@@ -435,14 +436,14 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
435
436
|
this.handleResult(msg);
|
|
436
437
|
break;
|
|
437
438
|
case "system":
|
|
438
|
-
log$
|
|
439
|
+
log$29.info("收到 system 消息", {
|
|
439
440
|
subtype: msg.subtype,
|
|
440
441
|
detail: JSON.stringify(msg).slice(0, 500)
|
|
441
442
|
});
|
|
442
443
|
this.emit("system", msg);
|
|
443
444
|
break;
|
|
444
445
|
default:
|
|
445
|
-
log$
|
|
446
|
+
log$29.debug("收到未知消息类型,已忽略", { type: msg.type });
|
|
446
447
|
break;
|
|
447
448
|
}
|
|
448
449
|
}
|
|
@@ -461,13 +462,13 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
461
462
|
switch (event.type) {
|
|
462
463
|
case "content_block_start": {
|
|
463
464
|
const block = event.content_block;
|
|
464
|
-
log$
|
|
465
|
+
log$29.debug("content_block_start 事件", {
|
|
465
466
|
blockType: block?.type,
|
|
466
467
|
toolName: block?.name,
|
|
467
468
|
toolUseId: block?.id
|
|
468
469
|
});
|
|
469
470
|
if (block?.type === "tool_use" && block.name) {
|
|
470
|
-
log$
|
|
471
|
+
log$29.info("工具调用开始", {
|
|
471
472
|
toolName: block.name,
|
|
472
473
|
displayName: getToolDisplayName(block.name),
|
|
473
474
|
toolUseId: block.id
|
|
@@ -486,7 +487,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
486
487
|
case "message_delta": {
|
|
487
488
|
const stopReason = event.delta?.stop_reason;
|
|
488
489
|
if (stopReason) {
|
|
489
|
-
log$
|
|
490
|
+
log$29.info("轮次结束", { stopReason });
|
|
490
491
|
this.emit("turnEnd", stopReason);
|
|
491
492
|
}
|
|
492
493
|
break;
|
|
@@ -506,7 +507,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
506
507
|
const content = msg.message?.content;
|
|
507
508
|
const stopReason = msg.message?.stop_reason;
|
|
508
509
|
const blockTypes = Array.isArray(content) ? content.map((b) => `${b.type}${b.name ? ":" + b.name : ""}`).join(", ") : "none";
|
|
509
|
-
log$
|
|
510
|
+
log$29.info("收到完整 assistant 消息", {
|
|
510
511
|
model: msg.message?.model,
|
|
511
512
|
stopReason,
|
|
512
513
|
contentBlocks: content?.length,
|
|
@@ -514,7 +515,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
514
515
|
});
|
|
515
516
|
if (Array.isArray(content)) {
|
|
516
517
|
for (const block of content) if (block.type === "tool_use" && block.name) {
|
|
517
|
-
log$
|
|
518
|
+
log$29.info("从 assistant 消息提取工具调用", {
|
|
518
519
|
toolName: block.name,
|
|
519
520
|
displayName: getToolDisplayName(block.name),
|
|
520
521
|
toolUseId: block.id
|
|
@@ -533,7 +534,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
533
534
|
const content = msg.message?.content;
|
|
534
535
|
if (!Array.isArray(content)) return;
|
|
535
536
|
for (const item of content) if (item.type === "tool_result" && item.tool_use_id) {
|
|
536
|
-
log$
|
|
537
|
+
log$29.debug("工具结果返回", { toolUseId: item.tool_use_id });
|
|
537
538
|
this.emit("toolResult", item.tool_use_id);
|
|
538
539
|
}
|
|
539
540
|
}
|
|
@@ -541,7 +542,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
541
542
|
* 处理 tool_progress 消息 — 工具执行进度
|
|
542
543
|
*/
|
|
543
544
|
handleToolProgress(msg) {
|
|
544
|
-
log$
|
|
545
|
+
log$29.debug("工具执行进度", {
|
|
545
546
|
toolName: msg.tool_name,
|
|
546
547
|
elapsed: msg.elapsed_time_seconds
|
|
547
548
|
});
|
|
@@ -551,7 +552,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
551
552
|
* 处理 result 消息 — CC 执行完成的最终结果
|
|
552
553
|
*/
|
|
553
554
|
handleResult(msg) {
|
|
554
|
-
log$
|
|
555
|
+
log$29.info("CC 执行完成", {
|
|
555
556
|
subtype: msg.subtype,
|
|
556
557
|
isError: msg.is_error,
|
|
557
558
|
durationMs: msg.duration_ms,
|
|
@@ -587,7 +588,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
587
588
|
* - github: { type: "api_key", token }
|
|
588
589
|
* - custom-api: { type: "http_bearer", base_url, token }
|
|
589
590
|
*/
|
|
590
|
-
const log$
|
|
591
|
+
const log$28 = larkLogger("user/credential-vault");
|
|
591
592
|
var CredentialVault = class {
|
|
592
593
|
credentialPath;
|
|
593
594
|
encryptionKey;
|
|
@@ -599,12 +600,12 @@ var CredentialVault = class {
|
|
|
599
600
|
if (keyEnv) {
|
|
600
601
|
this.encryptionKey = keyEnv.length === 64 ? Buffer.from(keyEnv, "hex") : Buffer.from(keyEnv, "base64");
|
|
601
602
|
if (this.encryptionKey.length !== 32) {
|
|
602
|
-
log$
|
|
603
|
+
log$28.error("LARKPAL_ENCRYPTION_KEY 长度不正确,需要 32 字节", { actualLength: this.encryptionKey.length });
|
|
603
604
|
this.encryptionKey = null;
|
|
604
|
-
} else log$
|
|
605
|
+
} else log$28.info("凭证加密已启用");
|
|
605
606
|
} else {
|
|
606
607
|
this.encryptionKey = null;
|
|
607
|
-
log$
|
|
608
|
+
log$28.warn("LARKPAL_ENCRYPTION_KEY 未设置,凭证将以明文存储(仅限开发模式)");
|
|
608
609
|
}
|
|
609
610
|
}
|
|
610
611
|
/**
|
|
@@ -612,7 +613,7 @@ var CredentialVault = class {
|
|
|
612
613
|
*/
|
|
613
614
|
async load() {
|
|
614
615
|
if (!existsSync$1(this.credentialPath)) {
|
|
615
|
-
log$
|
|
616
|
+
log$28.info("凭证文件不存在,使用空凭证", { path: this.credentialPath });
|
|
616
617
|
this.store = {};
|
|
617
618
|
this.loaded = true;
|
|
618
619
|
return;
|
|
@@ -621,12 +622,12 @@ var CredentialVault = class {
|
|
|
621
622
|
const raw = await readFile$1(this.credentialPath, "utf-8");
|
|
622
623
|
this.store = JSON.parse(raw);
|
|
623
624
|
this.loaded = true;
|
|
624
|
-
log$
|
|
625
|
+
log$28.info("凭证文件加载完成", {
|
|
625
626
|
path: this.credentialPath,
|
|
626
627
|
credentialCount: Object.keys(this.store).length
|
|
627
628
|
});
|
|
628
629
|
} catch (err) {
|
|
629
|
-
log$
|
|
630
|
+
log$28.error("凭证文件加载失败", {
|
|
630
631
|
path: this.credentialPath,
|
|
631
632
|
error: err instanceof Error ? err.message : String(err)
|
|
632
633
|
});
|
|
@@ -640,12 +641,12 @@ var CredentialVault = class {
|
|
|
640
641
|
async save() {
|
|
641
642
|
try {
|
|
642
643
|
await writeFile$1(this.credentialPath, JSON.stringify(this.store, null, 2), "utf-8");
|
|
643
|
-
log$
|
|
644
|
+
log$28.info("凭证文件保存完成", {
|
|
644
645
|
path: this.credentialPath,
|
|
645
646
|
credentialCount: Object.keys(this.store).length
|
|
646
647
|
});
|
|
647
648
|
} catch (err) {
|
|
648
|
-
log$
|
|
649
|
+
log$28.error("凭证文件保存失败", {
|
|
649
650
|
path: this.credentialPath,
|
|
650
651
|
error: err instanceof Error ? err.message : String(err)
|
|
651
652
|
});
|
|
@@ -667,7 +668,7 @@ var CredentialVault = class {
|
|
|
667
668
|
*/
|
|
668
669
|
async setCredential(name, credential) {
|
|
669
670
|
if (!this.loaded) await this.load();
|
|
670
|
-
log$
|
|
671
|
+
log$28.info("设置凭证", {
|
|
671
672
|
name,
|
|
672
673
|
fields: Object.keys(credential)
|
|
673
674
|
});
|
|
@@ -683,7 +684,7 @@ var CredentialVault = class {
|
|
|
683
684
|
if (!(name in this.store)) return false;
|
|
684
685
|
delete this.store[name];
|
|
685
686
|
await this.save();
|
|
686
|
-
log$
|
|
687
|
+
log$28.info("凭证已删除", { name });
|
|
687
688
|
return true;
|
|
688
689
|
}
|
|
689
690
|
/**
|
|
@@ -712,7 +713,7 @@ var CredentialVault = class {
|
|
|
712
713
|
envVars[envKey] = String(fieldValue);
|
|
713
714
|
}
|
|
714
715
|
} catch (err) {
|
|
715
|
-
log$
|
|
716
|
+
log$28.warn("凭证解密失败,跳过", {
|
|
716
717
|
name,
|
|
717
718
|
error: err instanceof Error ? err.message : String(err)
|
|
718
719
|
});
|
|
@@ -767,7 +768,7 @@ var CredentialVault = class {
|
|
|
767
768
|
* - B 用户的 CC 进程连接 B 的数据库 MCP Server
|
|
768
769
|
* - 全局工具(lark-cli、文件操作等)所有用户共享
|
|
769
770
|
*/
|
|
770
|
-
const log$
|
|
771
|
+
const log$27 = larkLogger("user/mcp-merge");
|
|
771
772
|
/** 全局 MCP 配置路径 */
|
|
772
773
|
const GLOBAL_MCP_CONFIG_PATH = path.join(os.homedir(), ".claude", "mcp-servers.json");
|
|
773
774
|
/**
|
|
@@ -781,29 +782,29 @@ async function mergeAndWriteMcpConfig(userCtx, sessionId) {
|
|
|
781
782
|
if (existsSync$1(userMcpPath)) try {
|
|
782
783
|
const raw = await readFile$1(userMcpPath, "utf-8");
|
|
783
784
|
userConfig = JSON.parse(raw);
|
|
784
|
-
log$
|
|
785
|
+
log$27.info("用户 MCP 配置加载完成", {
|
|
785
786
|
userId: userCtx.userId,
|
|
786
787
|
serverCount: Object.keys(userConfig).length,
|
|
787
788
|
servers: Object.keys(userConfig)
|
|
788
789
|
});
|
|
789
790
|
} catch (err) {
|
|
790
|
-
log$
|
|
791
|
+
log$27.warn("用户 MCP 配置文件解析失败", {
|
|
791
792
|
userId: userCtx.userId,
|
|
792
793
|
path: userMcpPath,
|
|
793
794
|
error: err instanceof Error ? err.message : String(err)
|
|
794
795
|
});
|
|
795
796
|
}
|
|
796
797
|
if (Object.keys(userConfig).length === 0) {
|
|
797
|
-
log$
|
|
798
|
+
log$27.info("用户无自定义 MCP 配置,使用全局默认", { userId: userCtx.userId });
|
|
798
799
|
return null;
|
|
799
800
|
}
|
|
800
801
|
let globalConfig = {};
|
|
801
802
|
if (existsSync$1(GLOBAL_MCP_CONFIG_PATH)) try {
|
|
802
803
|
const raw = await readFile$1(GLOBAL_MCP_CONFIG_PATH, "utf-8");
|
|
803
804
|
globalConfig = JSON.parse(raw);
|
|
804
|
-
log$
|
|
805
|
+
log$27.info("全局 MCP 配置加载完成", { serverCount: Object.keys(globalConfig).length });
|
|
805
806
|
} catch (err) {
|
|
806
|
-
log$
|
|
807
|
+
log$27.warn("全局 MCP 配置文件解析失败", {
|
|
807
808
|
path: GLOBAL_MCP_CONFIG_PATH,
|
|
808
809
|
error: err instanceof Error ? err.message : String(err)
|
|
809
810
|
});
|
|
@@ -812,7 +813,7 @@ async function mergeAndWriteMcpConfig(userCtx, sessionId) {
|
|
|
812
813
|
...globalConfig,
|
|
813
814
|
...userConfig
|
|
814
815
|
};
|
|
815
|
-
log$
|
|
816
|
+
log$27.info("MCP 配置合并完成", {
|
|
816
817
|
userId: userCtx.userId,
|
|
817
818
|
globalServers: Object.keys(globalConfig),
|
|
818
819
|
userServers: Object.keys(userConfig),
|
|
@@ -820,7 +821,7 @@ async function mergeAndWriteMcpConfig(userCtx, sessionId) {
|
|
|
820
821
|
});
|
|
821
822
|
const tmpPath = path.join(os.tmpdir(), `mcp-${sessionId}.json`);
|
|
822
823
|
await writeFile$1(tmpPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
823
|
-
log$
|
|
824
|
+
log$27.info("MCP 合并配置已写入临时文件", {
|
|
824
825
|
path: tmpPath,
|
|
825
826
|
serverCount: Object.keys(merged).length
|
|
826
827
|
});
|
|
@@ -834,7 +835,7 @@ async function cleanupMcpConfig(sessionId) {
|
|
|
834
835
|
try {
|
|
835
836
|
if (existsSync$1(tmpPath)) {
|
|
836
837
|
await unlink$1(tmpPath);
|
|
837
|
-
log$
|
|
838
|
+
log$27.debug("MCP 临时配置文件已清理", { path: tmpPath });
|
|
838
839
|
}
|
|
839
840
|
} catch {}
|
|
840
841
|
}
|
|
@@ -854,7 +855,7 @@ async function cleanupMcpConfig(sessionId) {
|
|
|
854
855
|
* 多轮对话:进程内自动保持完整对话上下文(mutableMessages 数组累积),
|
|
855
856
|
* 无需外部维护历史。
|
|
856
857
|
*/
|
|
857
|
-
const log$
|
|
858
|
+
const log$26 = larkLogger("cc-runtime/process-manager");
|
|
858
859
|
/**
|
|
859
860
|
* LarkPal 会话 UUID v5 命名空间
|
|
860
861
|
*
|
|
@@ -963,9 +964,9 @@ var SessionProcessManager = class {
|
|
|
963
964
|
const t0 = Date.now();
|
|
964
965
|
const prevLock = this.sessionLocks.get(sessionId);
|
|
965
966
|
if (prevLock) {
|
|
966
|
-
log$
|
|
967
|
+
log$26.info("等待上一条消息处理完成", { sessionId });
|
|
967
968
|
await prevLock;
|
|
968
|
-
log$
|
|
969
|
+
log$26.info("[perf] 串行锁等待完成", {
|
|
969
970
|
sessionId,
|
|
970
971
|
waitMs: Date.now() - t0
|
|
971
972
|
});
|
|
@@ -975,7 +976,7 @@ var SessionProcessManager = class {
|
|
|
975
976
|
this.activeCallbacks.set(sessionId, callbacks);
|
|
976
977
|
const existing = this.processes.get(sessionId);
|
|
977
978
|
if (existing && existing.status === "running" && existing.useStreamInput) {
|
|
978
|
-
log$
|
|
979
|
+
log$26.info("向常驻进程发送消息", {
|
|
979
980
|
sessionId,
|
|
980
981
|
promptLength: config.prompt.length,
|
|
981
982
|
pid: existing.childProcess.pid,
|
|
@@ -984,14 +985,14 @@ var SessionProcessManager = class {
|
|
|
984
985
|
this.sendMessage(sessionId, config.prompt);
|
|
985
986
|
this.resetIdleTimer(sessionId);
|
|
986
987
|
await completionPromise;
|
|
987
|
-
log$
|
|
988
|
+
log$26.info("[perf] executePrompt 完成(热进程)", {
|
|
988
989
|
sessionId,
|
|
989
990
|
totalMs: Date.now() - t0
|
|
990
991
|
});
|
|
991
992
|
return;
|
|
992
993
|
}
|
|
993
994
|
if (existing && (existing.status === "stopped" || existing.status === "crashed")) {
|
|
994
|
-
log$
|
|
995
|
+
log$26.info("清理已终止的旧进程,准备重启", {
|
|
995
996
|
sessionId,
|
|
996
997
|
oldStatus: existing.status
|
|
997
998
|
});
|
|
@@ -999,18 +1000,18 @@ var SessionProcessManager = class {
|
|
|
999
1000
|
this.sessionLocks.set(sessionId, completionPromise);
|
|
1000
1001
|
}
|
|
1001
1002
|
if (existing && existing.status === "running" && !existing.useStreamInput) {
|
|
1002
|
-
log$
|
|
1003
|
+
log$26.warn("停止旧的单次进程后重新启动为常驻模式", { sessionId });
|
|
1003
1004
|
await this.stopProcess(sessionId);
|
|
1004
1005
|
this.sessionLocks.set(sessionId, completionPromise);
|
|
1005
1006
|
}
|
|
1006
1007
|
const tStart = Date.now();
|
|
1007
1008
|
await this.startPersistentProcess(config);
|
|
1008
|
-
log$
|
|
1009
|
+
log$26.info("[perf] 冷启动进程完成", {
|
|
1009
1010
|
sessionId,
|
|
1010
1011
|
coldStartMs: Date.now() - tStart
|
|
1011
1012
|
});
|
|
1012
1013
|
await completionPromise;
|
|
1013
|
-
log$
|
|
1014
|
+
log$26.info("[perf] executePrompt 完成(冷启动)", {
|
|
1014
1015
|
sessionId,
|
|
1015
1016
|
totalMs: Date.now() - t0
|
|
1016
1017
|
});
|
|
@@ -1044,14 +1045,14 @@ var SessionProcessManager = class {
|
|
|
1044
1045
|
const mcpConfigPath = await mergeAndWriteMcpConfig(config.userContext, sessionId);
|
|
1045
1046
|
if (mcpConfigPath) {
|
|
1046
1047
|
args.push("--mcp-config", mcpConfigPath);
|
|
1047
|
-
log$
|
|
1048
|
+
log$26.info("MCP 配置已合并并注入到 CC 进程", {
|
|
1048
1049
|
sessionId,
|
|
1049
1050
|
userId: config.userContext.userId,
|
|
1050
1051
|
mcpConfigPath
|
|
1051
1052
|
});
|
|
1052
1053
|
}
|
|
1053
1054
|
} catch (err) {
|
|
1054
|
-
log$
|
|
1055
|
+
log$26.warn("MCP 配置合并失败,CC 进程将使用全局默认配置", {
|
|
1055
1056
|
sessionId,
|
|
1056
1057
|
error: err instanceof Error ? err.message : String(err)
|
|
1057
1058
|
});
|
|
@@ -1076,12 +1077,12 @@ var SessionProcessManager = class {
|
|
|
1076
1077
|
if (larkCred?.user_access_token && typeof larkCred.user_access_token === "string") processEnv.LARK_USER_ACCESS_TOKEN = larkCred.user_access_token;
|
|
1077
1078
|
} else {
|
|
1078
1079
|
delete processEnv.LARK_USER_ACCESS_TOKEN;
|
|
1079
|
-
log$
|
|
1080
|
+
log$26.info("LARKPAL_DISABLE_USER_AUTH 已启用,跳过飞书 user_access_token 注入", {
|
|
1080
1081
|
sessionId,
|
|
1081
1082
|
userId: userContext.userId
|
|
1082
1083
|
});
|
|
1083
1084
|
}
|
|
1084
|
-
log$
|
|
1085
|
+
log$26.info("用户凭证已注入到 CC 进程环境", {
|
|
1085
1086
|
sessionId,
|
|
1086
1087
|
userId: userContext.userId,
|
|
1087
1088
|
credentialCount: Object.keys(credentialEnvVars).length,
|
|
@@ -1089,14 +1090,14 @@ var SessionProcessManager = class {
|
|
|
1089
1090
|
disableUserAuth
|
|
1090
1091
|
});
|
|
1091
1092
|
} catch (err) {
|
|
1092
|
-
log$
|
|
1093
|
+
log$26.warn("用户凭证加载失败,CC 进程将使用默认环境", {
|
|
1093
1094
|
sessionId,
|
|
1094
1095
|
userId: userContext.userId,
|
|
1095
1096
|
error: err instanceof Error ? err.message : String(err)
|
|
1096
1097
|
});
|
|
1097
1098
|
}
|
|
1098
1099
|
}
|
|
1099
|
-
log$
|
|
1100
|
+
log$26.info("启动常驻 CC 进程", {
|
|
1100
1101
|
sessionId,
|
|
1101
1102
|
claudeSessionId,
|
|
1102
1103
|
isResuming,
|
|
@@ -1136,7 +1137,7 @@ var SessionProcessManager = class {
|
|
|
1136
1137
|
if (pending) {
|
|
1137
1138
|
clearTimeout(pending.timer);
|
|
1138
1139
|
this.pendingControlRequests.delete(msg.response.request_id);
|
|
1139
|
-
log$
|
|
1140
|
+
log$26.info("收到 control_response", {
|
|
1140
1141
|
sessionId,
|
|
1141
1142
|
requestId: msg.response.request_id,
|
|
1142
1143
|
subtype: msg.response.subtype,
|
|
@@ -1153,7 +1154,7 @@ var SessionProcessManager = class {
|
|
|
1153
1154
|
if (child.stderr) createInterface({ input: child.stderr }).on("line", (line) => {
|
|
1154
1155
|
if (line.includes("no stdin data received")) return;
|
|
1155
1156
|
if (line.toLowerCase().includes("already in use")) stderrSessionInUse = true;
|
|
1156
|
-
log$
|
|
1157
|
+
log$26.warn("CC 进程 stderr", {
|
|
1157
1158
|
sessionId,
|
|
1158
1159
|
line: line.slice(0, 500)
|
|
1159
1160
|
});
|
|
@@ -1162,7 +1163,7 @@ var SessionProcessManager = class {
|
|
|
1162
1163
|
const exitCode = code ?? -1;
|
|
1163
1164
|
const exitSignal = signal ?? "none";
|
|
1164
1165
|
if (exitCode === 1 && stderrSessionInUse && !isResuming) {
|
|
1165
|
-
log$
|
|
1166
|
+
log$26.info("Session ID 已被占用,准备使用 --resume 模式重试", {
|
|
1166
1167
|
sessionId,
|
|
1167
1168
|
claudeSessionId
|
|
1168
1169
|
});
|
|
@@ -1175,21 +1176,21 @@ var SessionProcessManager = class {
|
|
|
1175
1176
|
});
|
|
1176
1177
|
return;
|
|
1177
1178
|
} catch (retryErr) {
|
|
1178
|
-
log$
|
|
1179
|
+
log$26.error("Session ID 重试失败", {
|
|
1179
1180
|
sessionId,
|
|
1180
1181
|
error: retryErr instanceof Error ? retryErr.message : String(retryErr)
|
|
1181
1182
|
});
|
|
1182
1183
|
}
|
|
1183
1184
|
}
|
|
1184
1185
|
if (exitCode === 0) {
|
|
1185
|
-
log$
|
|
1186
|
+
log$26.info("CC 常驻进程正常退出", {
|
|
1186
1187
|
sessionId,
|
|
1187
1188
|
exitCode,
|
|
1188
1189
|
signal: exitSignal
|
|
1189
1190
|
});
|
|
1190
1191
|
ccProcess.status = "stopped";
|
|
1191
1192
|
} else {
|
|
1192
|
-
log$
|
|
1193
|
+
log$26.error("CC 常驻进程异常退出", {
|
|
1193
1194
|
sessionId,
|
|
1194
1195
|
exitCode,
|
|
1195
1196
|
signal: exitSignal
|
|
@@ -1201,7 +1202,7 @@ var SessionProcessManager = class {
|
|
|
1201
1202
|
this.cleanup(sessionId);
|
|
1202
1203
|
});
|
|
1203
1204
|
child.on("error", (err) => {
|
|
1204
|
-
log$
|
|
1205
|
+
log$26.error("CC 常驻进程启动失败", {
|
|
1205
1206
|
sessionId,
|
|
1206
1207
|
error: err.message
|
|
1207
1208
|
});
|
|
@@ -1211,7 +1212,7 @@ var SessionProcessManager = class {
|
|
|
1211
1212
|
this.cleanup(sessionId);
|
|
1212
1213
|
});
|
|
1213
1214
|
ccProcess.status = "running";
|
|
1214
|
-
log$
|
|
1215
|
+
log$26.info("CC 常驻进程已启动", {
|
|
1215
1216
|
sessionId,
|
|
1216
1217
|
pid: child.pid,
|
|
1217
1218
|
isResuming
|
|
@@ -1239,7 +1240,7 @@ var SessionProcessManager = class {
|
|
|
1239
1240
|
parser.on("textDelta", (text) => {
|
|
1240
1241
|
if (!firstTokenLogged) {
|
|
1241
1242
|
firstTokenLogged = true;
|
|
1242
|
-
log$
|
|
1243
|
+
log$26.info("[perf] 首 token (text)", {
|
|
1243
1244
|
sessionId,
|
|
1244
1245
|
ttftMs: Date.now() - messageSentAt
|
|
1245
1246
|
});
|
|
@@ -1249,7 +1250,7 @@ var SessionProcessManager = class {
|
|
|
1249
1250
|
parser.on("thinkingDelta", (text) => {
|
|
1250
1251
|
if (!firstTokenLogged) {
|
|
1251
1252
|
firstTokenLogged = true;
|
|
1252
|
-
log$
|
|
1253
|
+
log$26.info("[perf] 首 token (thinking)", {
|
|
1253
1254
|
sessionId,
|
|
1254
1255
|
ttftMs: Date.now() - messageSentAt
|
|
1255
1256
|
});
|
|
@@ -1266,7 +1267,7 @@ var SessionProcessManager = class {
|
|
|
1266
1267
|
getCallbacks()?.onToolProgress?.(toolName, elapsedSeconds);
|
|
1267
1268
|
});
|
|
1268
1269
|
parser.on("turnEnd", (stopReason) => {
|
|
1269
|
-
log$
|
|
1270
|
+
log$26.info("[perf] turnEnd", {
|
|
1270
1271
|
sessionId,
|
|
1271
1272
|
stopReason,
|
|
1272
1273
|
sinceMessageMs: Date.now() - messageSentAt
|
|
@@ -1274,7 +1275,7 @@ var SessionProcessManager = class {
|
|
|
1274
1275
|
getCallbacks()?.onTurnEnd?.(stopReason);
|
|
1275
1276
|
});
|
|
1276
1277
|
parser.on("result", (result) => {
|
|
1277
|
-
log$
|
|
1278
|
+
log$26.info("[perf] result 事件", {
|
|
1278
1279
|
sessionId,
|
|
1279
1280
|
sinceMessageMs: Date.now() - messageSentAt
|
|
1280
1281
|
});
|
|
@@ -1282,7 +1283,7 @@ var SessionProcessManager = class {
|
|
|
1282
1283
|
this.resolveMessageCompletion(sessionId);
|
|
1283
1284
|
});
|
|
1284
1285
|
parser.on("parseError", (error, rawLine) => {
|
|
1285
|
-
log$
|
|
1286
|
+
log$26.warn("流解析错误", {
|
|
1286
1287
|
sessionId,
|
|
1287
1288
|
error: error.message,
|
|
1288
1289
|
rawLine: rawLine.slice(0, 200)
|
|
@@ -1298,7 +1299,7 @@ var SessionProcessManager = class {
|
|
|
1298
1299
|
sendMessage(sessionId, message) {
|
|
1299
1300
|
const proc = this.processes.get(sessionId);
|
|
1300
1301
|
if (!proc || !proc.childProcess.stdin) {
|
|
1301
|
-
log$
|
|
1302
|
+
log$26.error("无法发送消息:进程不存在或 stdin 不可用", { sessionId });
|
|
1302
1303
|
return;
|
|
1303
1304
|
}
|
|
1304
1305
|
proc._resetPerfTimer?.();
|
|
@@ -1314,7 +1315,7 @@ var SessionProcessManager = class {
|
|
|
1314
1315
|
uuid: userMessageId
|
|
1315
1316
|
});
|
|
1316
1317
|
const messageLength = typeof message === "string" ? message.length : message.length;
|
|
1317
|
-
log$
|
|
1318
|
+
log$26.info("通过 stdin 发送用户消息", {
|
|
1318
1319
|
sessionId,
|
|
1319
1320
|
messageLength,
|
|
1320
1321
|
isMultimodal: Array.isArray(message),
|
|
@@ -1323,7 +1324,7 @@ var SessionProcessManager = class {
|
|
|
1323
1324
|
});
|
|
1324
1325
|
proc.childProcess.stdin.write(payload + "\n", (err) => {
|
|
1325
1326
|
if (err) {
|
|
1326
|
-
log$
|
|
1327
|
+
log$26.error("stdin 写入失败", {
|
|
1327
1328
|
sessionId,
|
|
1328
1329
|
error: err.message
|
|
1329
1330
|
});
|
|
@@ -1353,7 +1354,7 @@ var SessionProcessManager = class {
|
|
|
1353
1354
|
request_id: requestId,
|
|
1354
1355
|
request
|
|
1355
1356
|
});
|
|
1356
|
-
log$
|
|
1357
|
+
log$26.info("发送控制请求", {
|
|
1357
1358
|
sessionId,
|
|
1358
1359
|
subtype,
|
|
1359
1360
|
requestId,
|
|
@@ -1405,16 +1406,16 @@ var SessionProcessManager = class {
|
|
|
1405
1406
|
async ensureProcessForRewind(sessionId) {
|
|
1406
1407
|
const existing = this.processes.get(sessionId);
|
|
1407
1408
|
if (existing && existing.status === "running") {
|
|
1408
|
-
log$
|
|
1409
|
+
log$26.info("ensureProcessForRewind: 进程已在运行中,直接使用", { sessionId });
|
|
1409
1410
|
return existing;
|
|
1410
1411
|
}
|
|
1411
1412
|
const cwd = this.sessionCwdCache.get(sessionId);
|
|
1412
1413
|
if (!cwd) {
|
|
1413
|
-
log$
|
|
1414
|
+
log$26.warn("ensureProcessForRewind: 无法获取会话 cwd", { sessionId });
|
|
1414
1415
|
return;
|
|
1415
1416
|
}
|
|
1416
1417
|
if (existing) this.cleanup(sessionId);
|
|
1417
|
-
log$
|
|
1418
|
+
log$26.info("ensureProcessForRewind: 启动临时进程用于 rewind", {
|
|
1418
1419
|
sessionId,
|
|
1419
1420
|
cwd
|
|
1420
1421
|
});
|
|
@@ -1425,7 +1426,7 @@ var SessionProcessManager = class {
|
|
|
1425
1426
|
}, { skipInitialMessage: true });
|
|
1426
1427
|
const proc = this.processes.get(sessionId);
|
|
1427
1428
|
if (!proc || proc.status !== "running") {
|
|
1428
|
-
log$
|
|
1429
|
+
log$26.warn("ensureProcessForRewind: 进程启动失败", {
|
|
1429
1430
|
sessionId,
|
|
1430
1431
|
status: proc?.status
|
|
1431
1432
|
});
|
|
@@ -1447,11 +1448,11 @@ var SessionProcessManager = class {
|
|
|
1447
1448
|
async stopProcess(sessionId) {
|
|
1448
1449
|
const proc = this.processes.get(sessionId);
|
|
1449
1450
|
if (!proc) {
|
|
1450
|
-
log$
|
|
1451
|
+
log$26.warn("尝试停止不存在的进程", { sessionId });
|
|
1451
1452
|
return;
|
|
1452
1453
|
}
|
|
1453
1454
|
if (proc.status === "stopped" || proc.status === "crashed") {
|
|
1454
|
-
log$
|
|
1455
|
+
log$26.info("进程已经停止,直接清理", {
|
|
1455
1456
|
sessionId,
|
|
1456
1457
|
status: proc.status
|
|
1457
1458
|
});
|
|
@@ -1459,7 +1460,7 @@ var SessionProcessManager = class {
|
|
|
1459
1460
|
return;
|
|
1460
1461
|
}
|
|
1461
1462
|
proc.status = "stopping";
|
|
1462
|
-
log$
|
|
1463
|
+
log$26.info("正在停止 CC 常驻进程", {
|
|
1463
1464
|
sessionId,
|
|
1464
1465
|
pid: proc.childProcess.pid
|
|
1465
1466
|
});
|
|
@@ -1475,7 +1476,7 @@ var SessionProcessManager = class {
|
|
|
1475
1476
|
const child = proc.childProcess;
|
|
1476
1477
|
const forceKillTimer = setTimeout(() => {
|
|
1477
1478
|
if (!child.killed) {
|
|
1478
|
-
log$
|
|
1479
|
+
log$26.warn("CC 常驻进程未在规定时间内退出,强制终止", { sessionId });
|
|
1479
1480
|
child.kill("SIGKILL");
|
|
1480
1481
|
}
|
|
1481
1482
|
}, GRACEFUL_SHUTDOWN_MS);
|
|
@@ -1495,9 +1496,9 @@ var SessionProcessManager = class {
|
|
|
1495
1496
|
async stopAll() {
|
|
1496
1497
|
const sessionIds = Array.from(this.processes.keys());
|
|
1497
1498
|
if (sessionIds.length === 0) return;
|
|
1498
|
-
log$
|
|
1499
|
+
log$26.info("正在停止所有 CC 常驻进程", { count: sessionIds.length });
|
|
1499
1500
|
await Promise.all(sessionIds.map((id) => this.stopProcess(id)));
|
|
1500
|
-
log$
|
|
1501
|
+
log$26.info("所有 CC 常驻进程已停止");
|
|
1501
1502
|
}
|
|
1502
1503
|
/**
|
|
1503
1504
|
* 重置空闲计时器
|
|
@@ -1507,7 +1508,7 @@ var SessionProcessManager = class {
|
|
|
1507
1508
|
if (existing) clearTimeout(existing);
|
|
1508
1509
|
if (IDLE_TIMEOUT_MS <= 0) return;
|
|
1509
1510
|
const timer = setTimeout(() => {
|
|
1510
|
-
log$
|
|
1511
|
+
log$26.info("CC 常驻进程空闲超时,自动停止", {
|
|
1511
1512
|
sessionId,
|
|
1512
1513
|
timeoutMs: IDLE_TIMEOUT_MS
|
|
1513
1514
|
});
|
|
@@ -1535,7 +1536,7 @@ var SessionProcessManager = class {
|
|
|
1535
1536
|
this.idleTimers.delete(sessionId);
|
|
1536
1537
|
}
|
|
1537
1538
|
cleanupMcpConfig(sessionId);
|
|
1538
|
-
log$
|
|
1539
|
+
log$26.debug("已清理常驻进程状态", { sessionId });
|
|
1539
1540
|
}
|
|
1540
1541
|
/**
|
|
1541
1542
|
* 将 cwd 编码为 CC 的 projects 子目录名
|
|
@@ -1564,7 +1565,7 @@ var SessionProcessManager = class {
|
|
|
1564
1565
|
const cwdEncoded = this.encodeCwdForCC(cwd);
|
|
1565
1566
|
const sessionFile = join(homedir(), ".claude", "projects", cwdEncoded, `${claudeSessionId}.jsonl`);
|
|
1566
1567
|
const exists = existsSync(sessionFile);
|
|
1567
|
-
log$
|
|
1568
|
+
log$26.debug("检查 CC session 文件", {
|
|
1568
1569
|
cwd,
|
|
1569
1570
|
cwdEncoded,
|
|
1570
1571
|
claudeSessionId,
|
|
@@ -1573,14 +1574,14 @@ var SessionProcessManager = class {
|
|
|
1573
1574
|
});
|
|
1574
1575
|
return exists;
|
|
1575
1576
|
} catch (err) {
|
|
1576
|
-
log$
|
|
1577
|
+
log$26.warn("检查 CC session 文件失败,默认为首次创建", { error: err instanceof Error ? err.message : String(err) });
|
|
1577
1578
|
return false;
|
|
1578
1579
|
}
|
|
1579
1580
|
}
|
|
1580
1581
|
};
|
|
1581
1582
|
//#endregion
|
|
1582
1583
|
//#region src/runtime/claude-code-adapter.ts
|
|
1583
|
-
const log$
|
|
1584
|
+
const log$25 = larkLogger("runtime/claude-code-adapter");
|
|
1584
1585
|
var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
1585
1586
|
name = "claude-code";
|
|
1586
1587
|
processManager;
|
|
@@ -1595,7 +1596,7 @@ var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
|
1595
1596
|
constructor() {
|
|
1596
1597
|
this.processManager = new SessionProcessManager();
|
|
1597
1598
|
ClaudeCodeAdapter._instance = this;
|
|
1598
|
-
log$
|
|
1599
|
+
log$25.info("ClaudeCodeAdapter 初始化完成");
|
|
1599
1600
|
}
|
|
1600
1601
|
/**
|
|
1601
1602
|
* 获取底层 SessionProcessManager 实例
|
|
@@ -1607,7 +1608,7 @@ var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
|
1607
1608
|
return this.processManager;
|
|
1608
1609
|
}
|
|
1609
1610
|
async executePrompt(config, callbacks) {
|
|
1610
|
-
log$
|
|
1611
|
+
log$25.info("executePrompt via ClaudeCodeAdapter", {
|
|
1611
1612
|
sessionId: config.sessionId,
|
|
1612
1613
|
cwd: config.cwd,
|
|
1613
1614
|
promptLength: config.prompt.length,
|
|
@@ -1616,11 +1617,11 @@ var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
|
1616
1617
|
await this.processManager.executePrompt(config, callbacks);
|
|
1617
1618
|
}
|
|
1618
1619
|
async stopProcess(sessionId) {
|
|
1619
|
-
log$
|
|
1620
|
+
log$25.info("stopProcess via ClaudeCodeAdapter", { sessionId });
|
|
1620
1621
|
await this.processManager.stopProcess(sessionId);
|
|
1621
1622
|
}
|
|
1622
1623
|
async stopAll() {
|
|
1623
|
-
log$
|
|
1624
|
+
log$25.info("stopAll via ClaudeCodeAdapter");
|
|
1624
1625
|
await this.processManager.stopAll();
|
|
1625
1626
|
}
|
|
1626
1627
|
getProcessInfo(sessionId) {
|
|
@@ -1657,7 +1658,7 @@ var ClaudeCodeAdapter = class ClaudeCodeAdapter {
|
|
|
1657
1658
|
* 群聊 → /workspace/chats/group_{chat_id}/
|
|
1658
1659
|
* 话题 → /workspace/chats/group_{chat_id}/topics/{thread_id}/
|
|
1659
1660
|
*/
|
|
1660
|
-
const logger$
|
|
1661
|
+
const logger$7 = larkLogger("routing/session-router");
|
|
1661
1662
|
const SESSION_OUTPUT_RULES = [
|
|
1662
1663
|
`## 内容输出优先级`,
|
|
1663
1664
|
"",
|
|
@@ -1696,7 +1697,7 @@ var SessionRouter = class {
|
|
|
1696
1697
|
cwd: path.join(this.workspaceRoot, "chats", `p2p_${safeId}`),
|
|
1697
1698
|
type: "p2p"
|
|
1698
1699
|
};
|
|
1699
|
-
logger$
|
|
1700
|
+
logger$7.debug("解析私聊路由", {
|
|
1700
1701
|
chatId,
|
|
1701
1702
|
userId: safeId,
|
|
1702
1703
|
sessionId: route.sessionId,
|
|
@@ -1712,7 +1713,7 @@ var SessionRouter = class {
|
|
|
1712
1713
|
cwd: path.join(groupCwd, "topics", threadId),
|
|
1713
1714
|
type: "topic"
|
|
1714
1715
|
};
|
|
1715
|
-
logger$
|
|
1716
|
+
logger$7.debug("解析话题路由", {
|
|
1716
1717
|
chatId,
|
|
1717
1718
|
threadId,
|
|
1718
1719
|
sessionId: route.sessionId,
|
|
@@ -1725,7 +1726,7 @@ var SessionRouter = class {
|
|
|
1725
1726
|
cwd: groupCwd,
|
|
1726
1727
|
type: "group"
|
|
1727
1728
|
};
|
|
1728
|
-
logger$
|
|
1729
|
+
logger$7.debug("解析群聊路由", {
|
|
1729
1730
|
chatId,
|
|
1730
1731
|
sessionId: route.sessionId,
|
|
1731
1732
|
cwd: route.cwd
|
|
@@ -1739,7 +1740,7 @@ var SessionRouter = class {
|
|
|
1739
1740
|
* 此方法是幂等的,多次调用不会报错也不会覆盖已有文件。
|
|
1740
1741
|
*/
|
|
1741
1742
|
async ensureSessionDirectory(route) {
|
|
1742
|
-
logger$
|
|
1743
|
+
logger$7.info("确保会话目录存在", {
|
|
1743
1744
|
sessionId: route.sessionId,
|
|
1744
1745
|
cwd: route.cwd
|
|
1745
1746
|
});
|
|
@@ -1749,7 +1750,7 @@ var SessionRouter = class {
|
|
|
1749
1750
|
const claudeMdPath = path.join(route.cwd, "CLAUDE.md");
|
|
1750
1751
|
if (!existsSync$1(claudeMdPath)) {
|
|
1751
1752
|
await writeFile$1(claudeMdPath, this.generateClaudeMd(route), "utf-8");
|
|
1752
|
-
logger$
|
|
1753
|
+
logger$7.info("创建会话级 CLAUDE.md", {
|
|
1753
1754
|
path: claudeMdPath,
|
|
1754
1755
|
type: route.type
|
|
1755
1756
|
});
|
|
@@ -1813,7 +1814,7 @@ var SessionRouter = class {
|
|
|
1813
1814
|
*
|
|
1814
1815
|
* 内部通过 taskStore 维护所有任务的生命周期状态。
|
|
1815
1816
|
*/
|
|
1816
|
-
const logger$
|
|
1817
|
+
const logger$6 = larkLogger("gateway/execute");
|
|
1817
1818
|
/** 全局任务存储:taskId → TaskInfo */
|
|
1818
1819
|
const taskStore = /* @__PURE__ */ new Map();
|
|
1819
1820
|
/**
|
|
@@ -1847,7 +1848,7 @@ function createExecuteRouter(processManager) {
|
|
|
1847
1848
|
*/
|
|
1848
1849
|
async function handleExecute(req, res, processManager) {
|
|
1849
1850
|
const body = req.body;
|
|
1850
|
-
logger$
|
|
1851
|
+
logger$6.info("收到执行请求", {
|
|
1851
1852
|
session_id: body.session_id,
|
|
1852
1853
|
cwd: body.cwd,
|
|
1853
1854
|
prompt: body.prompt?.slice(0, 200),
|
|
@@ -1856,7 +1857,7 @@ async function handleExecute(req, res, processManager) {
|
|
|
1856
1857
|
max_budget_usd: body.max_budget_usd
|
|
1857
1858
|
});
|
|
1858
1859
|
if (!body.session_id || !body.cwd || !body.prompt) {
|
|
1859
|
-
logger$
|
|
1860
|
+
logger$6.warn("执行请求参数缺失", {
|
|
1860
1861
|
hasSessionId: !!body.session_id,
|
|
1861
1862
|
hasCwd: !!body.cwd,
|
|
1862
1863
|
hasPrompt: !!body.prompt
|
|
@@ -1876,7 +1877,7 @@ async function handleExecute(req, res, processManager) {
|
|
|
1876
1877
|
createdAt: /* @__PURE__ */ new Date()
|
|
1877
1878
|
};
|
|
1878
1879
|
taskStore.set(taskId, taskInfo);
|
|
1879
|
-
logger$
|
|
1880
|
+
logger$6.info("任务已创建", {
|
|
1880
1881
|
taskId,
|
|
1881
1882
|
sessionId: body.session_id,
|
|
1882
1883
|
mode
|
|
@@ -1892,7 +1893,7 @@ async function handleExecute(req, res, processManager) {
|
|
|
1892
1893
|
const callbacks = createTaskCallbacks(taskId);
|
|
1893
1894
|
processManager.executePrompt(config, callbacks).catch((err) => {
|
|
1894
1895
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1895
|
-
logger$
|
|
1896
|
+
logger$6.error("异步任务执行异常", {
|
|
1896
1897
|
taskId,
|
|
1897
1898
|
error: errorMsg
|
|
1898
1899
|
});
|
|
@@ -1900,7 +1901,7 @@ async function handleExecute(req, res, processManager) {
|
|
|
1900
1901
|
taskInfo.error = errorMsg;
|
|
1901
1902
|
taskInfo.completedAt = /* @__PURE__ */ new Date();
|
|
1902
1903
|
});
|
|
1903
|
-
logger$
|
|
1904
|
+
logger$6.info("异步任务已启动,立即返回", { taskId });
|
|
1904
1905
|
res.status(202).json({
|
|
1905
1906
|
task_id: taskId,
|
|
1906
1907
|
status: "running"
|
|
@@ -1908,8 +1909,8 @@ async function handleExecute(req, res, processManager) {
|
|
|
1908
1909
|
return;
|
|
1909
1910
|
}
|
|
1910
1911
|
try {
|
|
1911
|
-
const result = await executeAndWaitResult(taskId, config, processManager);
|
|
1912
|
-
logger$
|
|
1912
|
+
const result = await executeAndWaitResult$1(taskId, config, processManager);
|
|
1913
|
+
logger$6.info("同步任务执行完成", {
|
|
1913
1914
|
taskId,
|
|
1914
1915
|
resultSubtype: result?.subtype
|
|
1915
1916
|
});
|
|
@@ -1920,7 +1921,7 @@ async function handleExecute(req, res, processManager) {
|
|
|
1920
1921
|
});
|
|
1921
1922
|
} catch (err) {
|
|
1922
1923
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1923
|
-
logger$
|
|
1924
|
+
logger$6.error("同步任务执行失败", {
|
|
1924
1925
|
taskId,
|
|
1925
1926
|
error: errorMsg
|
|
1926
1927
|
});
|
|
@@ -1938,12 +1939,12 @@ async function handleExecute(req, res, processManager) {
|
|
|
1938
1939
|
*/
|
|
1939
1940
|
async function handleBatchExecute(req, res, processManager) {
|
|
1940
1941
|
const body = req.body;
|
|
1941
|
-
logger$
|
|
1942
|
+
logger$6.info("收到批量执行请求", {
|
|
1942
1943
|
taskCount: body.tasks?.length,
|
|
1943
1944
|
concurrency: body.concurrency
|
|
1944
1945
|
});
|
|
1945
1946
|
if (!body.tasks || !Array.isArray(body.tasks) || body.tasks.length === 0) {
|
|
1946
|
-
logger$
|
|
1947
|
+
logger$6.warn("批量执行请求参数缺失: tasks 为空或格式错误");
|
|
1947
1948
|
res.status(400).json({
|
|
1948
1949
|
error: "Bad Request",
|
|
1949
1950
|
message: "Missing or empty required field: tasks"
|
|
@@ -1953,7 +1954,7 @@ async function handleBatchExecute(req, res, processManager) {
|
|
|
1953
1954
|
for (let i = 0; i < body.tasks.length; i++) {
|
|
1954
1955
|
const task = body.tasks[i];
|
|
1955
1956
|
if (!task?.session_id || !task?.cwd || !task?.prompt) {
|
|
1956
|
-
logger$
|
|
1957
|
+
logger$6.warn("批量执行子任务参数缺失", { index: i });
|
|
1957
1958
|
res.status(400).json({
|
|
1958
1959
|
error: "Bad Request",
|
|
1959
1960
|
message: `Task at index ${i} is missing required fields: session_id, cwd, prompt`
|
|
@@ -1984,7 +1985,7 @@ async function handleBatchExecute(req, res, processManager) {
|
|
|
1984
1985
|
}
|
|
1985
1986
|
};
|
|
1986
1987
|
});
|
|
1987
|
-
logger$
|
|
1988
|
+
logger$6.info("批量任务已创建", {
|
|
1988
1989
|
batchId,
|
|
1989
1990
|
taskIds,
|
|
1990
1991
|
concurrency
|
|
@@ -2001,17 +2002,17 @@ async function handleBatchExecute(req, res, processManager) {
|
|
|
2001
2002
|
*/
|
|
2002
2003
|
function handleGetTask(req, res) {
|
|
2003
2004
|
const taskId = req.params.taskId;
|
|
2004
|
-
logger$
|
|
2005
|
+
logger$6.info("查询任务状态", { taskId });
|
|
2005
2006
|
const taskInfo = taskStore.get(taskId);
|
|
2006
2007
|
if (!taskInfo) {
|
|
2007
|
-
logger$
|
|
2008
|
+
logger$6.warn("任务不存在", { taskId });
|
|
2008
2009
|
res.status(404).json({
|
|
2009
2010
|
error: "Not Found",
|
|
2010
2011
|
message: `Task ${taskId} not found`
|
|
2011
2012
|
});
|
|
2012
2013
|
return;
|
|
2013
2014
|
}
|
|
2014
|
-
logger$
|
|
2015
|
+
logger$6.info("返回任务状态", {
|
|
2015
2016
|
taskId,
|
|
2016
2017
|
status: taskInfo.status
|
|
2017
2018
|
});
|
|
@@ -2028,10 +2029,10 @@ function handleGetTask(req, res) {
|
|
|
2028
2029
|
*/
|
|
2029
2030
|
async function handleCancelTask(req, res, processManager) {
|
|
2030
2031
|
const taskId = req.params.taskId;
|
|
2031
|
-
logger$
|
|
2032
|
+
logger$6.info("收到取消任务请求", { taskId });
|
|
2032
2033
|
const taskInfo = taskStore.get(taskId);
|
|
2033
2034
|
if (!taskInfo) {
|
|
2034
|
-
logger$
|
|
2035
|
+
logger$6.warn("取消失败:任务不存在", { taskId });
|
|
2035
2036
|
res.status(404).json({
|
|
2036
2037
|
error: "Not Found",
|
|
2037
2038
|
message: `Task ${taskId} not found`
|
|
@@ -2039,7 +2040,7 @@ async function handleCancelTask(req, res, processManager) {
|
|
|
2039
2040
|
return;
|
|
2040
2041
|
}
|
|
2041
2042
|
if (taskInfo.status !== "running") {
|
|
2042
|
-
logger$
|
|
2043
|
+
logger$6.warn("取消失败:任务不在运行状态", {
|
|
2043
2044
|
taskId,
|
|
2044
2045
|
status: taskInfo.status
|
|
2045
2046
|
});
|
|
@@ -2053,7 +2054,7 @@ async function handleCancelTask(req, res, processManager) {
|
|
|
2053
2054
|
await processManager.stopProcess(taskInfo.sessionId);
|
|
2054
2055
|
taskInfo.status = "cancelled";
|
|
2055
2056
|
taskInfo.completedAt = /* @__PURE__ */ new Date();
|
|
2056
|
-
logger$
|
|
2057
|
+
logger$6.info("任务已取消", {
|
|
2057
2058
|
taskId,
|
|
2058
2059
|
sessionId: taskInfo.sessionId
|
|
2059
2060
|
});
|
|
@@ -2063,7 +2064,7 @@ async function handleCancelTask(req, res, processManager) {
|
|
|
2063
2064
|
});
|
|
2064
2065
|
} catch (err) {
|
|
2065
2066
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2066
|
-
logger$
|
|
2067
|
+
logger$6.error("取消任务时发生错误", {
|
|
2067
2068
|
taskId,
|
|
2068
2069
|
error: errorMsg
|
|
2069
2070
|
});
|
|
@@ -2086,7 +2087,7 @@ function createTaskCallbacks(taskId) {
|
|
|
2086
2087
|
taskInfo.result = result;
|
|
2087
2088
|
taskInfo.status = result.isError ? "failed" : "completed";
|
|
2088
2089
|
taskInfo.completedAt = /* @__PURE__ */ new Date();
|
|
2089
|
-
logger$
|
|
2090
|
+
logger$6.info("任务收到结果", {
|
|
2090
2091
|
taskId,
|
|
2091
2092
|
status: taskInfo.status,
|
|
2092
2093
|
subtype: result.subtype,
|
|
@@ -2100,7 +2101,7 @@ function createTaskCallbacks(taskId) {
|
|
|
2100
2101
|
taskInfo.status = "failed";
|
|
2101
2102
|
taskInfo.error = error.message;
|
|
2102
2103
|
taskInfo.completedAt = /* @__PURE__ */ new Date();
|
|
2103
|
-
logger$
|
|
2104
|
+
logger$6.error("任务执行出错", {
|
|
2104
2105
|
taskId,
|
|
2105
2106
|
error: error.message
|
|
2106
2107
|
});
|
|
@@ -2112,7 +2113,7 @@ function createTaskCallbacks(taskId) {
|
|
|
2112
2113
|
*
|
|
2113
2114
|
* 通过 Promise 包装回调机制,在收到 onResult 或 onError 时 resolve/reject。
|
|
2114
2115
|
*/
|
|
2115
|
-
function executeAndWaitResult(taskId, config, processManager) {
|
|
2116
|
+
function executeAndWaitResult$1(taskId, config, processManager) {
|
|
2116
2117
|
return new Promise((resolve, reject) => {
|
|
2117
2118
|
processManager.executePrompt(config, {
|
|
2118
2119
|
onResult: (result) => {
|
|
@@ -2122,7 +2123,7 @@ function executeAndWaitResult(taskId, config, processManager) {
|
|
|
2122
2123
|
taskInfo.status = result.isError ? "failed" : "completed";
|
|
2123
2124
|
taskInfo.completedAt = /* @__PURE__ */ new Date();
|
|
2124
2125
|
}
|
|
2125
|
-
logger$
|
|
2126
|
+
logger$6.info("同步任务收到结果", {
|
|
2126
2127
|
taskId,
|
|
2127
2128
|
subtype: result.subtype,
|
|
2128
2129
|
durationMs: result.durationMs,
|
|
@@ -2137,7 +2138,7 @@ function executeAndWaitResult(taskId, config, processManager) {
|
|
|
2137
2138
|
taskInfo.error = error.message;
|
|
2138
2139
|
taskInfo.completedAt = /* @__PURE__ */ new Date();
|
|
2139
2140
|
}
|
|
2140
|
-
logger$
|
|
2141
|
+
logger$6.error("同步任务执行出错", {
|
|
2141
2142
|
taskId,
|
|
2142
2143
|
error: error.message
|
|
2143
2144
|
});
|
|
@@ -2145,7 +2146,7 @@ function executeAndWaitResult(taskId, config, processManager) {
|
|
|
2145
2146
|
}
|
|
2146
2147
|
}).catch((err) => {
|
|
2147
2148
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2148
|
-
logger$
|
|
2149
|
+
logger$6.error("同步任务 executePrompt 调用失败", {
|
|
2149
2150
|
taskId,
|
|
2150
2151
|
error: errorMsg
|
|
2151
2152
|
});
|
|
@@ -2165,7 +2166,7 @@ function executeAndWaitResult(taskId, config, processManager) {
|
|
|
2165
2166
|
* 使用简单的信号量模式控制并发数,逐个启动任务直到所有任务完成。
|
|
2166
2167
|
*/
|
|
2167
2168
|
async function runBatchTasks(taskConfigs, concurrency, processManager) {
|
|
2168
|
-
logger$
|
|
2169
|
+
logger$6.info("开始批量任务执行", {
|
|
2169
2170
|
totalTasks: taskConfigs.length,
|
|
2170
2171
|
concurrency
|
|
2171
2172
|
});
|
|
@@ -2177,7 +2178,7 @@ async function runBatchTasks(taskConfigs, concurrency, processManager) {
|
|
|
2177
2178
|
await processManager.executePrompt(config, callbacks);
|
|
2178
2179
|
} catch (err) {
|
|
2179
2180
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2180
|
-
logger$
|
|
2181
|
+
logger$6.error("批量子任务执行异常", {
|
|
2181
2182
|
taskId,
|
|
2182
2183
|
error: errorMsg
|
|
2183
2184
|
});
|
|
@@ -2194,7 +2195,223 @@ async function runBatchTasks(taskConfigs, concurrency, processManager) {
|
|
|
2194
2195
|
if (executing.size >= concurrency) await Promise.race(executing);
|
|
2195
2196
|
}
|
|
2196
2197
|
await Promise.all(executing);
|
|
2197
|
-
logger$
|
|
2198
|
+
logger$6.info("批量任务全部完成", { totalTasks: taskConfigs.length });
|
|
2199
|
+
}
|
|
2200
|
+
//#endregion
|
|
2201
|
+
//#region src/gateway/agent-completion-handler.ts
|
|
2202
|
+
/**
|
|
2203
|
+
* 通用 AI 补全路由 — Agent Gateway 统一能力接口
|
|
2204
|
+
*
|
|
2205
|
+
* 提供 POST /api/agent/completion 接口,接收任意外部服务的 AI 任务请求。
|
|
2206
|
+
* 内部通过 CC(Claude Code)RuntimeAdapter 执行。
|
|
2207
|
+
*
|
|
2208
|
+
* 本模块是通用的 AI 能力网关接口,不包含任何特定业务逻辑。
|
|
2209
|
+
* 各接入方(如 rd-assistant-api 等)自行构造 prompt 和解析结果。
|
|
2210
|
+
*
|
|
2211
|
+
* 支持两种模式:
|
|
2212
|
+
* - 同步模式(默认):等待 CC 完成后直接返回结果文本
|
|
2213
|
+
* - 异步模式:立即返回 accepted,完成后通过 X-Callback-Url 回调通知
|
|
2214
|
+
*
|
|
2215
|
+
* 鉴权:通过 X-Internal-Secret 头验证服务间调用身份
|
|
2216
|
+
*/
|
|
2217
|
+
const logger$5 = larkLogger("gateway/agent-completion");
|
|
2218
|
+
/** 内部通信密钥,从环境变量读取 */
|
|
2219
|
+
const INTERNAL_SECRET = process.env.LARKPAL_API_SECRET || "dev-internal-secret";
|
|
2220
|
+
/** CC 执行时的工作目录(使用临时目录) */
|
|
2221
|
+
const COMPLETION_CWD = process.env.LARKPAL_COMPLETION_CWD || "/tmp/larkpal-completion";
|
|
2222
|
+
/**
|
|
2223
|
+
* 创建通用 AI 补全路由
|
|
2224
|
+
*
|
|
2225
|
+
* @param runtimeAdapter - CC 运行时适配器实例
|
|
2226
|
+
*/
|
|
2227
|
+
function createAgentCompletionRouter(runtimeAdapter) {
|
|
2228
|
+
const router = Router();
|
|
2229
|
+
router.post("/api/agent/completion", (req, res) => {
|
|
2230
|
+
handleCompletion(req, res, runtimeAdapter);
|
|
2231
|
+
});
|
|
2232
|
+
return router;
|
|
2233
|
+
}
|
|
2234
|
+
async function handleCompletion(req, res, runtimeAdapter) {
|
|
2235
|
+
const secret = req.headers["x-internal-secret"];
|
|
2236
|
+
if (secret !== INTERNAL_SECRET) {
|
|
2237
|
+
logger$5.warn("Agent completion 鉴权失败", { providedSecret: secret?.slice(0, 8) + "..." });
|
|
2238
|
+
res.status(401).json({
|
|
2239
|
+
code: 1,
|
|
2240
|
+
message: "鉴权失败"
|
|
2241
|
+
});
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
const callbackUrl = req.headers["x-callback-url"];
|
|
2245
|
+
const body = req.body;
|
|
2246
|
+
if (!body.taskId || !body.prompt) {
|
|
2247
|
+
logger$5.warn("Agent completion 参数不完整", {
|
|
2248
|
+
hasTaskId: !!body.taskId,
|
|
2249
|
+
hasPrompt: !!body.prompt
|
|
2250
|
+
});
|
|
2251
|
+
res.status(400).json({
|
|
2252
|
+
code: 1,
|
|
2253
|
+
message: "缺少必要参数: taskId, prompt"
|
|
2254
|
+
});
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
logger$5.info("收到 Agent completion 请求", {
|
|
2258
|
+
taskId: body.taskId,
|
|
2259
|
+
task: body.task || "unknown",
|
|
2260
|
+
promptLength: body.prompt.length,
|
|
2261
|
+
fileCount: body.files?.length || 0,
|
|
2262
|
+
hasCallback: !!callbackUrl,
|
|
2263
|
+
maxTurns: body.options?.maxTurns,
|
|
2264
|
+
resultFileName: body.options?.resultFileName
|
|
2265
|
+
});
|
|
2266
|
+
try {
|
|
2267
|
+
const { mkdir, writeFile } = await import("node:fs/promises");
|
|
2268
|
+
const { join } = await import("node:path");
|
|
2269
|
+
const taskDir = join(COMPLETION_CWD, body.taskId);
|
|
2270
|
+
await mkdir(taskDir, { recursive: true });
|
|
2271
|
+
if (body.files && body.files.length > 0) for (const file of body.files) {
|
|
2272
|
+
const filePath = join(taskDir, file.name);
|
|
2273
|
+
const fileBuffer = Buffer.from(file.contentBase64, "base64");
|
|
2274
|
+
await writeFile(filePath, fileBuffer);
|
|
2275
|
+
logger$5.info("附件文件已写入", {
|
|
2276
|
+
filePath,
|
|
2277
|
+
sizeBytes: fileBuffer.length
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
const sessionId = `completion-${body.taskId}`;
|
|
2281
|
+
const maxTurns = body.options?.maxTurns || 10;
|
|
2282
|
+
const resultFileName = body.options?.resultFileName;
|
|
2283
|
+
let finalResult = await executeAndWaitResult(runtimeAdapter, {
|
|
2284
|
+
sessionId,
|
|
2285
|
+
cwd: taskDir,
|
|
2286
|
+
prompt: body.prompt,
|
|
2287
|
+
maxTurns
|
|
2288
|
+
});
|
|
2289
|
+
if (resultFileName) try {
|
|
2290
|
+
const { readFile } = await import("node:fs/promises");
|
|
2291
|
+
const resultFilePath = join(taskDir, resultFileName);
|
|
2292
|
+
const fileContent = await readFile(resultFilePath, "utf-8");
|
|
2293
|
+
logger$5.info("从结果文件读取输出", {
|
|
2294
|
+
resultFilePath,
|
|
2295
|
+
contentLength: fileContent.length
|
|
2296
|
+
});
|
|
2297
|
+
finalResult = fileContent;
|
|
2298
|
+
} catch {
|
|
2299
|
+
logger$5.info("结果文件不存在,使用 CC 文本输出");
|
|
2300
|
+
}
|
|
2301
|
+
try {
|
|
2302
|
+
const { rm } = await import("node:fs/promises");
|
|
2303
|
+
await rm(taskDir, {
|
|
2304
|
+
recursive: true,
|
|
2305
|
+
force: true
|
|
2306
|
+
});
|
|
2307
|
+
} catch {}
|
|
2308
|
+
logger$5.info("Agent completion 完成", {
|
|
2309
|
+
taskId: body.taskId,
|
|
2310
|
+
resultLength: finalResult.length
|
|
2311
|
+
});
|
|
2312
|
+
if (callbackUrl) {
|
|
2313
|
+
res.status(200).json({
|
|
2314
|
+
code: 0,
|
|
2315
|
+
message: "accepted",
|
|
2316
|
+
mode: "async"
|
|
2317
|
+
});
|
|
2318
|
+
await sendCallback(callbackUrl, body.taskId, "success", finalResult);
|
|
2319
|
+
} else res.status(200).json({
|
|
2320
|
+
code: 0,
|
|
2321
|
+
message: "ok",
|
|
2322
|
+
mode: "sync",
|
|
2323
|
+
result: finalResult
|
|
2324
|
+
});
|
|
2325
|
+
} catch (err) {
|
|
2326
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2327
|
+
logger$5.error("Agent completion 执行失败", {
|
|
2328
|
+
taskId: body.taskId,
|
|
2329
|
+
error: errorMsg
|
|
2330
|
+
});
|
|
2331
|
+
if (callbackUrl) {
|
|
2332
|
+
res.status(200).json({
|
|
2333
|
+
code: 0,
|
|
2334
|
+
message: "accepted",
|
|
2335
|
+
mode: "async"
|
|
2336
|
+
});
|
|
2337
|
+
await sendCallback(callbackUrl, body.taskId, "failed", void 0, errorMsg);
|
|
2338
|
+
} else res.status(500).json({
|
|
2339
|
+
code: 1,
|
|
2340
|
+
message: `执行失败: ${errorMsg}`
|
|
2341
|
+
});
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
/**
|
|
2345
|
+
* 调用 CC 执行 prompt 并等待完成
|
|
2346
|
+
*/
|
|
2347
|
+
async function executeAndWaitResult(runtimeAdapter, config) {
|
|
2348
|
+
return new Promise((resolve, reject) => {
|
|
2349
|
+
let resultText = "";
|
|
2350
|
+
runtimeAdapter.executePrompt(config, {
|
|
2351
|
+
onTextDelta(text) {
|
|
2352
|
+
resultText += text;
|
|
2353
|
+
},
|
|
2354
|
+
onResult(result) {
|
|
2355
|
+
logger$5.info("CC 执行返回结果", {
|
|
2356
|
+
sessionId: config.sessionId,
|
|
2357
|
+
subtype: result.subtype,
|
|
2358
|
+
resultLength: resultText.length
|
|
2359
|
+
});
|
|
2360
|
+
resolve(resultText || result.result || "");
|
|
2361
|
+
},
|
|
2362
|
+
onError(error) {
|
|
2363
|
+
logger$5.error("CC 执行出错", {
|
|
2364
|
+
sessionId: config.sessionId,
|
|
2365
|
+
error: error.message
|
|
2366
|
+
});
|
|
2367
|
+
reject(error);
|
|
2368
|
+
}
|
|
2369
|
+
}).catch(reject);
|
|
2370
|
+
});
|
|
2371
|
+
}
|
|
2372
|
+
/**
|
|
2373
|
+
* 向调用方发送回调通知
|
|
2374
|
+
*/
|
|
2375
|
+
async function sendCallback(callbackUrl, taskId, status, result, error) {
|
|
2376
|
+
try {
|
|
2377
|
+
logger$5.info("发送回调通知", {
|
|
2378
|
+
callbackUrl,
|
|
2379
|
+
taskId,
|
|
2380
|
+
status,
|
|
2381
|
+
resultLength: result?.length
|
|
2382
|
+
});
|
|
2383
|
+
const response = await fetch(callbackUrl, {
|
|
2384
|
+
method: "POST",
|
|
2385
|
+
headers: {
|
|
2386
|
+
"Content-Type": "application/json",
|
|
2387
|
+
"X-Internal-Secret": INTERNAL_SECRET
|
|
2388
|
+
},
|
|
2389
|
+
body: JSON.stringify({
|
|
2390
|
+
taskId,
|
|
2391
|
+
status,
|
|
2392
|
+
result,
|
|
2393
|
+
error
|
|
2394
|
+
})
|
|
2395
|
+
});
|
|
2396
|
+
if (!response.ok) {
|
|
2397
|
+
const errText = await response.text();
|
|
2398
|
+
logger$5.warn("回调通知失败", {
|
|
2399
|
+
callbackUrl,
|
|
2400
|
+
status: response.status,
|
|
2401
|
+
body: errText
|
|
2402
|
+
});
|
|
2403
|
+
} else logger$5.info("回调通知成功", {
|
|
2404
|
+
callbackUrl,
|
|
2405
|
+
taskId
|
|
2406
|
+
});
|
|
2407
|
+
} catch (err) {
|
|
2408
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2409
|
+
logger$5.error("回调通知异常", {
|
|
2410
|
+
callbackUrl,
|
|
2411
|
+
taskId,
|
|
2412
|
+
error: errorMsg
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2198
2415
|
}
|
|
2199
2416
|
//#endregion
|
|
2200
2417
|
//#region src/gateway/skills-handler.ts
|
|
@@ -2648,7 +2865,7 @@ function createHooksRouter() {
|
|
|
2648
2865
|
* 提供 /api/scheduled-tasks 的 RESTful CRUD 接口。
|
|
2649
2866
|
* 每个请求和响应都记录详细日志,便于后续错误排查。
|
|
2650
2867
|
*/
|
|
2651
|
-
const log$
|
|
2868
|
+
const log$24 = larkLogger("gateway/scheduler-handler");
|
|
2652
2869
|
/**
|
|
2653
2870
|
* 创建定时任务路由
|
|
2654
2871
|
*
|
|
@@ -2659,16 +2876,16 @@ function createSchedulerRouter(taskManager) {
|
|
|
2659
2876
|
const router = Router();
|
|
2660
2877
|
router.get("/api/scheduled-tasks", (req, res) => {
|
|
2661
2878
|
const requestId = req.headers["x-request-id"];
|
|
2662
|
-
log$
|
|
2879
|
+
log$24.info("请求列出所有定时任务", { requestId });
|
|
2663
2880
|
try {
|
|
2664
2881
|
const tasks = taskManager.listTasks();
|
|
2665
|
-
log$
|
|
2882
|
+
log$24.info("返回定时任务列表", {
|
|
2666
2883
|
requestId,
|
|
2667
2884
|
count: tasks.length
|
|
2668
2885
|
});
|
|
2669
2886
|
res.json({ tasks });
|
|
2670
2887
|
} catch (err) {
|
|
2671
|
-
log$
|
|
2888
|
+
log$24.error("列出定时任务失败", {
|
|
2672
2889
|
requestId,
|
|
2673
2890
|
error: String(err)
|
|
2674
2891
|
});
|
|
@@ -2681,13 +2898,13 @@ function createSchedulerRouter(taskManager) {
|
|
|
2681
2898
|
router.post("/api/scheduled-tasks", (req, res) => {
|
|
2682
2899
|
const requestId = req.headers["x-request-id"];
|
|
2683
2900
|
const body = req.body;
|
|
2684
|
-
log$
|
|
2901
|
+
log$24.info("请求创建定时任务", {
|
|
2685
2902
|
requestId,
|
|
2686
2903
|
body: JSON.stringify(body)
|
|
2687
2904
|
});
|
|
2688
2905
|
const { name, cron: cronExpr, session_id, cwd, prompt, example_output, constraints } = body;
|
|
2689
2906
|
if (!name || typeof name !== "string") {
|
|
2690
|
-
log$
|
|
2907
|
+
log$24.warn("创建任务参数缺失: name", { requestId });
|
|
2691
2908
|
res.status(400).json({
|
|
2692
2909
|
error: "参数错误",
|
|
2693
2910
|
detail: "name 为必填字符串"
|
|
@@ -2695,7 +2912,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
2695
2912
|
return;
|
|
2696
2913
|
}
|
|
2697
2914
|
if (!cronExpr || typeof cronExpr !== "string") {
|
|
2698
|
-
log$
|
|
2915
|
+
log$24.warn("创建任务参数缺失: cron", { requestId });
|
|
2699
2916
|
res.status(400).json({
|
|
2700
2917
|
error: "参数错误",
|
|
2701
2918
|
detail: "cron 为必填字符串"
|
|
@@ -2703,7 +2920,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
2703
2920
|
return;
|
|
2704
2921
|
}
|
|
2705
2922
|
if (!session_id || typeof session_id !== "string") {
|
|
2706
|
-
log$
|
|
2923
|
+
log$24.warn("创建任务参数缺失: session_id", { requestId });
|
|
2707
2924
|
res.status(400).json({
|
|
2708
2925
|
error: "参数错误",
|
|
2709
2926
|
detail: "session_id 为必填字符串"
|
|
@@ -2711,7 +2928,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
2711
2928
|
return;
|
|
2712
2929
|
}
|
|
2713
2930
|
if (!cwd || typeof cwd !== "string") {
|
|
2714
|
-
log$
|
|
2931
|
+
log$24.warn("创建任务参数缺失: cwd", { requestId });
|
|
2715
2932
|
res.status(400).json({
|
|
2716
2933
|
error: "参数错误",
|
|
2717
2934
|
detail: "cwd 为必填字符串"
|
|
@@ -2719,7 +2936,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
2719
2936
|
return;
|
|
2720
2937
|
}
|
|
2721
2938
|
if (!prompt || typeof prompt !== "string") {
|
|
2722
|
-
log$
|
|
2939
|
+
log$24.warn("创建任务参数缺失: prompt", { requestId });
|
|
2723
2940
|
res.status(400).json({
|
|
2724
2941
|
error: "参数错误",
|
|
2725
2942
|
detail: "prompt 为必填字符串"
|
|
@@ -2727,7 +2944,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
2727
2944
|
return;
|
|
2728
2945
|
}
|
|
2729
2946
|
if (!cron.validate(cronExpr)) {
|
|
2730
|
-
log$
|
|
2947
|
+
log$24.warn("cron 表达式不合法", {
|
|
2731
2948
|
requestId,
|
|
2732
2949
|
cron: cronExpr
|
|
2733
2950
|
});
|
|
@@ -2748,7 +2965,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
2748
2965
|
constraints
|
|
2749
2966
|
};
|
|
2750
2967
|
const task = taskManager.createTask(params);
|
|
2751
|
-
log$
|
|
2968
|
+
log$24.info("定时任务创建成功", {
|
|
2752
2969
|
requestId,
|
|
2753
2970
|
taskId: task.id,
|
|
2754
2971
|
name: task.name,
|
|
@@ -2756,7 +2973,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
2756
2973
|
});
|
|
2757
2974
|
res.status(201).json({ task });
|
|
2758
2975
|
} catch (err) {
|
|
2759
|
-
log$
|
|
2976
|
+
log$24.error("创建定时任务失败", {
|
|
2760
2977
|
requestId,
|
|
2761
2978
|
error: String(err)
|
|
2762
2979
|
});
|
|
@@ -2770,13 +2987,13 @@ function createSchedulerRouter(taskManager) {
|
|
|
2770
2987
|
const requestId = req.headers["x-request-id"];
|
|
2771
2988
|
const { id } = req.params;
|
|
2772
2989
|
const body = req.body;
|
|
2773
|
-
log$
|
|
2990
|
+
log$24.info("请求更新定时任务", {
|
|
2774
2991
|
requestId,
|
|
2775
2992
|
taskId: id,
|
|
2776
2993
|
body: JSON.stringify(body)
|
|
2777
2994
|
});
|
|
2778
2995
|
if (!taskManager.getTask(id)) {
|
|
2779
|
-
log$
|
|
2996
|
+
log$24.warn("更新的任务不存在", {
|
|
2780
2997
|
requestId,
|
|
2781
2998
|
taskId: id
|
|
2782
2999
|
});
|
|
@@ -2788,7 +3005,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
2788
3005
|
}
|
|
2789
3006
|
if (body.cron !== void 0) {
|
|
2790
3007
|
if (typeof body.cron !== "string" || !cron.validate(body.cron)) {
|
|
2791
|
-
log$
|
|
3008
|
+
log$24.warn("更新的 cron 表达式不合法", {
|
|
2792
3009
|
requestId,
|
|
2793
3010
|
taskId: id,
|
|
2794
3011
|
cron: body.cron
|
|
@@ -2809,7 +3026,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
2809
3026
|
if (body.constraints !== void 0) updates.constraints = body.constraints;
|
|
2810
3027
|
if (body.enabled !== void 0) updates.enabled = body.enabled;
|
|
2811
3028
|
const task = taskManager.updateTask(id, updates);
|
|
2812
|
-
log$
|
|
3029
|
+
log$24.info("定时任务更新成功", {
|
|
2813
3030
|
requestId,
|
|
2814
3031
|
taskId: task.id,
|
|
2815
3032
|
name: task.name,
|
|
@@ -2817,7 +3034,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
2817
3034
|
});
|
|
2818
3035
|
res.json({ task });
|
|
2819
3036
|
} catch (err) {
|
|
2820
|
-
log$
|
|
3037
|
+
log$24.error("更新定时任务失败", {
|
|
2821
3038
|
requestId,
|
|
2822
3039
|
taskId: id,
|
|
2823
3040
|
error: String(err)
|
|
@@ -2831,12 +3048,12 @@ function createSchedulerRouter(taskManager) {
|
|
|
2831
3048
|
router.delete("/api/scheduled-tasks/:id", (req, res) => {
|
|
2832
3049
|
const requestId = req.headers["x-request-id"];
|
|
2833
3050
|
const id = String(req.params.id);
|
|
2834
|
-
log$
|
|
3051
|
+
log$24.info("请求删除定时任务", {
|
|
2835
3052
|
requestId,
|
|
2836
3053
|
taskId: id
|
|
2837
3054
|
});
|
|
2838
3055
|
if (!taskManager.getTask(id)) {
|
|
2839
|
-
log$
|
|
3056
|
+
log$24.warn("删除的任务不存在", {
|
|
2840
3057
|
requestId,
|
|
2841
3058
|
taskId: id
|
|
2842
3059
|
});
|
|
@@ -2848,7 +3065,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
2848
3065
|
}
|
|
2849
3066
|
try {
|
|
2850
3067
|
const deleted = taskManager.deleteTask(id);
|
|
2851
|
-
log$
|
|
3068
|
+
log$24.info("定时任务删除结果", {
|
|
2852
3069
|
requestId,
|
|
2853
3070
|
taskId: id,
|
|
2854
3071
|
deleted
|
|
@@ -2858,7 +3075,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
2858
3075
|
deleted: true
|
|
2859
3076
|
});
|
|
2860
3077
|
} catch (err) {
|
|
2861
|
-
log$
|
|
3078
|
+
log$24.error("删除定时任务失败", {
|
|
2862
3079
|
requestId,
|
|
2863
3080
|
taskId: id,
|
|
2864
3081
|
error: String(err)
|
|
@@ -2888,7 +3105,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
2888
3105
|
* - GET /open-apis/application/v6/applications/me?lang=zh_cn(需要 application:application:self_manage 权限,返回完整信息)
|
|
2889
3106
|
* 如果后者无权限则 fallback 到前者
|
|
2890
3107
|
*/
|
|
2891
|
-
const log$
|
|
3108
|
+
const log$23 = larkLogger("core/app-info-sync");
|
|
2892
3109
|
/**
|
|
2893
3110
|
* 从飞书获取应用信息
|
|
2894
3111
|
*
|
|
@@ -2899,7 +3116,7 @@ async function fetchAppInfo(credentials) {
|
|
|
2899
3116
|
const { appId, appSecret } = credentials;
|
|
2900
3117
|
const token = await getTenantAccessToken(appId, appSecret);
|
|
2901
3118
|
if (!token) {
|
|
2902
|
-
log$
|
|
3119
|
+
log$23.error("获取 tenant_access_token 失败,无法同步应用信息");
|
|
2903
3120
|
return null;
|
|
2904
3121
|
}
|
|
2905
3122
|
const fullInfo = await fetchFromApplicationApi(token);
|
|
@@ -2910,13 +3127,13 @@ async function fetchAppInfo(credentials) {
|
|
|
2910
3127
|
async function fetchFromApplicationApi(token) {
|
|
2911
3128
|
try {
|
|
2912
3129
|
const data = await (await fetch("https://open.feishu.cn/open-apis/application/v6/applications/me?lang=zh_cn", { headers: { Authorization: `Bearer ${token}` } })).json();
|
|
2913
|
-
log$
|
|
3130
|
+
log$23.info("application/v6 API 响应", {
|
|
2914
3131
|
code: data.code,
|
|
2915
3132
|
msg: data.msg,
|
|
2916
3133
|
hasApp: !!data.data?.app
|
|
2917
3134
|
});
|
|
2918
3135
|
if (data.code !== 0 || !data.data?.app) {
|
|
2919
|
-
log$
|
|
3136
|
+
log$23.warn("application/v6 API 返回非零或无数据,将 fallback", {
|
|
2920
3137
|
code: data.code,
|
|
2921
3138
|
msg: data.msg
|
|
2922
3139
|
});
|
|
@@ -2931,7 +3148,7 @@ async function fetchFromApplicationApi(token) {
|
|
|
2931
3148
|
helpDocUrl: zhInfo?.help_use
|
|
2932
3149
|
};
|
|
2933
3150
|
} catch (err) {
|
|
2934
|
-
log$
|
|
3151
|
+
log$23.warn("application/v6 API 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
2935
3152
|
return null;
|
|
2936
3153
|
}
|
|
2937
3154
|
}
|
|
@@ -2939,13 +3156,13 @@ async function fetchFromApplicationApi(token) {
|
|
|
2939
3156
|
async function fetchFromBotApi(token) {
|
|
2940
3157
|
try {
|
|
2941
3158
|
const data = await (await fetch("https://open.feishu.cn/open-apis/bot/v3/info", { headers: { Authorization: `Bearer ${token}` } })).json();
|
|
2942
|
-
log$
|
|
3159
|
+
log$23.info("bot/v3/info API 响应", {
|
|
2943
3160
|
code: data.code,
|
|
2944
3161
|
msg: data.msg,
|
|
2945
3162
|
hasBot: !!data.bot
|
|
2946
3163
|
});
|
|
2947
3164
|
if (data.code !== 0 || !data.bot) {
|
|
2948
|
-
log$
|
|
3165
|
+
log$23.error("bot/v3/info API 失败", {
|
|
2949
3166
|
code: data.code,
|
|
2950
3167
|
msg: data.msg
|
|
2951
3168
|
});
|
|
@@ -2956,7 +3173,7 @@ async function fetchFromBotApi(token) {
|
|
|
2956
3173
|
avatarUrl: data.bot.avatar_url
|
|
2957
3174
|
};
|
|
2958
3175
|
} catch (err) {
|
|
2959
|
-
log$
|
|
3176
|
+
log$23.error("bot/v3/info API 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
2960
3177
|
return null;
|
|
2961
3178
|
}
|
|
2962
3179
|
}
|
|
@@ -2972,7 +3189,7 @@ async function getTenantAccessToken(appId, appSecret) {
|
|
|
2972
3189
|
})
|
|
2973
3190
|
})).json();
|
|
2974
3191
|
if (data.code !== 0 || !data.tenant_access_token) {
|
|
2975
|
-
log$
|
|
3192
|
+
log$23.error("获取 tenant_access_token 失败", {
|
|
2976
3193
|
code: data.code,
|
|
2977
3194
|
msg: data.msg
|
|
2978
3195
|
});
|
|
@@ -2980,7 +3197,7 @@ async function getTenantAccessToken(appId, appSecret) {
|
|
|
2980
3197
|
}
|
|
2981
3198
|
return data.tenant_access_token;
|
|
2982
3199
|
} catch (err) {
|
|
2983
|
-
log$
|
|
3200
|
+
log$23.error("获取 tenant_access_token 异常", { error: err instanceof Error ? err.message : String(err) });
|
|
2984
3201
|
return null;
|
|
2985
3202
|
}
|
|
2986
3203
|
}
|
|
@@ -3018,10 +3235,10 @@ function parseDocTokenFromUrl(url) {
|
|
|
3018
3235
|
async function fetchDocContent(docUrl, accessToken) {
|
|
3019
3236
|
const parsed = parseDocTokenFromUrl(docUrl);
|
|
3020
3237
|
if (!parsed) {
|
|
3021
|
-
log$
|
|
3238
|
+
log$23.warn("无法从 URL 中解析文档 token", { url: docUrl });
|
|
3022
3239
|
return null;
|
|
3023
3240
|
}
|
|
3024
|
-
log$
|
|
3241
|
+
log$23.info("开始读取人设文档", {
|
|
3025
3242
|
url: docUrl,
|
|
3026
3243
|
type: parsed.type,
|
|
3027
3244
|
token: parsed.token
|
|
@@ -3029,18 +3246,18 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
3029
3246
|
let docToken = parsed.token;
|
|
3030
3247
|
if (parsed.type === "wiki") {
|
|
3031
3248
|
const realToken = await resolveWikiNodeToDocToken(parsed.token, accessToken);
|
|
3032
|
-
if (!realToken) log$
|
|
3249
|
+
if (!realToken) log$23.warn("wiki 节点解析失败,尝试直接使用 token 读取");
|
|
3033
3250
|
else docToken = realToken;
|
|
3034
3251
|
}
|
|
3035
3252
|
try {
|
|
3036
3253
|
const data = await (await fetch(`https://open.feishu.cn/open-apis/docx/v1/documents/${docToken}/raw_content`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
|
|
3037
|
-
log$
|
|
3254
|
+
log$23.info("文档 raw_content API 响应", {
|
|
3038
3255
|
code: data.code,
|
|
3039
3256
|
msg: data.msg,
|
|
3040
3257
|
contentLength: data.data?.content?.length
|
|
3041
3258
|
});
|
|
3042
3259
|
if (data.code !== 0 || !data.data?.content) {
|
|
3043
|
-
log$
|
|
3260
|
+
log$23.warn("读取文档内容失败", {
|
|
3044
3261
|
code: data.code,
|
|
3045
3262
|
msg: data.msg,
|
|
3046
3263
|
docToken
|
|
@@ -3049,7 +3266,7 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
3049
3266
|
}
|
|
3050
3267
|
return data.data.content.trim();
|
|
3051
3268
|
} catch (err) {
|
|
3052
|
-
log$
|
|
3269
|
+
log$23.error("读取文档内容异常", {
|
|
3053
3270
|
error: err instanceof Error ? err.message : String(err),
|
|
3054
3271
|
docToken
|
|
3055
3272
|
});
|
|
@@ -3060,7 +3277,7 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
3060
3277
|
async function resolveWikiNodeToDocToken(wikiToken, accessToken) {
|
|
3061
3278
|
try {
|
|
3062
3279
|
const data = await (await fetch(`https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token=${wikiToken}`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
|
|
3063
|
-
log$
|
|
3280
|
+
log$23.info("wiki get_node API 响应", {
|
|
3064
3281
|
code: data.code,
|
|
3065
3282
|
msg: data.msg,
|
|
3066
3283
|
objType: data.data?.node?.obj_type
|
|
@@ -3068,7 +3285,7 @@ async function resolveWikiNodeToDocToken(wikiToken, accessToken) {
|
|
|
3068
3285
|
if (data.code !== 0 || !data.data?.node?.obj_token) return null;
|
|
3069
3286
|
return data.data.node.obj_token;
|
|
3070
3287
|
} catch (err) {
|
|
3071
|
-
log$
|
|
3288
|
+
log$23.warn("wiki get_node 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
3072
3289
|
return null;
|
|
3073
3290
|
}
|
|
3074
3291
|
}
|
|
@@ -3090,7 +3307,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
3090
3307
|
const infoBlock = buildAppInfoBlock(appInfo);
|
|
3091
3308
|
if (!existsSync$1(claudeMdPath)) {
|
|
3092
3309
|
await writeFile$1(claudeMdPath, infoBlock + "\n\n" + getDefaultClaudeMdBody(), "utf-8");
|
|
3093
|
-
log$
|
|
3310
|
+
log$23.info("CLAUDE.md 已创建(含应用信息)", { appName: appInfo.appName });
|
|
3094
3311
|
return;
|
|
3095
3312
|
}
|
|
3096
3313
|
let content = await readFile$1(claudeMdPath, "utf-8");
|
|
@@ -3102,7 +3319,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
3102
3319
|
content = before + infoBlock + after;
|
|
3103
3320
|
} else content = infoBlock + "\n\n" + content;
|
|
3104
3321
|
await writeFile$1(claudeMdPath, content, "utf-8");
|
|
3105
|
-
log$
|
|
3322
|
+
log$23.info("CLAUDE.md 应用信息已同步", {
|
|
3106
3323
|
appName: appInfo.appName,
|
|
3107
3324
|
hasDescription: !!appInfo.description,
|
|
3108
3325
|
hasAvatar: !!appInfo.avatarUrl
|
|
@@ -3117,7 +3334,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
3117
3334
|
async function syncPersonaDocToClaudeMd(personaContent) {
|
|
3118
3335
|
const claudeMdPath = join$1(homedir$1(), ".claude", "CLAUDE.md");
|
|
3119
3336
|
if (!existsSync$1(claudeMdPath)) {
|
|
3120
|
-
log$
|
|
3337
|
+
log$23.warn("CLAUDE.md 不存在,无法同步人设文档(需先同步应用信息)");
|
|
3121
3338
|
return;
|
|
3122
3339
|
}
|
|
3123
3340
|
let content = await readFile$1(claudeMdPath, "utf-8");
|
|
@@ -3138,7 +3355,7 @@ async function syncPersonaDocToClaudeMd(personaContent) {
|
|
|
3138
3355
|
content = before + personaBlock + after;
|
|
3139
3356
|
} else content = content.trimEnd() + "\n\n" + personaBlock + "\n";
|
|
3140
3357
|
await writeFile$1(claudeMdPath, content, "utf-8");
|
|
3141
|
-
log$
|
|
3358
|
+
log$23.info("CLAUDE.md 人设文档已同步", { contentLength: personaContent.length });
|
|
3142
3359
|
}
|
|
3143
3360
|
/** 构建应用信息标记区块 */
|
|
3144
3361
|
function buildAppInfoBlock(appInfo) {
|
|
@@ -3176,24 +3393,24 @@ function getDefaultClaudeMdBody() {
|
|
|
3176
3393
|
* @returns 同步后的应用信息,如果失败返回 null
|
|
3177
3394
|
*/
|
|
3178
3395
|
async function syncAppInfo(credentials) {
|
|
3179
|
-
log$
|
|
3396
|
+
log$23.info("开始同步应用信息", { appId: credentials.appId });
|
|
3180
3397
|
const appInfo = await fetchAppInfo(credentials);
|
|
3181
3398
|
if (!appInfo) {
|
|
3182
|
-
log$
|
|
3399
|
+
log$23.warn("获取应用信息失败,跳过同步");
|
|
3183
3400
|
return null;
|
|
3184
3401
|
}
|
|
3185
3402
|
await syncAppInfoToClaudeMd(appInfo);
|
|
3186
3403
|
if (appInfo.helpDocUrl) {
|
|
3187
|
-
log$
|
|
3404
|
+
log$23.info("检测到帮助文档 URL,尝试同步人设文档", { helpDocUrl: appInfo.helpDocUrl });
|
|
3188
3405
|
const token = await getTenantAccessToken(credentials.appId, credentials.appSecret);
|
|
3189
3406
|
if (token) {
|
|
3190
3407
|
const personaContent = await fetchDocContent(appInfo.helpDocUrl, token);
|
|
3191
3408
|
if (personaContent) await syncPersonaDocToClaudeMd(personaContent);
|
|
3192
|
-
else log$
|
|
3409
|
+
else log$23.warn("人设文档内容为空或读取失败,跳过同步");
|
|
3193
3410
|
}
|
|
3194
3411
|
}
|
|
3195
3412
|
await installSyncSkill();
|
|
3196
|
-
log$
|
|
3413
|
+
log$23.info("应用信息同步完成", {
|
|
3197
3414
|
appName: appInfo.appName,
|
|
3198
3415
|
description: appInfo.description?.substring(0, 50),
|
|
3199
3416
|
hasPersonaDoc: !!appInfo.helpDocUrl
|
|
@@ -3211,7 +3428,7 @@ async function installSyncSkill() {
|
|
|
3211
3428
|
if ((await readFile$1(SYNC_SKILL_PATH, "utf-8")).includes(`skill-version: ${SYNC_SKILL_VERSION}`)) return;
|
|
3212
3429
|
}
|
|
3213
3430
|
await writeFile$1(SYNC_SKILL_PATH, SYNC_SKILL_CONTENT, "utf-8");
|
|
3214
|
-
log$
|
|
3431
|
+
log$23.info("sync-app-info 技能已安装", { path: SYNC_SKILL_PATH });
|
|
3215
3432
|
}
|
|
3216
3433
|
const SYNC_SKILL_CONTENT = `---
|
|
3217
3434
|
skill-version: ${SYNC_SKILL_VERSION}
|
|
@@ -3299,8 +3516,10 @@ function notImplemented(_req, res) {
|
|
|
3299
3516
|
* 其余路由当前仍为占位 handler。
|
|
3300
3517
|
*/
|
|
3301
3518
|
function registerRoutes(app, processManager, scheduledTaskManager, appCredentials) {
|
|
3302
|
-
if (processManager)
|
|
3303
|
-
|
|
3519
|
+
if (processManager) {
|
|
3520
|
+
app.use(createExecuteRouter(processManager));
|
|
3521
|
+
app.use(createAgentCompletionRouter(processManager));
|
|
3522
|
+
} else {
|
|
3304
3523
|
logger$1.warn("processManager 未注入,执行路由使用占位 handler");
|
|
3305
3524
|
app.post("/api/execute", notImplemented);
|
|
3306
3525
|
app.post("/api/execute/batch", notImplemented);
|
|
@@ -3317,6 +3536,7 @@ function registerRoutes(app, processManager, scheduledTaskManager, appCredential
|
|
|
3317
3536
|
app.delete("/api/scheduled-tasks/:id", notImplemented);
|
|
3318
3537
|
}
|
|
3319
3538
|
app.use("/api", createStatusRouter());
|
|
3539
|
+
app.use(createToolInvokeProxyRouter());
|
|
3320
3540
|
app.use("/hooks", createHooksRouter());
|
|
3321
3541
|
if (appCredentials) app.post("/api/sync-app-info", async (_req, res) => {
|
|
3322
3542
|
try {
|
|
@@ -3403,7 +3623,7 @@ function createGatewayServer(config) {
|
|
|
3403
3623
|
*
|
|
3404
3624
|
* 持久化文件: /workspace/config/scheduled-tasks.json
|
|
3405
3625
|
*/
|
|
3406
|
-
const log$
|
|
3626
|
+
const log$22 = larkLogger("gateway/scheduler");
|
|
3407
3627
|
/** 持久化文件路径 */
|
|
3408
3628
|
const PERSIST_PATH = "/workspace/config/scheduled-tasks.json";
|
|
3409
3629
|
/**
|
|
@@ -3421,7 +3641,7 @@ var ScheduledTaskManager = class {
|
|
|
3421
3641
|
processManager;
|
|
3422
3642
|
constructor(processManager) {
|
|
3423
3643
|
this.processManager = processManager;
|
|
3424
|
-
log$
|
|
3644
|
+
log$22.info("ScheduledTaskManager 已创建");
|
|
3425
3645
|
}
|
|
3426
3646
|
/**
|
|
3427
3647
|
* 创建定时任务
|
|
@@ -3441,7 +3661,7 @@ var ScheduledTaskManager = class {
|
|
|
3441
3661
|
enabled: true,
|
|
3442
3662
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3443
3663
|
};
|
|
3444
|
-
log$
|
|
3664
|
+
log$22.info("创建定时任务", {
|
|
3445
3665
|
taskId: task.id,
|
|
3446
3666
|
name: task.name,
|
|
3447
3667
|
cron: task.cron,
|
|
@@ -3477,7 +3697,7 @@ var ScheduledTaskManager = class {
|
|
|
3477
3697
|
if (updates.example_output !== void 0) task.example_output = updates.example_output;
|
|
3478
3698
|
if (updates.constraints !== void 0) task.constraints = updates.constraints;
|
|
3479
3699
|
if (updates.enabled !== void 0) task.enabled = updates.enabled;
|
|
3480
|
-
log$
|
|
3700
|
+
log$22.info("更新定时任务", {
|
|
3481
3701
|
taskId: id,
|
|
3482
3702
|
name: task.name,
|
|
3483
3703
|
cronChanged: oldCron !== task.cron,
|
|
@@ -3499,10 +3719,10 @@ var ScheduledTaskManager = class {
|
|
|
3499
3719
|
deleteTask(id) {
|
|
3500
3720
|
const task = this.tasks.get(id);
|
|
3501
3721
|
if (!task) {
|
|
3502
|
-
log$
|
|
3722
|
+
log$22.warn("尝试删除不存在的任务", { taskId: id });
|
|
3503
3723
|
return false;
|
|
3504
3724
|
}
|
|
3505
|
-
log$
|
|
3725
|
+
log$22.info("删除定时任务", {
|
|
3506
3726
|
taskId: id,
|
|
3507
3727
|
name: task.name
|
|
3508
3728
|
});
|
|
@@ -3517,28 +3737,28 @@ var ScheduledTaskManager = class {
|
|
|
3517
3737
|
* 启动时调用,读取 JSON 文件并为每个 enabled 的任务注册 cron。
|
|
3518
3738
|
*/
|
|
3519
3739
|
async loadFromDisk() {
|
|
3520
|
-
log$
|
|
3740
|
+
log$22.info("从磁盘加载定时任务", { path: PERSIST_PATH });
|
|
3521
3741
|
try {
|
|
3522
3742
|
const content = await readFile(PERSIST_PATH, "utf-8");
|
|
3523
3743
|
const data = JSON.parse(content);
|
|
3524
3744
|
if (!Array.isArray(data)) {
|
|
3525
|
-
log$
|
|
3745
|
+
log$22.warn("持久化文件格式异常,跳过加载", { path: PERSIST_PATH });
|
|
3526
3746
|
return;
|
|
3527
3747
|
}
|
|
3528
3748
|
for (const task of data) {
|
|
3529
3749
|
this.tasks.set(task.id, task);
|
|
3530
3750
|
if (task.enabled) this.registerCronJob(task);
|
|
3531
3751
|
}
|
|
3532
|
-
log$
|
|
3752
|
+
log$22.info("定时任务加载完成", {
|
|
3533
3753
|
total: data.length,
|
|
3534
3754
|
enabled: data.filter((t) => t.enabled).length
|
|
3535
3755
|
});
|
|
3536
3756
|
} catch (err) {
|
|
3537
3757
|
if (err.code === "ENOENT") {
|
|
3538
|
-
log$
|
|
3758
|
+
log$22.info("持久化文件不存在,跳过加载(首次启动)", { path: PERSIST_PATH });
|
|
3539
3759
|
return;
|
|
3540
3760
|
}
|
|
3541
|
-
log$
|
|
3761
|
+
log$22.error("加载定时任务失败", {
|
|
3542
3762
|
path: PERSIST_PATH,
|
|
3543
3763
|
error: String(err)
|
|
3544
3764
|
});
|
|
@@ -3554,12 +3774,12 @@ var ScheduledTaskManager = class {
|
|
|
3554
3774
|
try {
|
|
3555
3775
|
await mkdir(dirname(PERSIST_PATH), { recursive: true });
|
|
3556
3776
|
await writeFile(PERSIST_PATH, JSON.stringify(tasks, null, 2), "utf-8");
|
|
3557
|
-
log$
|
|
3777
|
+
log$22.info("定时任务已持久化到磁盘", {
|
|
3558
3778
|
path: PERSIST_PATH,
|
|
3559
3779
|
count: tasks.length
|
|
3560
3780
|
});
|
|
3561
3781
|
} catch (err) {
|
|
3562
|
-
log$
|
|
3782
|
+
log$22.error("持久化定时任务失败", {
|
|
3563
3783
|
path: PERSIST_PATH,
|
|
3564
3784
|
error: String(err)
|
|
3565
3785
|
});
|
|
@@ -3572,13 +3792,13 @@ var ScheduledTaskManager = class {
|
|
|
3572
3792
|
*/
|
|
3573
3793
|
registerCronJob(task) {
|
|
3574
3794
|
this.unregisterCronJob(task.id);
|
|
3575
|
-
log$
|
|
3795
|
+
log$22.info("注册 cron 调度", {
|
|
3576
3796
|
taskId: task.id,
|
|
3577
3797
|
name: task.name,
|
|
3578
3798
|
cron: task.cron
|
|
3579
3799
|
});
|
|
3580
3800
|
const job = cron.schedule(task.cron, () => {
|
|
3581
|
-
log$
|
|
3801
|
+
log$22.info("cron 触发任务执行", {
|
|
3582
3802
|
taskId: task.id,
|
|
3583
3803
|
name: task.name
|
|
3584
3804
|
});
|
|
@@ -3594,7 +3814,7 @@ var ScheduledTaskManager = class {
|
|
|
3594
3814
|
if (job) {
|
|
3595
3815
|
job.stop();
|
|
3596
3816
|
this.cronJobs.delete(taskId);
|
|
3597
|
-
log$
|
|
3817
|
+
log$22.info("已注销 cron 调度", { taskId });
|
|
3598
3818
|
}
|
|
3599
3819
|
}
|
|
3600
3820
|
/**
|
|
@@ -3604,10 +3824,10 @@ var ScheduledTaskManager = class {
|
|
|
3604
3824
|
*/
|
|
3605
3825
|
stopAll() {
|
|
3606
3826
|
const count = this.cronJobs.size;
|
|
3607
|
-
log$
|
|
3827
|
+
log$22.info("停止所有 cron 调度", { count });
|
|
3608
3828
|
for (const [taskId, job] of this.cronJobs) {
|
|
3609
3829
|
job.stop();
|
|
3610
|
-
log$
|
|
3830
|
+
log$22.debug("已停止 cron 调度", { taskId });
|
|
3611
3831
|
}
|
|
3612
3832
|
this.cronJobs.clear();
|
|
3613
3833
|
}
|
|
@@ -3621,17 +3841,17 @@ var ScheduledTaskManager = class {
|
|
|
3621
3841
|
async executeTask(taskId) {
|
|
3622
3842
|
const task = this.tasks.get(taskId);
|
|
3623
3843
|
if (!task) {
|
|
3624
|
-
log$
|
|
3844
|
+
log$22.warn("任务执行时未找到任务", { taskId });
|
|
3625
3845
|
return;
|
|
3626
3846
|
}
|
|
3627
3847
|
if (!task.enabled) {
|
|
3628
|
-
log$
|
|
3848
|
+
log$22.info("任务已禁用,跳过执行", {
|
|
3629
3849
|
taskId,
|
|
3630
3850
|
name: task.name
|
|
3631
3851
|
});
|
|
3632
3852
|
return;
|
|
3633
3853
|
}
|
|
3634
|
-
log$
|
|
3854
|
+
log$22.info("开始执行定时任务", {
|
|
3635
3855
|
taskId: task.id,
|
|
3636
3856
|
name: task.name,
|
|
3637
3857
|
sessionId: task.session_id,
|
|
@@ -3652,7 +3872,7 @@ var ScheduledTaskManager = class {
|
|
|
3652
3872
|
onResult: (result) => {
|
|
3653
3873
|
const summary = result.result ?? resultText;
|
|
3654
3874
|
task.last_result = summary.slice(0, 2e3);
|
|
3655
|
-
log$
|
|
3875
|
+
log$22.info("定时任务执行完成", {
|
|
3656
3876
|
taskId: task.id,
|
|
3657
3877
|
name: task.name,
|
|
3658
3878
|
subtype: result.subtype,
|
|
@@ -3665,7 +3885,7 @@ var ScheduledTaskManager = class {
|
|
|
3665
3885
|
},
|
|
3666
3886
|
onError: (error) => {
|
|
3667
3887
|
task.last_result = `执行失败: ${error.message}`;
|
|
3668
|
-
log$
|
|
3888
|
+
log$22.error("定时任务执行失败", {
|
|
3669
3889
|
taskId: task.id,
|
|
3670
3890
|
name: task.name,
|
|
3671
3891
|
error: error.message
|
|
@@ -3677,7 +3897,7 @@ var ScheduledTaskManager = class {
|
|
|
3677
3897
|
await this.processManager.executePrompt(config, callbacks);
|
|
3678
3898
|
} catch (err) {
|
|
3679
3899
|
task.last_result = `执行异常: ${String(err)}`;
|
|
3680
|
-
log$
|
|
3900
|
+
log$22.error("定时任务执行异常", {
|
|
3681
3901
|
taskId: task.id,
|
|
3682
3902
|
name: task.name,
|
|
3683
3903
|
error: String(err)
|
|
@@ -3970,7 +4190,7 @@ function getUserAgent() {
|
|
|
3970
4190
|
* - `LarkClient.fromCredentials(credentials)` — ephemeral instance (not cached)
|
|
3971
4191
|
* - `LarkClient.fromProvider(provider, opts)` — from ICredentialProvider (not cached)
|
|
3972
4192
|
*/
|
|
3973
|
-
const log$
|
|
4193
|
+
const log$20 = larkLogger("core/lark-client");
|
|
3974
4194
|
const GLOBAL_LARK_USER_AGENT_KEY = "LARK_USER_AGENT";
|
|
3975
4195
|
function installGlobalUserAgent() {
|
|
3976
4196
|
globalThis[GLOBAL_LARK_USER_AGENT_KEY] = getUserAgent();
|
|
@@ -4062,7 +4282,7 @@ var LarkClient = class LarkClient {
|
|
|
4062
4282
|
const existing = cache.get(account.accountId);
|
|
4063
4283
|
if (existing && existing.account.appId === account.appId && credentialsEqual(existing.account.appSecret, account.appSecret)) return existing;
|
|
4064
4284
|
if (existing) {
|
|
4065
|
-
log$
|
|
4285
|
+
log$20.info(`credentials changed, disposing stale instance`, { accountId: account.accountId });
|
|
4066
4286
|
existing.dispose();
|
|
4067
4287
|
}
|
|
4068
4288
|
const instance = new LarkClient(account);
|
|
@@ -4105,7 +4325,7 @@ var LarkClient = class LarkClient {
|
|
|
4105
4325
|
static fromProvider(provider, opts) {
|
|
4106
4326
|
const appId = provider.getAppId();
|
|
4107
4327
|
const appSecret = provider.getAppSecret();
|
|
4108
|
-
log$
|
|
4328
|
+
log$20.info("通过 ICredentialProvider 创建 LarkClient", {
|
|
4109
4329
|
appId,
|
|
4110
4330
|
accountId: opts?.accountId ?? "default",
|
|
4111
4331
|
brand: opts?.brand ?? "feishu"
|
|
@@ -4141,7 +4361,7 @@ var LarkClient = class LarkClient {
|
|
|
4141
4361
|
get sdk() {
|
|
4142
4362
|
if (!this._sdk) {
|
|
4143
4363
|
const { appId, appSecret } = this.requireCredentials();
|
|
4144
|
-
log$
|
|
4364
|
+
log$20.info("创建 Lark SDK 客户端实例", {
|
|
4145
4365
|
accountId: this.accountId,
|
|
4146
4366
|
appId,
|
|
4147
4367
|
brand: this.account.brand
|
|
@@ -4156,7 +4376,7 @@ var LarkClient = class LarkClient {
|
|
|
4156
4376
|
if (sdkAny.httpInstance?.interceptors) {
|
|
4157
4377
|
const accountId = this.accountId;
|
|
4158
4378
|
sdkAny.httpInstance.interceptors.request.use((req) => {
|
|
4159
|
-
log$
|
|
4379
|
+
log$20.debug("飞书 API 请求", {
|
|
4160
4380
|
accountId,
|
|
4161
4381
|
method: req.method,
|
|
4162
4382
|
url: req.url,
|
|
@@ -4166,7 +4386,7 @@ var LarkClient = class LarkClient {
|
|
|
4166
4386
|
return req;
|
|
4167
4387
|
}, void 0, { synchronous: true });
|
|
4168
4388
|
sdkAny.httpInstance.interceptors.response.use((res) => {
|
|
4169
|
-
log$
|
|
4389
|
+
log$20.debug("飞书 API 响应", {
|
|
4170
4390
|
accountId,
|
|
4171
4391
|
code: res?.code,
|
|
4172
4392
|
msg: res?.msg,
|
|
@@ -4174,7 +4394,7 @@ var LarkClient = class LarkClient {
|
|
|
4174
4394
|
});
|
|
4175
4395
|
return res;
|
|
4176
4396
|
}, (err) => {
|
|
4177
|
-
log$
|
|
4397
|
+
log$20.error("飞书 API 请求失败", {
|
|
4178
4398
|
accountId,
|
|
4179
4399
|
error: err instanceof Error ? err.message : String(err),
|
|
4180
4400
|
url: err?.config?.url
|
|
@@ -4261,7 +4481,7 @@ var LarkClient = class LarkClient {
|
|
|
4261
4481
|
dispatcher.register(handlers);
|
|
4262
4482
|
const { appId, appSecret } = this.requireCredentials();
|
|
4263
4483
|
if (this._wsClient) {
|
|
4264
|
-
log$
|
|
4484
|
+
log$20.warn(`closing previous WSClient before reconnect`, { accountId: this.accountId });
|
|
4265
4485
|
try {
|
|
4266
4486
|
this._wsClient.close({ force: true });
|
|
4267
4487
|
} catch {}
|
|
@@ -4278,7 +4498,7 @@ var LarkClient = class LarkClient {
|
|
|
4278
4498
|
wsClientAny.handleEventData = (data) => {
|
|
4279
4499
|
const msgType = data.headers?.find?.((h) => h.key === "type")?.value;
|
|
4280
4500
|
const eventType = data.headers?.find?.((h) => h.key === "event_type")?.value;
|
|
4281
|
-
log$
|
|
4501
|
+
log$20.info("WS 收到原始事件", {
|
|
4282
4502
|
accountId: this.accountId,
|
|
4283
4503
|
msgType,
|
|
4284
4504
|
eventType,
|
|
@@ -4304,14 +4524,14 @@ var LarkClient = class LarkClient {
|
|
|
4304
4524
|
/** Disconnect WebSocket but keep instance in cache. */
|
|
4305
4525
|
disconnect() {
|
|
4306
4526
|
if (this._wsClient) {
|
|
4307
|
-
log$
|
|
4527
|
+
log$20.info(`disconnecting WebSocket`, { accountId: this.accountId });
|
|
4308
4528
|
try {
|
|
4309
4529
|
this._wsClient.close({ force: true });
|
|
4310
4530
|
} catch {}
|
|
4311
4531
|
}
|
|
4312
4532
|
this._wsClient = null;
|
|
4313
4533
|
if (this.messageDedup) {
|
|
4314
|
-
log$
|
|
4534
|
+
log$20.info(`disposing message dedup`, {
|
|
4315
4535
|
accountId: this.accountId,
|
|
4316
4536
|
size: this.messageDedup.size
|
|
4317
4537
|
});
|
|
@@ -4617,7 +4837,7 @@ function sortTraceValue(value) {
|
|
|
4617
4837
|
}
|
|
4618
4838
|
//#endregion
|
|
4619
4839
|
//#region src/card/cc-stream-bridge.ts
|
|
4620
|
-
const log$
|
|
4840
|
+
const log$19 = larkLogger("card/cc-stream-bridge");
|
|
4621
4841
|
const CC_INTERNAL_PLACEHOLDER = "No response requested.";
|
|
4622
4842
|
/**
|
|
4623
4843
|
* CCStreamBridge — 将 CC 流事件桥接到 StreamingCardController
|
|
@@ -4642,7 +4862,7 @@ var CCStreamBridge = class {
|
|
|
4642
4862
|
sessionKey: options?.sessionKey
|
|
4643
4863
|
};
|
|
4644
4864
|
if (this.options.sessionKey) startToolUseTraceRun(this.options.sessionKey);
|
|
4645
|
-
log$
|
|
4865
|
+
log$19.info("CCStreamBridge 初始化", {
|
|
4646
4866
|
autoCompleteOnTurnEnd: this.options.autoCompleteOnTurnEnd,
|
|
4647
4867
|
sessionKey: this.options.sessionKey ?? "(none)"
|
|
4648
4868
|
});
|
|
@@ -4650,7 +4870,7 @@ var CCStreamBridge = class {
|
|
|
4650
4870
|
/** 文本增量 → 累积后调用 controller.onPartialReply */
|
|
4651
4871
|
onTextDelta(text) {
|
|
4652
4872
|
this.accumulatedText += text;
|
|
4653
|
-
log$
|
|
4873
|
+
log$19.debug("textDelta 事件", {
|
|
4654
4874
|
deltaLen: text.length,
|
|
4655
4875
|
totalLen: this.accumulatedText.length
|
|
4656
4876
|
});
|
|
@@ -4659,7 +4879,7 @@ var CCStreamBridge = class {
|
|
|
4659
4879
|
/** 思考增量 → 累积后调用 controller.onReasoningStream */
|
|
4660
4880
|
onThinkingDelta(text) {
|
|
4661
4881
|
this.accumulatedThinkingText += text;
|
|
4662
|
-
log$
|
|
4882
|
+
log$19.debug("thinkingDelta 事件", {
|
|
4663
4883
|
deltaLen: text.length,
|
|
4664
4884
|
totalLen: this.accumulatedThinkingText.length
|
|
4665
4885
|
});
|
|
@@ -4671,7 +4891,7 @@ var CCStreamBridge = class {
|
|
|
4671
4891
|
const displayName = getToolDisplayName(toolName);
|
|
4672
4892
|
const toolParams = typeof _toolInput === "object" && _toolInput !== null ? _toolInput : void 0;
|
|
4673
4893
|
const hasParams = toolParams && Object.keys(toolParams).length > 0;
|
|
4674
|
-
log$
|
|
4894
|
+
log$19.info("toolUseStart 事件", {
|
|
4675
4895
|
toolName,
|
|
4676
4896
|
displayName,
|
|
4677
4897
|
activeToolsCount: this.activeTools.size,
|
|
@@ -4684,7 +4904,7 @@ var CCStreamBridge = class {
|
|
|
4684
4904
|
toolName,
|
|
4685
4905
|
toolParams
|
|
4686
4906
|
})) {
|
|
4687
|
-
log$
|
|
4907
|
+
log$19.info("toolUseStart: 更新已有步骤的 params", { toolName });
|
|
4688
4908
|
this.controller.onToolStart({
|
|
4689
4909
|
name: toolName,
|
|
4690
4910
|
phase: "start"
|
|
@@ -4708,7 +4928,7 @@ var CCStreamBridge = class {
|
|
|
4708
4928
|
const toolName = this.activeTools.get(toolUseId) ?? this.lastToolName ?? "unknown";
|
|
4709
4929
|
const displayName = getToolDisplayName(toolName);
|
|
4710
4930
|
this.activeTools.delete(toolUseId);
|
|
4711
|
-
log$
|
|
4931
|
+
log$19.info("toolResult 事件", {
|
|
4712
4932
|
toolUseId,
|
|
4713
4933
|
toolName,
|
|
4714
4934
|
displayName,
|
|
@@ -4723,7 +4943,7 @@ var CCStreamBridge = class {
|
|
|
4723
4943
|
}
|
|
4724
4944
|
/** 工具执行进度 → 记录日志 */
|
|
4725
4945
|
onToolProgress(toolName, elapsedSeconds) {
|
|
4726
|
-
log$
|
|
4946
|
+
log$19.debug("toolProgress 事件", {
|
|
4727
4947
|
toolName,
|
|
4728
4948
|
displayName: getToolDisplayName(toolName),
|
|
4729
4949
|
elapsedSeconds
|
|
@@ -4731,7 +4951,7 @@ var CCStreamBridge = class {
|
|
|
4731
4951
|
}
|
|
4732
4952
|
/** 轮次结束 → 仅在 end_turn 时标记完成 + 触发 onIdle */
|
|
4733
4953
|
onTurnEnd(stopReason) {
|
|
4734
|
-
log$
|
|
4954
|
+
log$19.info("turnEnd 事件", {
|
|
4735
4955
|
stopReason,
|
|
4736
4956
|
accumulatedTextLen: this.accumulatedText.length,
|
|
4737
4957
|
accumulatedThinkingTextLen: this.accumulatedThinkingText.length
|
|
@@ -4739,7 +4959,7 @@ var CCStreamBridge = class {
|
|
|
4739
4959
|
if (this.options.autoCompleteOnTurnEnd && stopReason === "end_turn") {
|
|
4740
4960
|
const trimmedText = this.accumulatedText.trim();
|
|
4741
4961
|
if (trimmedText === CC_INTERNAL_PLACEHOLDER || trimmedText === "") {
|
|
4742
|
-
log$
|
|
4962
|
+
log$19.info("检测到 CC 内部占位消息,静默丢弃", {
|
|
4743
4963
|
text: trimmedText.slice(0, 50),
|
|
4744
4964
|
sessionKey: this.options.sessionKey
|
|
4745
4965
|
});
|
|
@@ -4752,7 +4972,7 @@ var CCStreamBridge = class {
|
|
|
4752
4972
|
}
|
|
4753
4973
|
/** 最终结果 → 兜底最终化 + 错误处理 */
|
|
4754
4974
|
onResult(data) {
|
|
4755
|
-
log$
|
|
4975
|
+
log$19.info("result 事件", {
|
|
4756
4976
|
subtype: data.subtype,
|
|
4757
4977
|
isError: data.isError,
|
|
4758
4978
|
durationMs: data.durationMs,
|
|
@@ -4764,7 +4984,7 @@ var CCStreamBridge = class {
|
|
|
4764
4984
|
const pm = ClaudeCodeAdapter.getInstance()?.getProcessManager();
|
|
4765
4985
|
const sessionId = this.options.sessionKey;
|
|
4766
4986
|
if (sessionId && pm ? pm.consumeAborted(sessionId) : false) {
|
|
4767
|
-
log$
|
|
4987
|
+
log$19.info("用户主动中断,按正常完成处理", {
|
|
4768
4988
|
subtype: data.subtype,
|
|
4769
4989
|
sessionId
|
|
4770
4990
|
});
|
|
@@ -4773,7 +4993,7 @@ var CCStreamBridge = class {
|
|
|
4773
4993
|
this.controller.onIdle();
|
|
4774
4994
|
} else {
|
|
4775
4995
|
const errorMessage = data.result ?? `CC 执行失败: ${data.subtype}`;
|
|
4776
|
-
log$
|
|
4996
|
+
log$19.error("CC 执行返回错误", {
|
|
4777
4997
|
subtype: data.subtype,
|
|
4778
4998
|
errorMessage: errorMessage.slice(0, 200)
|
|
4779
4999
|
});
|
|
@@ -4790,7 +5010,7 @@ var CCStreamBridge = class {
|
|
|
4790
5010
|
* 内部复用上面的直接调用方法。
|
|
4791
5011
|
*/
|
|
4792
5012
|
bindParser(parser) {
|
|
4793
|
-
log$
|
|
5013
|
+
log$19.info("绑定 CCStreamParser 事件到卡片控制器");
|
|
4794
5014
|
parser.on("textDelta", (text) => this.onTextDelta(text));
|
|
4795
5015
|
parser.on("thinkingDelta", (text) => this.onThinkingDelta(text));
|
|
4796
5016
|
parser.on("toolUseStart", (toolName, toolInput) => this.onToolUseStart(toolName, toolInput));
|
|
@@ -4798,7 +5018,7 @@ var CCStreamBridge = class {
|
|
|
4798
5018
|
parser.on("toolProgress", (toolName, elapsedSeconds) => this.onToolProgress(toolName, elapsedSeconds));
|
|
4799
5019
|
parser.on("turnEnd", (stopReason) => this.onTurnEnd(stopReason));
|
|
4800
5020
|
parser.on("result", (data) => this.onResult(data));
|
|
4801
|
-
log$
|
|
5021
|
+
log$19.info("CCStreamParser 事件绑定完成");
|
|
4802
5022
|
}
|
|
4803
5023
|
};
|
|
4804
5024
|
//#endregion
|
|
@@ -6814,7 +7034,7 @@ function resolveLarkSdk(cfg, accountId) {
|
|
|
6814
7034
|
if (cached) return cached.sdk;
|
|
6815
7035
|
return LarkClient.fromCfg(cfg, accountId).sdk;
|
|
6816
7036
|
}
|
|
6817
|
-
const log$
|
|
7037
|
+
const log$18 = larkLogger("card/cardkit");
|
|
6818
7038
|
/**
|
|
6819
7039
|
* 记录 CardKit API 响应日志,检测错误码并抛出异常。
|
|
6820
7040
|
*
|
|
@@ -6824,13 +7044,13 @@ const log$17 = larkLogger("card/cardkit");
|
|
|
6824
7044
|
function logCardKitResponse(params) {
|
|
6825
7045
|
const { resp, api, context } = params;
|
|
6826
7046
|
const { code, msg } = resp;
|
|
6827
|
-
log$
|
|
7047
|
+
log$18.info(`cardkit ${api} response`, {
|
|
6828
7048
|
code,
|
|
6829
7049
|
msg,
|
|
6830
7050
|
context
|
|
6831
7051
|
});
|
|
6832
7052
|
if (code && code !== 0) {
|
|
6833
|
-
log$
|
|
7053
|
+
log$18.warn(`cardkit ${api} FAILED`, {
|
|
6834
7054
|
code,
|
|
6835
7055
|
msg,
|
|
6836
7056
|
context,
|
|
@@ -7241,7 +7461,7 @@ function validateLocalMediaRoots(filePath, localRoots) {
|
|
|
7241
7461
|
* Feishu messages, uploading media to the Feishu IM storage, and
|
|
7242
7462
|
* sending image / file messages to chats.
|
|
7243
7463
|
*/
|
|
7244
|
-
const log$
|
|
7464
|
+
const log$17 = larkLogger("outbound/media");
|
|
7245
7465
|
/**
|
|
7246
7466
|
* Upload an image to Feishu IM storage.
|
|
7247
7467
|
*
|
|
@@ -7309,7 +7529,7 @@ async function validateRemoteUrl(raw) {
|
|
|
7309
7529
|
for (const addr of addresses) if (isPrivateIP(addr)) throw new Error(`[feishu-media] Domain "${hostname}" resolves to private/reserved IP "${addr}" (SSRF protection). URL: "${raw}"`);
|
|
7310
7530
|
} catch (err) {
|
|
7311
7531
|
if (err instanceof Error && err.message.includes("SSRF protection")) throw err;
|
|
7312
|
-
log$
|
|
7532
|
+
log$17.warn(`[feishu-media] DNS resolution failed for "${hostname}": ${err}`);
|
|
7313
7533
|
}
|
|
7314
7534
|
}
|
|
7315
7535
|
/**
|
|
@@ -7327,21 +7547,21 @@ async function fetchMediaBuffer(urlOrPath, localRoots) {
|
|
|
7327
7547
|
if (localRoots !== void 0) validateLocalMediaRoots(filePath, localRoots);
|
|
7328
7548
|
else throw new Error(`[feishu-media] Local file access denied for "${filePath}": mediaLocalRoots is not configured. Configure mediaLocalRoots to explicitly allow local file access.`);
|
|
7329
7549
|
const buf = fs.readFileSync(filePath);
|
|
7330
|
-
log$
|
|
7550
|
+
log$17.debug(`local file read: "${filePath}", ${buf.length} bytes`);
|
|
7331
7551
|
return buf;
|
|
7332
7552
|
}
|
|
7333
7553
|
await validateRemoteUrl(raw);
|
|
7334
7554
|
const FETCH_TIMEOUT_MS = 3e4;
|
|
7335
|
-
log$
|
|
7555
|
+
log$17.info(`fetching remote media: ${raw}`);
|
|
7336
7556
|
const response = await fetch(raw, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
7337
7557
|
if (!response.ok) throw new Error(`[feishu-media] Failed to fetch media from "${raw}": HTTP ${response.status} ${response.statusText}. Verify the URL is accessible and returns a valid media resource.`);
|
|
7338
7558
|
const arrayBuffer = await response.arrayBuffer();
|
|
7339
|
-
log$
|
|
7559
|
+
log$17.debug(`remote media fetched: ${raw}, ${arrayBuffer.byteLength} bytes`);
|
|
7340
7560
|
return Buffer.from(arrayBuffer);
|
|
7341
7561
|
}
|
|
7342
7562
|
//#endregion
|
|
7343
7563
|
//#region src/card/image-resolver.ts
|
|
7344
|
-
const log$
|
|
7564
|
+
const log$16 = larkLogger("card/image-resolver");
|
|
7345
7565
|
/** Matches complete markdown image syntax: `` */
|
|
7346
7566
|
const IMAGE_RE = /!\[([^\]]*)\]\(([^)\s]+)\)/g;
|
|
7347
7567
|
var ImageResolver = class {
|
|
@@ -7387,14 +7607,14 @@ var ImageResolver = class {
|
|
|
7387
7607
|
async resolveImagesAwait(text, timeoutMs) {
|
|
7388
7608
|
this.resolveImages(text);
|
|
7389
7609
|
if (this.pending.size > 0) {
|
|
7390
|
-
log$
|
|
7610
|
+
log$16.info("resolveImagesAwait: waiting for uploads", {
|
|
7391
7611
|
count: this.pending.size,
|
|
7392
7612
|
timeoutMs
|
|
7393
7613
|
});
|
|
7394
7614
|
const allUploads = Promise.all(this.pending.values());
|
|
7395
7615
|
const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
7396
7616
|
await Promise.race([allUploads, timeout]);
|
|
7397
|
-
if (this.pending.size > 0) log$
|
|
7617
|
+
if (this.pending.size > 0) log$16.warn("resolveImagesAwait: timed out with pending uploads", { remaining: this.pending.size });
|
|
7398
7618
|
}
|
|
7399
7619
|
return this.resolveImages(text);
|
|
7400
7620
|
}
|
|
@@ -7404,7 +7624,7 @@ var ImageResolver = class {
|
|
|
7404
7624
|
}
|
|
7405
7625
|
async doUpload(url) {
|
|
7406
7626
|
try {
|
|
7407
|
-
log$
|
|
7627
|
+
log$16.info("uploading image", { url });
|
|
7408
7628
|
const buffer = await fetchRemoteImageBuffer(url);
|
|
7409
7629
|
const { imageKey } = await uploadImageLark({
|
|
7410
7630
|
cfg: this.cfg,
|
|
@@ -7412,7 +7632,7 @@ var ImageResolver = class {
|
|
|
7412
7632
|
imageType: "message",
|
|
7413
7633
|
accountId: this.accountId
|
|
7414
7634
|
});
|
|
7415
|
-
log$
|
|
7635
|
+
log$16.info("image uploaded", {
|
|
7416
7636
|
url,
|
|
7417
7637
|
imageKey
|
|
7418
7638
|
});
|
|
@@ -7421,7 +7641,7 @@ var ImageResolver = class {
|
|
|
7421
7641
|
this.onImageResolved();
|
|
7422
7642
|
return imageKey;
|
|
7423
7643
|
} catch (err) {
|
|
7424
|
-
log$
|
|
7644
|
+
log$16.warn("image upload failed", {
|
|
7425
7645
|
url,
|
|
7426
7646
|
error: String(err)
|
|
7427
7647
|
});
|
|
@@ -7442,7 +7662,7 @@ var ImageResolver = class {
|
|
|
7442
7662
|
* Encapsulates the terminateDueToUnavailable / shouldSkipForUnavailable
|
|
7443
7663
|
* logic previously scattered as closures in reply-dispatcher.ts.
|
|
7444
7664
|
*/
|
|
7445
|
-
const log$
|
|
7665
|
+
const log$15 = larkLogger("card/unavailable-guard");
|
|
7446
7666
|
var UnavailableGuard = class {
|
|
7447
7667
|
terminated = false;
|
|
7448
7668
|
replyToMessageId;
|
|
@@ -7495,7 +7715,7 @@ var UnavailableGuard = class {
|
|
|
7495
7715
|
this.terminated = true;
|
|
7496
7716
|
this.onTerminate();
|
|
7497
7717
|
const affectedMessageId = fromError?.messageId ?? this.replyToMessageId ?? cardMessageId ?? "unknown";
|
|
7498
|
-
log$
|
|
7718
|
+
log$15.warn("reply pipeline terminated by unavailable message", {
|
|
7499
7719
|
source,
|
|
7500
7720
|
apiCode,
|
|
7501
7721
|
messageId: affectedMessageId
|
|
@@ -7532,7 +7752,7 @@ function getActiveCard(sessionId) {
|
|
|
7532
7752
|
* Delegates throttling to FlushController and message-unavailable
|
|
7533
7753
|
* detection to UnavailableGuard.
|
|
7534
7754
|
*/
|
|
7535
|
-
const log$
|
|
7755
|
+
const log$14 = larkLogger("card/streaming");
|
|
7536
7756
|
var StreamingCardController = class StreamingCardController {
|
|
7537
7757
|
phase = "idle";
|
|
7538
7758
|
cardKit = {
|
|
@@ -7620,7 +7840,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7620
7840
|
}
|
|
7621
7841
|
}
|
|
7622
7842
|
if (!entry) {
|
|
7623
|
-
log$
|
|
7843
|
+
log$14.debug("footer metrics lookup: session entry missing", {
|
|
7624
7844
|
sessionKey: this.deps.sessionKey,
|
|
7625
7845
|
candidateKeys,
|
|
7626
7846
|
storePath,
|
|
@@ -7638,7 +7858,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7638
7858
|
contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
|
|
7639
7859
|
model: typeof entry.model === "string" ? entry.model : void 0
|
|
7640
7860
|
};
|
|
7641
|
-
log$
|
|
7861
|
+
log$14.debug("footer metrics lookup: session entry found", {
|
|
7642
7862
|
sessionKey: this.deps.sessionKey,
|
|
7643
7863
|
matchedKey,
|
|
7644
7864
|
storePath,
|
|
@@ -7663,7 +7883,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7663
7883
|
}
|
|
7664
7884
|
}
|
|
7665
7885
|
if (!entry) {
|
|
7666
|
-
log$
|
|
7886
|
+
log$14.debug("footer metrics lookup: session entry missing", {
|
|
7667
7887
|
sessionKey: this.deps.sessionKey,
|
|
7668
7888
|
candidateKeys,
|
|
7669
7889
|
storePath,
|
|
@@ -7681,7 +7901,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7681
7901
|
contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
|
|
7682
7902
|
model: typeof entry.model === "string" ? entry.model : void 0
|
|
7683
7903
|
};
|
|
7684
|
-
log$
|
|
7904
|
+
log$14.debug("footer metrics lookup: session entry found", {
|
|
7685
7905
|
sessionKey: this.deps.sessionKey,
|
|
7686
7906
|
matchedKey,
|
|
7687
7907
|
storePath,
|
|
@@ -7689,7 +7909,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7689
7909
|
});
|
|
7690
7910
|
return metrics;
|
|
7691
7911
|
} catch (err) {
|
|
7692
|
-
log$
|
|
7912
|
+
log$14.warn("footer metrics lookup failed", {
|
|
7693
7913
|
error: String(err),
|
|
7694
7914
|
sessionKey: this.deps.sessionKey
|
|
7695
7915
|
});
|
|
@@ -7796,7 +8016,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7796
8016
|
const from = this.phase;
|
|
7797
8017
|
if (from === to) return false;
|
|
7798
8018
|
if (!PHASE_TRANSITIONS[from].has(to)) {
|
|
7799
|
-
log$
|
|
8019
|
+
log$14.warn("phase transition rejected", {
|
|
7800
8020
|
from,
|
|
7801
8021
|
to,
|
|
7802
8022
|
source
|
|
@@ -7804,7 +8024,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7804
8024
|
return false;
|
|
7805
8025
|
}
|
|
7806
8026
|
this.phase = to;
|
|
7807
|
-
log$
|
|
8027
|
+
log$14.info("phase transition", {
|
|
7808
8028
|
from,
|
|
7809
8029
|
to,
|
|
7810
8030
|
source,
|
|
@@ -7924,7 +8144,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7924
8144
|
this.reasoning.dirty = true;
|
|
7925
8145
|
}
|
|
7926
8146
|
const text = split.answerText ?? stripReasoningTags(rawText);
|
|
7927
|
-
log$
|
|
8147
|
+
log$14.debug("onPartialReply", { len: text.length });
|
|
7928
8148
|
if (!text) return;
|
|
7929
8149
|
this.captureToolUseElapsed();
|
|
7930
8150
|
if (!this.reasoning.reasoningStartTime) this.reasoning.reasoningStartTime = Date.now();
|
|
@@ -7936,7 +8156,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7936
8156
|
this.text.lastPartialText = text;
|
|
7937
8157
|
this.text.accumulatedText = this.text.streamingPrefix ? this.text.streamingPrefix + "\n\n" + text : text;
|
|
7938
8158
|
if (!this.text.streamingPrefix && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim())) {
|
|
7939
|
-
log$
|
|
8159
|
+
log$14.debug("onPartialReply: buffering NO_REPLY prefix");
|
|
7940
8160
|
return;
|
|
7941
8161
|
}
|
|
7942
8162
|
await this.ensureCardCreated();
|
|
@@ -7946,7 +8166,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7946
8166
|
}
|
|
7947
8167
|
async onError(err, info) {
|
|
7948
8168
|
if (this.guard.terminate("onError", err)) return;
|
|
7949
|
-
log$
|
|
8169
|
+
log$14.error(`${info.kind} reply failed`, { error: String(err) });
|
|
7950
8170
|
this.captureToolUseElapsed();
|
|
7951
8171
|
this.finalizeCard("onError", "error");
|
|
7952
8172
|
await this.flush.waitForFlush();
|
|
@@ -8002,7 +8222,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8002
8222
|
if (this.cardKit.cardMessageId) {
|
|
8003
8223
|
const isNoReplyLeak = !this.text.completedText && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim());
|
|
8004
8224
|
const displayText = this.text.completedText || (isNoReplyLeak ? "" : this.text.accumulatedText) || "Done.";
|
|
8005
|
-
if (!this.text.completedText && !this.text.accumulatedText) log$
|
|
8225
|
+
if (!this.text.completedText && !this.text.accumulatedText) log$14.warn("reply completed without visible text, using empty-reply fallback");
|
|
8006
8226
|
const resolvedDisplayText = await this.imageResolver.resolveImagesAwait(displayText, 15e3);
|
|
8007
8227
|
const idleToolUseDisplay = this.computeToolUseDisplay();
|
|
8008
8228
|
const terminalContent = prepareTerminalCardContent({
|
|
@@ -8030,7 +8250,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8030
8250
|
const rewindSessionId = this.deps.sessionKey;
|
|
8031
8251
|
const rewindMessageId = ClaudeCodeAdapter.getInstance()?.getProcessManager()?.getLastUserMessageId(this.deps.sessionKey);
|
|
8032
8252
|
const rewindHasFileChanges = this.hasFileChangingTools(idleToolUseDisplay?.steps);
|
|
8033
|
-
log$
|
|
8253
|
+
log$14.info("onIdle: rewind button conditions", {
|
|
8034
8254
|
sessionId: rewindSessionId,
|
|
8035
8255
|
messageId: rewindMessageId,
|
|
8036
8256
|
hasFileChanges: rewindHasFileChanges,
|
|
@@ -8043,7 +8263,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8043
8263
|
if (idleEffectiveCardId) {
|
|
8044
8264
|
const seqBeforeClose = this.cardKit.cardKitSequence;
|
|
8045
8265
|
this.cardKit.cardKitSequence += 1;
|
|
8046
|
-
log$
|
|
8266
|
+
log$14.info("onIdle: closing streaming mode", {
|
|
8047
8267
|
attempt,
|
|
8048
8268
|
seqBefore: seqBeforeClose,
|
|
8049
8269
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -8057,7 +8277,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8057
8277
|
});
|
|
8058
8278
|
const seqBeforeUpdate = this.cardKit.cardKitSequence;
|
|
8059
8279
|
this.cardKit.cardKitSequence += 1;
|
|
8060
|
-
log$
|
|
8280
|
+
log$14.info("onIdle: updating final card", {
|
|
8061
8281
|
attempt,
|
|
8062
8282
|
seqBefore: seqBeforeUpdate,
|
|
8063
8283
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -8075,33 +8295,33 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8075
8295
|
card: completeCard,
|
|
8076
8296
|
accountId: this.deps.accountId
|
|
8077
8297
|
});
|
|
8078
|
-
log$
|
|
8298
|
+
log$14.info("reply completed, card finalized", {
|
|
8079
8299
|
elapsedMs: this.elapsed(),
|
|
8080
8300
|
isCardKit: !!idleEffectiveCardId,
|
|
8081
8301
|
attempt
|
|
8082
8302
|
});
|
|
8083
8303
|
break;
|
|
8084
8304
|
} catch (retryErr) {
|
|
8085
|
-
log$
|
|
8305
|
+
log$14.warn("final card update attempt failed", {
|
|
8086
8306
|
attempt,
|
|
8087
8307
|
maxRetries: MAX_FINAL_UPDATE_RETRIES,
|
|
8088
8308
|
error: String(retryErr)
|
|
8089
8309
|
});
|
|
8090
8310
|
if (attempt < MAX_FINAL_UPDATE_RETRIES) await new Promise((resolve) => setTimeout(resolve, FINAL_UPDATE_RETRY_DELAY_MS * attempt));
|
|
8091
|
-
else log$
|
|
8311
|
+
else log$14.error("final card update exhausted all retries, card may remain in streaming state", {
|
|
8092
8312
|
cardId: idleEffectiveCardId,
|
|
8093
8313
|
messageId: this.cardKit.cardMessageId
|
|
8094
8314
|
});
|
|
8095
8315
|
}
|
|
8096
8316
|
}
|
|
8097
8317
|
} catch (err) {
|
|
8098
|
-
log$
|
|
8318
|
+
log$14.error("final card update failed (outer)", { error: String(err) });
|
|
8099
8319
|
} finally {
|
|
8100
8320
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
8101
8321
|
}
|
|
8102
8322
|
}
|
|
8103
8323
|
markFullyComplete() {
|
|
8104
|
-
log$
|
|
8324
|
+
log$14.debug("markFullyComplete", {
|
|
8105
8325
|
completedTextLen: this.text.completedText.length,
|
|
8106
8326
|
accumulatedTextLen: this.text.accumulatedText.length
|
|
8107
8327
|
});
|
|
@@ -8140,7 +8360,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8140
8360
|
footerMetrics
|
|
8141
8361
|
});
|
|
8142
8362
|
await this.closeStreamingAndUpdate(effectiveCardId, abortCardContent, "abortCard");
|
|
8143
|
-
log$
|
|
8363
|
+
log$14.info("abortCard completed", { effectiveCardId });
|
|
8144
8364
|
} else if (this.cardKit.cardMessageId) {
|
|
8145
8365
|
const abortCard = buildCardContent("complete", {
|
|
8146
8366
|
text: terminalContent.text,
|
|
@@ -8161,10 +8381,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8161
8381
|
card: abortCard,
|
|
8162
8382
|
accountId: this.deps.accountId
|
|
8163
8383
|
});
|
|
8164
|
-
log$
|
|
8384
|
+
log$14.info("abortCard completed (IM fallback)", { messageId: this.cardKit.cardMessageId });
|
|
8165
8385
|
}
|
|
8166
8386
|
} catch (err) {
|
|
8167
|
-
log$
|
|
8387
|
+
log$14.warn("abortCard failed", { error: String(err) });
|
|
8168
8388
|
} finally {
|
|
8169
8389
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
8170
8390
|
}
|
|
@@ -8179,10 +8399,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8179
8399
|
async forceCloseStreaming() {
|
|
8180
8400
|
const effectiveCardId = this.cardKit.cardKitCardId ?? this.cardKit.originalCardKitCardId;
|
|
8181
8401
|
if (!effectiveCardId && !this.cardKit.cardMessageId) {
|
|
8182
|
-
log$
|
|
8402
|
+
log$14.warn("forceCloseStreaming: no card to close");
|
|
8183
8403
|
return;
|
|
8184
8404
|
}
|
|
8185
|
-
log$
|
|
8405
|
+
log$14.info("forceCloseStreaming: 强制终态化卡片", {
|
|
8186
8406
|
cardId: effectiveCardId,
|
|
8187
8407
|
messageId: this.cardKit.cardMessageId,
|
|
8188
8408
|
phase: this.phase
|
|
@@ -8232,10 +8452,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8232
8452
|
card: completeCard,
|
|
8233
8453
|
accountId: this.deps.accountId
|
|
8234
8454
|
});
|
|
8235
|
-
log$
|
|
8455
|
+
log$14.info("forceCloseStreaming: 卡片已强制终态化");
|
|
8236
8456
|
if (!this.isTerminalPhase) this.finalizeCard("forceCloseStreaming", "abort");
|
|
8237
8457
|
} catch (err) {
|
|
8238
|
-
log$
|
|
8458
|
+
log$14.error("forceCloseStreaming failed", { error: String(err) });
|
|
8239
8459
|
} finally {
|
|
8240
8460
|
unregisterActiveCard(this.deps.sessionKey);
|
|
8241
8461
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
@@ -8259,7 +8479,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8259
8479
|
this.disposeShutdownHook = null;
|
|
8260
8480
|
return;
|
|
8261
8481
|
}
|
|
8262
|
-
log$
|
|
8482
|
+
log$14.info("reusing placeholder card", {
|
|
8263
8483
|
cardId: this.deps.placeholderCardId,
|
|
8264
8484
|
messageId: this.deps.placeholderMessageId
|
|
8265
8485
|
});
|
|
@@ -8283,7 +8503,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8283
8503
|
accountId: this.deps.accountId
|
|
8284
8504
|
});
|
|
8285
8505
|
if (this.isStaleCreate(epoch)) {
|
|
8286
|
-
log$
|
|
8506
|
+
log$14.info("ensureCardCreated: stale epoch after createCardEntity, bailing out", {
|
|
8287
8507
|
epoch,
|
|
8288
8508
|
phase: this.phase
|
|
8289
8509
|
});
|
|
@@ -8294,7 +8514,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8294
8514
|
this.cardKit.originalCardKitCardId = cId;
|
|
8295
8515
|
this.cardKit.cardKitSequence = 1;
|
|
8296
8516
|
this.disposeShutdownHook = registerShutdownHook(`streaming-card:${cId}`, () => this.abortCard());
|
|
8297
|
-
log$
|
|
8517
|
+
log$14.info("created CardKit entity", {
|
|
8298
8518
|
cardId: cId,
|
|
8299
8519
|
initialSequence: this.cardKit.cardKitSequence
|
|
8300
8520
|
});
|
|
@@ -8307,7 +8527,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8307
8527
|
accountId: this.deps.accountId
|
|
8308
8528
|
});
|
|
8309
8529
|
if (this.isStaleCreate(epoch)) {
|
|
8310
|
-
log$
|
|
8530
|
+
log$14.info("ensureCardCreated: stale epoch after sendCardByCardId, bailing out", {
|
|
8311
8531
|
epoch,
|
|
8312
8532
|
phase: this.phase
|
|
8313
8533
|
});
|
|
@@ -8322,13 +8542,13 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8322
8542
|
this.disposeShutdownHook = null;
|
|
8323
8543
|
return;
|
|
8324
8544
|
}
|
|
8325
|
-
log$
|
|
8545
|
+
log$14.info("sent CardKit card", { messageId: result.messageId });
|
|
8326
8546
|
} else throw new Error("card.create returned empty card_id");
|
|
8327
8547
|
} catch (cardKitErr) {
|
|
8328
8548
|
if (this.isStaleCreate(epoch)) return;
|
|
8329
8549
|
if (this.guard.terminate("ensureCardCreated.cardkitFlow", cardKitErr)) return;
|
|
8330
8550
|
const apiDetail = extractApiDetail(cardKitErr);
|
|
8331
|
-
log$
|
|
8551
|
+
log$14.warn("CardKit flow failed, falling back to IM", { apiDetail });
|
|
8332
8552
|
this.cardKit.cardKitCardId = null;
|
|
8333
8553
|
this.cardKit.originalCardKitCardId = null;
|
|
8334
8554
|
const fallbackCard = buildCardContent("streaming", { showToolUse: this.deps.toolUseDisplay.showToolUse });
|
|
@@ -8341,7 +8561,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8341
8561
|
accountId: this.deps.accountId
|
|
8342
8562
|
});
|
|
8343
8563
|
if (this.isStaleCreate(epoch)) {
|
|
8344
|
-
log$
|
|
8564
|
+
log$14.info("ensureCardCreated: stale epoch after IM fallback send, bailing out", {
|
|
8345
8565
|
epoch,
|
|
8346
8566
|
phase: this.phase
|
|
8347
8567
|
});
|
|
@@ -8350,12 +8570,12 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8350
8570
|
this.cardKit.cardMessageId = result.messageId;
|
|
8351
8571
|
this.flush.setCardMessageReady(true);
|
|
8352
8572
|
if (!this.transition("streaming", "ensureCardCreated.imFallback")) return;
|
|
8353
|
-
log$
|
|
8573
|
+
log$14.info("sent fallback IM card", { messageId: result.messageId });
|
|
8354
8574
|
}
|
|
8355
8575
|
} catch (err) {
|
|
8356
8576
|
if (this.isStaleCreate(epoch)) return;
|
|
8357
8577
|
if (this.guard.terminate("ensureCardCreated.outer", err)) return;
|
|
8358
|
-
log$
|
|
8578
|
+
log$14.warn("thinking card failed, falling back to static", { error: String(err) });
|
|
8359
8579
|
this.transition("creation_failed", "ensureCardCreated.outer", "creation_failed");
|
|
8360
8580
|
}
|
|
8361
8581
|
})();
|
|
@@ -8364,10 +8584,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8364
8584
|
async performFlush() {
|
|
8365
8585
|
if (!this.cardKit.cardMessageId || this.isTerminalPhase) return;
|
|
8366
8586
|
if (!this.cardKit.cardKitCardId && this.cardKit.originalCardKitCardId) {
|
|
8367
|
-
log$
|
|
8587
|
+
log$14.debug("performFlush: skipping (CardKit streaming disabled, awaiting final update)");
|
|
8368
8588
|
return;
|
|
8369
8589
|
}
|
|
8370
|
-
log$
|
|
8590
|
+
log$14.debug("flushCardUpdate: enter", {
|
|
8371
8591
|
seq: this.cardKit.cardKitSequence,
|
|
8372
8592
|
isCardKit: !!this.cardKit.cardKitCardId
|
|
8373
8593
|
});
|
|
@@ -8389,7 +8609,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8389
8609
|
reasoningText: this.reasoning.accumulatedReasoningText || void 0
|
|
8390
8610
|
});
|
|
8391
8611
|
this.cardKit.cardKitSequence += 1;
|
|
8392
|
-
log$
|
|
8612
|
+
log$14.debug("flushCardUpdate: full card update (dirty)", {
|
|
8393
8613
|
seq: this.cardKit.cardKitSequence,
|
|
8394
8614
|
stepCount: display?.stepCount,
|
|
8395
8615
|
hasReasoning: !!this.reasoning.accumulatedReasoningText
|
|
@@ -8405,7 +8625,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8405
8625
|
} else if (resolvedText !== this.text.lastFlushedText) {
|
|
8406
8626
|
const prevSeq = this.cardKit.cardKitSequence;
|
|
8407
8627
|
this.cardKit.cardKitSequence += 1;
|
|
8408
|
-
log$
|
|
8628
|
+
log$14.debug("flushCardUpdate: answer seq bump", {
|
|
8409
8629
|
seqBefore: prevSeq,
|
|
8410
8630
|
seqAfter: this.cardKit.cardKitSequence
|
|
8411
8631
|
});
|
|
@@ -8420,7 +8640,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8420
8640
|
this.text.lastFlushedText = resolvedText;
|
|
8421
8641
|
}
|
|
8422
8642
|
} else {
|
|
8423
|
-
log$
|
|
8643
|
+
log$14.debug("flushCardUpdate: IM patch fallback");
|
|
8424
8644
|
const flushDisplay = this.computeToolUseDisplay();
|
|
8425
8645
|
const card = buildCardContent("streaming", {
|
|
8426
8646
|
text: this.reasoning.isReasoningPhase ? "" : resolvedText,
|
|
@@ -8440,31 +8660,31 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8440
8660
|
if (this.guard.terminate("flushCardUpdate", err)) return;
|
|
8441
8661
|
const apiCode = extractLarkApiCode(err);
|
|
8442
8662
|
if (isCardRateLimitError(err)) {
|
|
8443
|
-
log$
|
|
8663
|
+
log$14.info("flushCardUpdate: rate limited (230020), skipping", { seq: this.cardKit.cardKitSequence });
|
|
8444
8664
|
return;
|
|
8445
8665
|
}
|
|
8446
8666
|
if (isCardTableLimitError(err)) {
|
|
8447
|
-
log$
|
|
8667
|
+
log$14.warn("flushCardUpdate: card table limit exceeded (230099/11310), disabling CardKit streaming", { seq: this.cardKit.cardKitSequence });
|
|
8448
8668
|
this.cardKit.cardKitCardId = null;
|
|
8449
8669
|
return;
|
|
8450
8670
|
}
|
|
8451
8671
|
if (apiCode === 300317 && this.cardKit.cardKitCardId) {
|
|
8452
8672
|
const oldSeq = this.cardKit.cardKitSequence;
|
|
8453
8673
|
this.cardKit.cardKitSequence += 100;
|
|
8454
|
-
log$
|
|
8674
|
+
log$14.warn("flushCardUpdate: sequence conflict (300317), jumping sequence", {
|
|
8455
8675
|
oldSeq,
|
|
8456
8676
|
newSeq: this.cardKit.cardKitSequence
|
|
8457
8677
|
});
|
|
8458
8678
|
return;
|
|
8459
8679
|
}
|
|
8460
8680
|
const apiDetail = extractApiDetail(err);
|
|
8461
|
-
log$
|
|
8681
|
+
log$14.error("card stream update failed", {
|
|
8462
8682
|
apiCode,
|
|
8463
8683
|
seq: this.cardKit.cardKitSequence,
|
|
8464
8684
|
apiDetail
|
|
8465
8685
|
});
|
|
8466
8686
|
if (this.cardKit.cardKitCardId) {
|
|
8467
|
-
log$
|
|
8687
|
+
log$14.warn("disabling CardKit streaming, falling back to im.message.patch");
|
|
8468
8688
|
this.cardKit.cardKitCardId = null;
|
|
8469
8689
|
}
|
|
8470
8690
|
}
|
|
@@ -8489,7 +8709,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8489
8709
|
if (!this.cardKit.cardKitCardId || this.isTerminalPhase) return;
|
|
8490
8710
|
try {
|
|
8491
8711
|
const display = this.computeToolUseDisplay();
|
|
8492
|
-
log$
|
|
8712
|
+
log$14.info("updateToolUseStatus", {
|
|
8493
8713
|
hasDisplay: !!display,
|
|
8494
8714
|
stepCount: display?.stepCount,
|
|
8495
8715
|
steps: display?.steps?.map((s) => `${s.title}:${s.status}`).join(", ")
|
|
@@ -8511,7 +8731,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8511
8731
|
accountId: this.deps.accountId
|
|
8512
8732
|
});
|
|
8513
8733
|
} catch (err) {
|
|
8514
|
-
log$
|
|
8734
|
+
log$14.debug("updateToolUseStatus failed", { error: String(err) });
|
|
8515
8735
|
}
|
|
8516
8736
|
}
|
|
8517
8737
|
finalizeCard(source, reason) {
|
|
@@ -8523,7 +8743,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8523
8743
|
async closeStreamingAndUpdate(cardId, card, label) {
|
|
8524
8744
|
const seqBeforeClose = this.cardKit.cardKitSequence;
|
|
8525
8745
|
this.cardKit.cardKitSequence += 1;
|
|
8526
|
-
log$
|
|
8746
|
+
log$14.info(`${label}: closing streaming mode`, {
|
|
8527
8747
|
seqBefore: seqBeforeClose,
|
|
8528
8748
|
seqAfter: this.cardKit.cardKitSequence
|
|
8529
8749
|
});
|
|
@@ -8536,7 +8756,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8536
8756
|
});
|
|
8537
8757
|
const seqBeforeUpdate = this.cardKit.cardKitSequence;
|
|
8538
8758
|
this.cardKit.cardKitSequence += 1;
|
|
8539
|
-
log$
|
|
8759
|
+
log$14.info(`${label}: updating card`, {
|
|
8540
8760
|
seqBefore: seqBeforeUpdate,
|
|
8541
8761
|
seqAfter: this.cardKit.cardKitSequence
|
|
8542
8762
|
});
|
|
@@ -8570,7 +8790,7 @@ function extractApiDetail(err) {
|
|
|
8570
8790
|
}
|
|
8571
8791
|
//#endregion
|
|
8572
8792
|
//#region src/messaging/format-for-cc.ts
|
|
8573
|
-
const log$
|
|
8793
|
+
const log$13 = larkLogger("messaging/format-for-cc");
|
|
8574
8794
|
function safeParseJSON(raw) {
|
|
8575
8795
|
try {
|
|
8576
8796
|
return JSON.parse(raw);
|
|
@@ -8626,7 +8846,7 @@ function extractPostText(rawContent) {
|
|
|
8626
8846
|
*/
|
|
8627
8847
|
function formatContentByType(ctx) {
|
|
8628
8848
|
const { contentType, content, resources } = ctx;
|
|
8629
|
-
log$
|
|
8849
|
+
log$13.debug("formatContentByType 开始格式化", {
|
|
8630
8850
|
contentType,
|
|
8631
8851
|
contentLength: content?.length ?? 0,
|
|
8632
8852
|
resourceCount: resources?.length ?? 0
|
|
@@ -8668,7 +8888,7 @@ function formatContentByType(ctx) {
|
|
|
8668
8888
|
case "todo":
|
|
8669
8889
|
case "vote": return content;
|
|
8670
8890
|
default:
|
|
8671
|
-
log$
|
|
8891
|
+
log$13.warn("遇到不支持的消息类型", { contentType });
|
|
8672
8892
|
return `[不支持的消息类型: ${contentType}]`;
|
|
8673
8893
|
}
|
|
8674
8894
|
}
|
|
@@ -8711,7 +8931,7 @@ function formatMessageTime(createTime) {
|
|
|
8711
8931
|
* @returns 格式化后的纯文本字符串
|
|
8712
8932
|
*/
|
|
8713
8933
|
function formatForCC(ctx, isGroup) {
|
|
8714
|
-
log$
|
|
8934
|
+
log$13.info("formatForCC 开始处理", {
|
|
8715
8935
|
messageId: ctx.messageId,
|
|
8716
8936
|
contentType: ctx.contentType,
|
|
8717
8937
|
chatType: ctx.chatType,
|
|
@@ -8727,7 +8947,7 @@ function formatForCC(ctx, isGroup) {
|
|
|
8727
8947
|
if (isGroup && ctx.senderName) parts.push(`[${ctx.senderName}]`);
|
|
8728
8948
|
if (ctx.threadId) parts.push("[话题回复]");
|
|
8729
8949
|
const result = (parts.length > 0 ? parts.join(" ") + " " : "") + formattedContent;
|
|
8730
|
-
log$
|
|
8950
|
+
log$13.info("formatForCC 完成", {
|
|
8731
8951
|
messageId: ctx.messageId,
|
|
8732
8952
|
resultLength: result.length,
|
|
8733
8953
|
hasTime: !!timeStr,
|
|
@@ -8737,7 +8957,7 @@ function formatForCC(ctx, isGroup) {
|
|
|
8737
8957
|
}
|
|
8738
8958
|
//#endregion
|
|
8739
8959
|
//#region src/messaging/inbound/dispatch-cc.ts
|
|
8740
|
-
const log$
|
|
8960
|
+
const log$12 = larkLogger("inbound/dispatch-cc");
|
|
8741
8961
|
async function dispatchToCC(params) {
|
|
8742
8962
|
const { ctx, account, sessionRouter, processManager } = params;
|
|
8743
8963
|
const chatId = ctx.chatId;
|
|
@@ -8747,7 +8967,7 @@ async function dispatchToCC(params) {
|
|
|
8747
8967
|
const ccModel = params.model;
|
|
8748
8968
|
const maxTurns = params.maxTurns;
|
|
8749
8969
|
const maxBudgetUsd = params.maxBudgetUsd;
|
|
8750
|
-
log$
|
|
8970
|
+
log$12.info("dispatchToCC 开始", {
|
|
8751
8971
|
msgId,
|
|
8752
8972
|
chatId,
|
|
8753
8973
|
chatType,
|
|
@@ -8760,12 +8980,12 @@ async function dispatchToCC(params) {
|
|
|
8760
8980
|
threadId,
|
|
8761
8981
|
userId: ctx.senderId
|
|
8762
8982
|
});
|
|
8763
|
-
log$
|
|
8983
|
+
log$12.info("会话路由解析完成", {
|
|
8764
8984
|
sessionId: route.sessionId,
|
|
8765
8985
|
cwd: route.cwd,
|
|
8766
8986
|
type: route.type
|
|
8767
8987
|
});
|
|
8768
|
-
if (params.userContext) log$
|
|
8988
|
+
if (params.userContext) log$12.info("用户上下文已注入", {
|
|
8769
8989
|
userId: params.userContext.userId,
|
|
8770
8990
|
isTenantIdentity: params.userContext.isTenantIdentity,
|
|
8771
8991
|
credentialDir: params.userContext.credentialDir
|
|
@@ -8802,7 +9022,7 @@ async function dispatchToCC(params) {
|
|
|
8802
9022
|
for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
8803
9023
|
buffer = Buffer.concat(chunks);
|
|
8804
9024
|
} else {
|
|
8805
|
-
log$
|
|
9025
|
+
log$12.warn("图片资源下载返回未知格式,跳过", {
|
|
8806
9026
|
fileKey: imgRes.fileKey,
|
|
8807
9027
|
responseType: typeof response
|
|
8808
9028
|
});
|
|
@@ -8810,7 +9030,7 @@ async function dispatchToCC(params) {
|
|
|
8810
9030
|
}
|
|
8811
9031
|
if (resp.headers?.["content-type"]) mediaType = resp.headers["content-type"];
|
|
8812
9032
|
} else {
|
|
8813
|
-
log$
|
|
9033
|
+
log$12.warn("图片资源下载返回空,跳过", { fileKey: imgRes.fileKey });
|
|
8814
9034
|
continue;
|
|
8815
9035
|
}
|
|
8816
9036
|
const imgFileName = `${imgRes.fileKey}.png`;
|
|
@@ -8820,13 +9040,13 @@ async function dispatchToCC(params) {
|
|
|
8820
9040
|
const { writeFile, mkdir } = await import("fs/promises");
|
|
8821
9041
|
await mkdir(filesDir, { recursive: true });
|
|
8822
9042
|
await writeFile(imgFilePath, buffer);
|
|
8823
|
-
log$
|
|
9043
|
+
log$12.info("图片已保存到工作目录", {
|
|
8824
9044
|
fileKey: imgRes.fileKey,
|
|
8825
9045
|
path: imgFilePath,
|
|
8826
9046
|
sizeBytes: buffer.length
|
|
8827
9047
|
});
|
|
8828
9048
|
} catch (saveErr) {
|
|
8829
|
-
log$
|
|
9049
|
+
log$12.warn("图片保存到工作目录失败", {
|
|
8830
9050
|
fileKey: imgRes.fileKey,
|
|
8831
9051
|
path: imgFilePath,
|
|
8832
9052
|
error: saveErr instanceof Error ? saveErr.message : String(saveErr)
|
|
@@ -8841,13 +9061,13 @@ async function dispatchToCC(params) {
|
|
|
8841
9061
|
data: base64Data
|
|
8842
9062
|
}
|
|
8843
9063
|
});
|
|
8844
|
-
log$
|
|
9064
|
+
log$12.info("图片资源下载成功", {
|
|
8845
9065
|
fileKey: imgRes.fileKey,
|
|
8846
9066
|
mediaType,
|
|
8847
9067
|
sizeBytes: buffer.length
|
|
8848
9068
|
});
|
|
8849
9069
|
} catch (err) {
|
|
8850
|
-
log$
|
|
9070
|
+
log$12.warn("图片资源下载失败,跳过", {
|
|
8851
9071
|
fileKey: imgRes.fileKey,
|
|
8852
9072
|
error: err instanceof Error ? err.message : String(err)
|
|
8853
9073
|
});
|
|
@@ -8857,7 +9077,7 @@ async function dispatchToCC(params) {
|
|
|
8857
9077
|
type: "text",
|
|
8858
9078
|
text: textPrompt
|
|
8859
9079
|
}, ...imageBlocks];
|
|
8860
|
-
log$
|
|
9080
|
+
log$12.info("已构建多模态 prompt", {
|
|
8861
9081
|
textLength: textPrompt.length,
|
|
8862
9082
|
imageCount: imageBlocks.length
|
|
8863
9083
|
});
|
|
@@ -8870,7 +9090,7 @@ async function dispatchToCC(params) {
|
|
|
8870
9090
|
await fsMkdir(filesDir, { recursive: true });
|
|
8871
9091
|
let updatedTextPrompt = typeof prompt === "string" ? prompt : prompt[0].text;
|
|
8872
9092
|
for (const res of attachmentResources) try {
|
|
8873
|
-
log$
|
|
9093
|
+
log$12.info("开始下载非图片附件", {
|
|
8874
9094
|
type: res.type,
|
|
8875
9095
|
fileKey: res.fileKey,
|
|
8876
9096
|
fileName: res.fileName,
|
|
@@ -8884,7 +9104,7 @@ async function dispatchToCC(params) {
|
|
|
8884
9104
|
},
|
|
8885
9105
|
params: { type: "file" }
|
|
8886
9106
|
});
|
|
8887
|
-
log$
|
|
9107
|
+
log$12.info("非图片附件 API 响应已收到", {
|
|
8888
9108
|
type: res.type,
|
|
8889
9109
|
fileKey: res.fileKey,
|
|
8890
9110
|
responseType: typeof response,
|
|
@@ -8903,7 +9123,7 @@ async function dispatchToCC(params) {
|
|
|
8903
9123
|
for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
8904
9124
|
buffer = Buffer.concat(chunks);
|
|
8905
9125
|
} else {
|
|
8906
|
-
log$
|
|
9126
|
+
log$12.warn("非图片附件下载返回未知格式,跳过", {
|
|
8907
9127
|
type: res.type,
|
|
8908
9128
|
fileKey: res.fileKey,
|
|
8909
9129
|
responseType: typeof response
|
|
@@ -8911,7 +9131,7 @@ async function dispatchToCC(params) {
|
|
|
8911
9131
|
continue;
|
|
8912
9132
|
}
|
|
8913
9133
|
} else {
|
|
8914
|
-
log$
|
|
9134
|
+
log$12.warn("非图片附件下载返回空,跳过", {
|
|
8915
9135
|
type: res.type,
|
|
8916
9136
|
fileKey: res.fileKey
|
|
8917
9137
|
});
|
|
@@ -8934,7 +9154,7 @@ async function dispatchToCC(params) {
|
|
|
8934
9154
|
}
|
|
8935
9155
|
const filePath = path.join(filesDir, savedFileName);
|
|
8936
9156
|
await fsWriteFile(filePath, buffer);
|
|
8937
|
-
log$
|
|
9157
|
+
log$12.info("非图片附件已保存到工作目录", {
|
|
8938
9158
|
type: res.type,
|
|
8939
9159
|
fileKey: res.fileKey,
|
|
8940
9160
|
savedFileName,
|
|
@@ -8968,7 +9188,7 @@ async function dispatchToCC(params) {
|
|
|
8968
9188
|
}
|
|
8969
9189
|
}
|
|
8970
9190
|
} catch (err) {
|
|
8971
|
-
log$
|
|
9191
|
+
log$12.warn("非图片附件下载失败,保留原始占位符", {
|
|
8972
9192
|
type: res.type,
|
|
8973
9193
|
fileKey: res.fileKey,
|
|
8974
9194
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -8979,12 +9199,12 @@ async function dispatchToCC(params) {
|
|
|
8979
9199
|
const textBlock = prompt.find((b) => b.type === "text");
|
|
8980
9200
|
if (textBlock) textBlock.text = updatedTextPrompt;
|
|
8981
9201
|
}
|
|
8982
|
-
log$
|
|
9202
|
+
log$12.info("非图片附件处理完成", {
|
|
8983
9203
|
totalAttachments: attachmentResources.length,
|
|
8984
9204
|
promptLength: typeof prompt === "string" ? prompt.length : prompt.find((b) => b.type === "text")?.text?.length
|
|
8985
9205
|
});
|
|
8986
9206
|
}
|
|
8987
|
-
log$
|
|
9207
|
+
log$12.info("消息格式化完成", {
|
|
8988
9208
|
promptLength: typeof prompt === "string" ? prompt.length : prompt.length,
|
|
8989
9209
|
isMultimodal: Array.isArray(prompt),
|
|
8990
9210
|
isGroup
|
|
@@ -9016,50 +9236,50 @@ async function dispatchToCC(params) {
|
|
|
9016
9236
|
};
|
|
9017
9237
|
const cardController = new StreamingCardController(cardDeps);
|
|
9018
9238
|
registerActiveCard(route.sessionId, cardController);
|
|
9019
|
-
log$
|
|
9239
|
+
log$12.info("StreamingCardController 已创建", {
|
|
9020
9240
|
sessionId: route.sessionId,
|
|
9021
9241
|
chatId,
|
|
9022
9242
|
replyToMessageId: cardDeps.replyToMessageId
|
|
9023
9243
|
});
|
|
9024
9244
|
cardController.ensureCardCreated().catch((err) => {
|
|
9025
|
-
log$
|
|
9245
|
+
log$12.warn("提前创建卡片失败(streaming 回调会重试)", { error: String(err) });
|
|
9026
9246
|
});
|
|
9027
9247
|
const bridge = new CCStreamBridge(cardController, {
|
|
9028
9248
|
autoCompleteOnTurnEnd: true,
|
|
9029
9249
|
sessionKey: route.sessionId
|
|
9030
9250
|
});
|
|
9031
|
-
log$
|
|
9251
|
+
log$12.info("CCStreamBridge 已创建", { sessionId: route.sessionId });
|
|
9032
9252
|
const callbacks = {
|
|
9033
9253
|
onTextDelta: (text) => {
|
|
9034
|
-
log$
|
|
9254
|
+
log$12.debug("CC onTextDelta", {
|
|
9035
9255
|
sessionId: route.sessionId,
|
|
9036
9256
|
deltaLen: text.length
|
|
9037
9257
|
});
|
|
9038
9258
|
bridge.onTextDelta(text);
|
|
9039
9259
|
},
|
|
9040
9260
|
onThinkingDelta: (text) => {
|
|
9041
|
-
log$
|
|
9261
|
+
log$12.debug("CC onThinkingDelta", {
|
|
9042
9262
|
sessionId: route.sessionId,
|
|
9043
9263
|
deltaLen: text.length
|
|
9044
9264
|
});
|
|
9045
9265
|
bridge.onThinkingDelta(text);
|
|
9046
9266
|
},
|
|
9047
9267
|
onToolUseStart: (toolName, toolInput) => {
|
|
9048
|
-
log$
|
|
9268
|
+
log$12.info("CC onToolUseStart", {
|
|
9049
9269
|
sessionId: route.sessionId,
|
|
9050
9270
|
toolName
|
|
9051
9271
|
});
|
|
9052
9272
|
bridge.onToolUseStart(toolName, toolInput);
|
|
9053
9273
|
},
|
|
9054
9274
|
onToolResult: (toolUseId) => {
|
|
9055
|
-
log$
|
|
9275
|
+
log$12.info("CC onToolResult", {
|
|
9056
9276
|
sessionId: route.sessionId,
|
|
9057
9277
|
toolUseId
|
|
9058
9278
|
});
|
|
9059
9279
|
bridge.onToolResult(toolUseId);
|
|
9060
9280
|
},
|
|
9061
9281
|
onToolProgress: (toolName, elapsedSeconds) => {
|
|
9062
|
-
log$
|
|
9282
|
+
log$12.debug("CC onToolProgress", {
|
|
9063
9283
|
sessionId: route.sessionId,
|
|
9064
9284
|
toolName,
|
|
9065
9285
|
elapsedSeconds
|
|
@@ -9067,14 +9287,14 @@ async function dispatchToCC(params) {
|
|
|
9067
9287
|
bridge.onToolProgress(toolName, elapsedSeconds);
|
|
9068
9288
|
},
|
|
9069
9289
|
onTurnEnd: (stopReason) => {
|
|
9070
|
-
log$
|
|
9290
|
+
log$12.info("CC onTurnEnd", {
|
|
9071
9291
|
sessionId: route.sessionId,
|
|
9072
9292
|
stopReason
|
|
9073
9293
|
});
|
|
9074
9294
|
bridge.onTurnEnd(stopReason);
|
|
9075
9295
|
},
|
|
9076
9296
|
onResult: (result) => {
|
|
9077
|
-
log$
|
|
9297
|
+
log$12.info("CC onResult", {
|
|
9078
9298
|
sessionId: route.sessionId,
|
|
9079
9299
|
subtype: result.subtype,
|
|
9080
9300
|
isError: result.isError,
|
|
@@ -9085,14 +9305,14 @@ async function dispatchToCC(params) {
|
|
|
9085
9305
|
bridge.onResult(result);
|
|
9086
9306
|
},
|
|
9087
9307
|
onError: (error) => {
|
|
9088
|
-
log$
|
|
9308
|
+
log$12.error("CC 进程错误", {
|
|
9089
9309
|
sessionId: route.sessionId,
|
|
9090
9310
|
error: error.message
|
|
9091
9311
|
});
|
|
9092
9312
|
cardController.onError(error, { kind: "cc-process" });
|
|
9093
9313
|
}
|
|
9094
9314
|
};
|
|
9095
|
-
log$
|
|
9315
|
+
log$12.info("开始执行 CC 进程", {
|
|
9096
9316
|
sessionId: route.sessionId,
|
|
9097
9317
|
cwd: route.cwd,
|
|
9098
9318
|
promptLength: prompt.length,
|
|
@@ -9109,10 +9329,10 @@ async function dispatchToCC(params) {
|
|
|
9109
9329
|
maxBudgetUsd,
|
|
9110
9330
|
userContext: params.userContext
|
|
9111
9331
|
}, callbacks);
|
|
9112
|
-
log$
|
|
9332
|
+
log$12.info("CC 进程 executePrompt 调用完成", { sessionId: route.sessionId });
|
|
9113
9333
|
} catch (err) {
|
|
9114
9334
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
9115
|
-
log$
|
|
9335
|
+
log$12.error("CC 进程 executePrompt 调用异常", {
|
|
9116
9336
|
sessionId: route.sessionId,
|
|
9117
9337
|
error: errorMessage
|
|
9118
9338
|
});
|
|
@@ -9134,7 +9354,7 @@ async function dispatchToCC(params) {
|
|
|
9134
9354
|
*/
|
|
9135
9355
|
async function dispatchTeammateEval(params) {
|
|
9136
9356
|
const { chatId, prompt, account, sessionRouter, processManager } = params;
|
|
9137
|
-
log$
|
|
9357
|
+
log$12.info("dispatchTeammateEval 开始", {
|
|
9138
9358
|
chatId,
|
|
9139
9359
|
promptLength: prompt.length
|
|
9140
9360
|
});
|
|
@@ -9151,12 +9371,11 @@ async function dispatchTeammateEval(params) {
|
|
|
9151
9371
|
startToolUseTraceRun(teammateSessionKey);
|
|
9152
9372
|
const NO_REPLY_TOKEN = SILENT_REPLY_TOKEN;
|
|
9153
9373
|
const CC_INTERNAL_PLACEHOLDER = "No response requested.";
|
|
9154
|
-
const PREFIX_LEN = 22;
|
|
9155
9374
|
/** 缓冲的 thinking 内容 */
|
|
9156
9375
|
let thinkingBuffer = "";
|
|
9157
|
-
/** text
|
|
9158
|
-
let
|
|
9159
|
-
/** 是否已确认 CC
|
|
9376
|
+
/** 全部 text 输出缓冲(end_turn 时统一判断是否含 NO_REPLY) */
|
|
9377
|
+
let fullTextBuffer = "";
|
|
9378
|
+
/** 是否已确认 CC 决定回复(仅在 end_turn 时才确认) */
|
|
9160
9379
|
let confirmed = false;
|
|
9161
9380
|
/** 是否已确认 CC 决定静默 */
|
|
9162
9381
|
let silenced = false;
|
|
@@ -9169,15 +9388,15 @@ async function dispatchTeammateEval(params) {
|
|
|
9169
9388
|
const pendingToolEvents = [];
|
|
9170
9389
|
let finalStopReason = "";
|
|
9171
9390
|
/**
|
|
9172
|
-
* 确认 CC 要回复 — 创建卡片 + bridge,灌入缓冲的 thinking + text
|
|
9391
|
+
* 确认 CC 要回复 — 创建卡片 + bridge,灌入缓冲的 thinking + text
|
|
9173
9392
|
*/
|
|
9174
9393
|
const confirmReply = () => {
|
|
9175
9394
|
if (confirmed) return;
|
|
9176
9395
|
confirmed = true;
|
|
9177
|
-
log$
|
|
9396
|
+
log$12.info("teammate 确认回复,创建流式卡片", {
|
|
9178
9397
|
chatId,
|
|
9179
9398
|
thinkingLen: thinkingBuffer.length,
|
|
9180
|
-
|
|
9399
|
+
textLen: fullTextBuffer.length
|
|
9181
9400
|
});
|
|
9182
9401
|
cardController = new StreamingCardController({
|
|
9183
9402
|
cfg: {},
|
|
@@ -9208,13 +9427,18 @@ async function dispatchTeammateEval(params) {
|
|
|
9208
9427
|
sessionKey: teammateSessionKey
|
|
9209
9428
|
});
|
|
9210
9429
|
if (thinkingBuffer) bridge.onThinkingDelta(thinkingBuffer);
|
|
9211
|
-
|
|
9430
|
+
const cleanedText = fullTextBuffer.replace(/\s*NO_REPLY\s*$/, "").trim();
|
|
9431
|
+
if (cleanedText) bridge.onTextDelta(cleanedText);
|
|
9212
9432
|
for (const evt of pendingToolEvents) if (evt.type === "start") bridge.onToolUseStart(evt.name, evt.input);
|
|
9213
9433
|
else bridge.onToolResult(evt.id);
|
|
9214
9434
|
pendingToolEvents.length = 0;
|
|
9215
9435
|
};
|
|
9216
9436
|
/**
|
|
9217
|
-
* 处理 text delta —
|
|
9437
|
+
* 处理 text delta — 全量缓冲,直到 end_turn 时统一判断
|
|
9438
|
+
*
|
|
9439
|
+
* 策略:CC 在 teammate 模式下可能先执行工具再输出文本,且文本中可能正文在前、
|
|
9440
|
+
* NO_REPLY 在末尾。因此不做前缀检测,而是缓冲所有 text,在 onTurnEnd(end_turn)
|
|
9441
|
+
* 时统一判断完整输出是否包含 NO_REPLY。
|
|
9218
9442
|
*/
|
|
9219
9443
|
const handleTextDelta = (text) => {
|
|
9220
9444
|
if (silenced) return;
|
|
@@ -9222,24 +9446,7 @@ async function dispatchTeammateEval(params) {
|
|
|
9222
9446
|
bridge.onTextDelta(text);
|
|
9223
9447
|
return;
|
|
9224
9448
|
}
|
|
9225
|
-
|
|
9226
|
-
if (textPrefixBuffer.length >= PREFIX_LEN) {
|
|
9227
|
-
const trimmed = textPrefixBuffer.trim();
|
|
9228
|
-
if (trimmed === NO_REPLY_TOKEN || trimmed === CC_INTERNAL_PLACEHOLDER) {
|
|
9229
|
-
silenced = true;
|
|
9230
|
-
log$11.info("teammate 前缀检测: 静默", {
|
|
9231
|
-
chatId,
|
|
9232
|
-
trimmed: trimmed.slice(0, 30)
|
|
9233
|
-
});
|
|
9234
|
-
return;
|
|
9235
|
-
}
|
|
9236
|
-
confirmReply();
|
|
9237
|
-
return;
|
|
9238
|
-
}
|
|
9239
|
-
const currentTrimmed = textPrefixBuffer.trim();
|
|
9240
|
-
const couldBeNoReply = NO_REPLY_TOKEN.startsWith(currentTrimmed);
|
|
9241
|
-
const couldBePlaceholder = CC_INTERNAL_PLACEHOLDER.startsWith(currentTrimmed);
|
|
9242
|
-
if (!couldBeNoReply && !couldBePlaceholder) confirmReply();
|
|
9449
|
+
fullTextBuffer += text;
|
|
9243
9450
|
};
|
|
9244
9451
|
try {
|
|
9245
9452
|
await new Promise((resolve) => {
|
|
@@ -9280,17 +9487,17 @@ async function dispatchTeammateEval(params) {
|
|
|
9280
9487
|
onTurnEnd: (stopReason) => {
|
|
9281
9488
|
finalStopReason = stopReason;
|
|
9282
9489
|
if (stopReason === "tool_use") {
|
|
9283
|
-
log$
|
|
9490
|
+
log$12.debug("teammate turnEnd: tool_use, 跳过最终判断", { chatId });
|
|
9284
9491
|
if (confirmed && bridge) bridge.onTurnEnd(stopReason);
|
|
9285
9492
|
return;
|
|
9286
9493
|
}
|
|
9287
9494
|
if (!confirmed && !silenced) {
|
|
9288
|
-
const trimmed =
|
|
9289
|
-
if (trimmed === NO_REPLY_TOKEN || trimmed === CC_INTERNAL_PLACEHOLDER || trimmed === "") {
|
|
9495
|
+
const trimmed = fullTextBuffer.trim();
|
|
9496
|
+
if (trimmed === NO_REPLY_TOKEN || trimmed === CC_INTERNAL_PLACEHOLDER || trimmed.endsWith(NO_REPLY_TOKEN) || trimmed === "") {
|
|
9290
9497
|
silenced = true;
|
|
9291
|
-
log$
|
|
9498
|
+
log$12.info("teammate turnEnd 最终判断: 静默", {
|
|
9292
9499
|
chatId,
|
|
9293
|
-
trimmed: trimmed.slice(0,
|
|
9500
|
+
trimmed: trimmed.slice(0, 60)
|
|
9294
9501
|
});
|
|
9295
9502
|
} else {
|
|
9296
9503
|
confirmReply();
|
|
@@ -9303,7 +9510,7 @@ async function dispatchTeammateEval(params) {
|
|
|
9303
9510
|
resolve();
|
|
9304
9511
|
},
|
|
9305
9512
|
onError: (error) => {
|
|
9306
|
-
log$
|
|
9513
|
+
log$12.error("teammate 评估 CC 进程错误", {
|
|
9307
9514
|
chatId,
|
|
9308
9515
|
error: error.message
|
|
9309
9516
|
});
|
|
@@ -9316,25 +9523,25 @@ async function dispatchTeammateEval(params) {
|
|
|
9316
9523
|
prompt,
|
|
9317
9524
|
model: params.model
|
|
9318
9525
|
}, callbacks).catch((err) => {
|
|
9319
|
-
log$
|
|
9526
|
+
log$12.error("teammate executePrompt 异常", {
|
|
9320
9527
|
chatId,
|
|
9321
9528
|
error: err instanceof Error ? err.message : String(err)
|
|
9322
9529
|
});
|
|
9323
9530
|
resolve();
|
|
9324
9531
|
});
|
|
9325
9532
|
});
|
|
9326
|
-
log$
|
|
9533
|
+
log$12.info("dispatchTeammateEval 完成", {
|
|
9327
9534
|
chatId,
|
|
9328
9535
|
confirmed,
|
|
9329
9536
|
silenced,
|
|
9330
|
-
|
|
9537
|
+
fullTextBufferLen: fullTextBuffer.length,
|
|
9331
9538
|
thinkingBufferLen: thinkingBuffer.length,
|
|
9332
9539
|
stopReason: finalStopReason
|
|
9333
9540
|
});
|
|
9334
9541
|
return { replied: confirmed };
|
|
9335
9542
|
} catch (err) {
|
|
9336
9543
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
9337
|
-
log$
|
|
9544
|
+
log$12.error("dispatchTeammateEval 异常", {
|
|
9338
9545
|
chatId,
|
|
9339
9546
|
error: errorMessage
|
|
9340
9547
|
});
|
|
@@ -10846,7 +11053,7 @@ const convertLocation = (raw) => {
|
|
|
10846
11053
|
* injected via callbacks in `ConvertContext`. Callers are responsible
|
|
10847
11054
|
* for creating the appropriate callbacks (UAT / TAT / event push).
|
|
10848
11055
|
*/
|
|
10849
|
-
const log$
|
|
11056
|
+
const log$11 = larkLogger("converters/merge-forward");
|
|
10850
11057
|
/**
|
|
10851
11058
|
* Recursively expand a merge_forward message.
|
|
10852
11059
|
*
|
|
@@ -10874,7 +11081,7 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
|
|
|
10874
11081
|
try {
|
|
10875
11082
|
items = await fetchSubMessages(messageId);
|
|
10876
11083
|
} catch (error) {
|
|
10877
|
-
log$
|
|
11084
|
+
log$11.error("fetch sub-messages failed", {
|
|
10878
11085
|
messageId,
|
|
10879
11086
|
error: error instanceof Error ? error.message : String(error)
|
|
10880
11087
|
});
|
|
@@ -10886,7 +11093,7 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
|
|
|
10886
11093
|
if (senderIds.length > 0 && batchResolveNames) try {
|
|
10887
11094
|
await batchResolveNames(senderIds);
|
|
10888
11095
|
} catch (err) {
|
|
10889
|
-
log$
|
|
11096
|
+
log$11.debug("batchResolveNames failed (best-effort)", { error: err instanceof Error ? err.message : String(err) });
|
|
10890
11097
|
}
|
|
10891
11098
|
return formatSubTree(messageId, childrenMap, accountId, resolveUserName, convertContent);
|
|
10892
11099
|
}
|
|
@@ -10966,7 +11173,7 @@ async function formatSubTree(parentId, childrenMap, accountId, resolveUserName,
|
|
|
10966
11173
|
const indented = indentLines(content, " ");
|
|
10967
11174
|
parts.push(`[${timestamp}] ${displayName}:\n${indented}`);
|
|
10968
11175
|
} catch (err) {
|
|
10969
|
-
log$
|
|
11176
|
+
log$11.warn("failed to convert sub-message", {
|
|
10970
11177
|
messageId: item.message_id,
|
|
10971
11178
|
msgType: item.msg_type ?? "unknown",
|
|
10972
11179
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -11180,7 +11387,7 @@ async function convertMessageContent(raw, messageType, ctx) {
|
|
|
11180
11387
|
}
|
|
11181
11388
|
//#endregion
|
|
11182
11389
|
//#region src/messaging/inbound/parse-io.ts
|
|
11183
|
-
const log$
|
|
11390
|
+
const log$10 = larkLogger("inbound/parse-io");
|
|
11184
11391
|
/**
|
|
11185
11392
|
* 对 interactive 消息,通过 TAT 调用 API 获取完整 v2 卡片内容。
|
|
11186
11393
|
* 事件推送的 content 可能不包含 json_card,API 调用可返回完整的 raw_card_content。
|
|
@@ -11200,7 +11407,7 @@ async function fetchCardContent(messageId, larkClient) {
|
|
|
11200
11407
|
}
|
|
11201
11408
|
}))?.data?.items?.[0]?.body?.content ?? void 0;
|
|
11202
11409
|
} catch (err) {
|
|
11203
|
-
log$
|
|
11410
|
+
log$10.warn(`fetchCardContent failed for ${messageId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
11204
11411
|
return;
|
|
11205
11412
|
}
|
|
11206
11413
|
}
|
|
@@ -11234,11 +11441,11 @@ function createFetchSubMessages(larkClient) {
|
|
|
11234
11441
|
* the account and log function.
|
|
11235
11442
|
*/
|
|
11236
11443
|
function createParseResolveNames(account) {
|
|
11237
|
-
return createBatchResolveNames(account, (...args) => log$
|
|
11444
|
+
return createBatchResolveNames(account, (...args) => log$10.info(args.map(String).join(" ")));
|
|
11238
11445
|
}
|
|
11239
11446
|
//#endregion
|
|
11240
11447
|
//#region src/messaging/inbound/parse.ts
|
|
11241
|
-
const log$
|
|
11448
|
+
const log$9 = larkLogger("inbound/parse");
|
|
11242
11449
|
/**
|
|
11243
11450
|
* Parse a raw Feishu message event into a normalised MessageContext.
|
|
11244
11451
|
*
|
|
@@ -11288,7 +11495,7 @@ async function parseMessageEvent(event, botOpenId, expandCtx) {
|
|
|
11288
11495
|
const fullContent = await fetchCardContent(event.message.message_id, larkClient);
|
|
11289
11496
|
if (fullContent) {
|
|
11290
11497
|
effectiveContent = fullContent;
|
|
11291
|
-
log$
|
|
11498
|
+
log$9.info("replaced interactive content with full v2 card data");
|
|
11292
11499
|
}
|
|
11293
11500
|
}
|
|
11294
11501
|
const convertCtx = {
|
|
@@ -11408,6 +11615,123 @@ var MessageDedup = class {
|
|
|
11408
11615
|
}
|
|
11409
11616
|
};
|
|
11410
11617
|
//#endregion
|
|
11618
|
+
//#region src/messaging/inbound/media-download.ts
|
|
11619
|
+
const log$8 = larkLogger("inbound/media-download");
|
|
11620
|
+
/**
|
|
11621
|
+
* 从飞书 API 下载单个图片资源,返回 Buffer + mediaType
|
|
11622
|
+
*/
|
|
11623
|
+
async function downloadLarkImage(params) {
|
|
11624
|
+
const { account, messageId, fileKey } = params;
|
|
11625
|
+
try {
|
|
11626
|
+
const response = await LarkClient.fromAccount(account).sdk.im.messageResource.get({
|
|
11627
|
+
path: {
|
|
11628
|
+
message_id: messageId,
|
|
11629
|
+
file_key: fileKey
|
|
11630
|
+
},
|
|
11631
|
+
params: { type: "image" }
|
|
11632
|
+
});
|
|
11633
|
+
let buffer;
|
|
11634
|
+
let mediaType = "image/png";
|
|
11635
|
+
if (response instanceof ArrayBuffer) buffer = Buffer.from(response);
|
|
11636
|
+
else if (Buffer.isBuffer(response)) buffer = response;
|
|
11637
|
+
else if (response && typeof response === "object") {
|
|
11638
|
+
const resp = response;
|
|
11639
|
+
if (resp.data instanceof ArrayBuffer) buffer = Buffer.from(resp.data);
|
|
11640
|
+
else if (Buffer.isBuffer(resp.data)) buffer = resp.data;
|
|
11641
|
+
else if (resp.writeFile) {
|
|
11642
|
+
const chunks = [];
|
|
11643
|
+
const readable = resp.getReadableStream();
|
|
11644
|
+
for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
11645
|
+
buffer = Buffer.concat(chunks);
|
|
11646
|
+
} else {
|
|
11647
|
+
log$8.warn("图片资源下载返回未知格式", {
|
|
11648
|
+
fileKey,
|
|
11649
|
+
responseType: typeof response
|
|
11650
|
+
});
|
|
11651
|
+
return null;
|
|
11652
|
+
}
|
|
11653
|
+
if (resp.headers?.["content-type"]) mediaType = resp.headers["content-type"];
|
|
11654
|
+
} else {
|
|
11655
|
+
log$8.warn("图片资源下载返回空", { fileKey });
|
|
11656
|
+
return null;
|
|
11657
|
+
}
|
|
11658
|
+
log$8.info("图片资源下载成功", {
|
|
11659
|
+
fileKey,
|
|
11660
|
+
mediaType,
|
|
11661
|
+
sizeBytes: buffer.length
|
|
11662
|
+
});
|
|
11663
|
+
return {
|
|
11664
|
+
buffer,
|
|
11665
|
+
mediaType
|
|
11666
|
+
};
|
|
11667
|
+
} catch (err) {
|
|
11668
|
+
log$8.warn("图片资源下载失败", {
|
|
11669
|
+
fileKey,
|
|
11670
|
+
error: err instanceof Error ? err.message : String(err)
|
|
11671
|
+
});
|
|
11672
|
+
return null;
|
|
11673
|
+
}
|
|
11674
|
+
}
|
|
11675
|
+
/**
|
|
11676
|
+
* 批量下载图片资源并构建 CCContentBlock[] 多模态 prompt
|
|
11677
|
+
*
|
|
11678
|
+
* @param textPrompt - 文本部分
|
|
11679
|
+
* @param imageResources - 图片资源描述符列表
|
|
11680
|
+
* @param account - 飞书账号(用于 API 调用)
|
|
11681
|
+
* @param messageId - 消息 ID(用于下载)
|
|
11682
|
+
* @param saveDir - 可选,保存图片到该目录(供 CC 文件系统访问)
|
|
11683
|
+
* @returns 多模态 prompt(如果有图片)或纯文本 prompt
|
|
11684
|
+
*/
|
|
11685
|
+
async function buildMultimodalPrompt(params) {
|
|
11686
|
+
const { textPrompt, imageResources, account, messageId, saveDir } = params;
|
|
11687
|
+
if (imageResources.length === 0) return textPrompt;
|
|
11688
|
+
const imageBlocks = [];
|
|
11689
|
+
for (const imgRes of imageResources) {
|
|
11690
|
+
const result = await downloadLarkImage({
|
|
11691
|
+
account,
|
|
11692
|
+
messageId,
|
|
11693
|
+
fileKey: imgRes.fileKey
|
|
11694
|
+
});
|
|
11695
|
+
if (!result) continue;
|
|
11696
|
+
const { buffer, mediaType } = result;
|
|
11697
|
+
if (saveDir) try {
|
|
11698
|
+
const { writeFile, mkdir } = await import("fs/promises");
|
|
11699
|
+
const path = await import("path");
|
|
11700
|
+
await mkdir(saveDir, { recursive: true });
|
|
11701
|
+
const imgFilePath = path.join(saveDir, `${imgRes.fileKey}.png`);
|
|
11702
|
+
await writeFile(imgFilePath, buffer);
|
|
11703
|
+
log$8.info("图片已保存到工作目录", {
|
|
11704
|
+
fileKey: imgRes.fileKey,
|
|
11705
|
+
path: imgFilePath
|
|
11706
|
+
});
|
|
11707
|
+
} catch (saveErr) {
|
|
11708
|
+
log$8.warn("图片保存失败", {
|
|
11709
|
+
fileKey: imgRes.fileKey,
|
|
11710
|
+
error: saveErr instanceof Error ? saveErr.message : String(saveErr)
|
|
11711
|
+
});
|
|
11712
|
+
}
|
|
11713
|
+
imageBlocks.push({
|
|
11714
|
+
type: "image",
|
|
11715
|
+
source: {
|
|
11716
|
+
type: "base64",
|
|
11717
|
+
media_type: mediaType,
|
|
11718
|
+
data: buffer.toString("base64")
|
|
11719
|
+
}
|
|
11720
|
+
});
|
|
11721
|
+
}
|
|
11722
|
+
if (imageBlocks.length > 0) {
|
|
11723
|
+
log$8.info("已构建多模态 prompt", {
|
|
11724
|
+
textLength: textPrompt.length,
|
|
11725
|
+
imageCount: imageBlocks.length
|
|
11726
|
+
});
|
|
11727
|
+
return [{
|
|
11728
|
+
type: "text",
|
|
11729
|
+
text: textPrompt
|
|
11730
|
+
}, ...imageBlocks];
|
|
11731
|
+
}
|
|
11732
|
+
return textPrompt;
|
|
11733
|
+
}
|
|
11734
|
+
//#endregion
|
|
11411
11735
|
//#region src/user/types.ts
|
|
11412
11736
|
/** Tenant 级别的 UserContext(群聊默认身份) */
|
|
11413
11737
|
const TENANT_USER_ID = "tenant";
|
|
@@ -11533,9 +11857,7 @@ async function ensureUserDirectory(userCtx) {
|
|
|
11533
11857
|
larkLogger("user/oauth-proxy");
|
|
11534
11858
|
//#endregion
|
|
11535
11859
|
//#region src/gateway/plugins/loader.ts
|
|
11536
|
-
const PLUGIN_REGISTRY = {
|
|
11537
|
-
return (await import("./preview-proxy-KMPQK_j4.mjs")).createPreviewProxyPlugin;
|
|
11538
|
-
} };
|
|
11860
|
+
const PLUGIN_REGISTRY = {};
|
|
11539
11861
|
const log$4 = larkLogger("gateway/plugin-loader");
|
|
11540
11862
|
/** 已加载的插件实例列表 */
|
|
11541
11863
|
let loadedPlugins = [];
|
|
@@ -11983,7 +12305,8 @@ var TeammateBuffer = class {
|
|
|
11983
12305
|
this.evalCallback({
|
|
11984
12306
|
chatId,
|
|
11985
12307
|
prompt,
|
|
11986
|
-
messageCount: messages.length
|
|
12308
|
+
messageCount: messages.length,
|
|
12309
|
+
messages: [...messages]
|
|
11987
12310
|
}).catch((err) => {
|
|
11988
12311
|
log$1.error("teammate 评估回调异常", {
|
|
11989
12312
|
chatId,
|
|
@@ -12003,8 +12326,11 @@ var TeammateBuffer = class {
|
|
|
12003
12326
|
const header = [
|
|
12004
12327
|
"[旁听评估任务] 以下是群聊中最近的对话,你作为团队成员正在旁听。",
|
|
12005
12328
|
"请判断是否需要主动参与讨论。如果不需要,只输出 NO_REPLY。",
|
|
12006
|
-
"
|
|
12007
|
-
"
|
|
12329
|
+
"如果需要参与,直接输出你的回复内容(不要加 NO_REPLY)。",
|
|
12330
|
+
"",
|
|
12331
|
+
"重要规则:",
|
|
12332
|
+
"- 这是一次旁听评估,仅在本次评估中适用 NO_REPLY 规则。后续如果用户直接 @你 则必须正常回复。",
|
|
12333
|
+
"- 不要使用工具来查找或分析消息内容,直接基于消息内容判断是否参与。",
|
|
12008
12334
|
"",
|
|
12009
12335
|
"---"
|
|
12010
12336
|
].join("\n");
|
|
@@ -12516,6 +12842,8 @@ async function main() {
|
|
|
12516
12842
|
workspaceRoot,
|
|
12517
12843
|
externalBaseUrl: process.env.LARKPAL_EXTERNAL_URL
|
|
12518
12844
|
});
|
|
12845
|
+
const { discoverTools } = await import("./tool-registry-consumer-DrklfqGF.mjs").then((n) => n.n);
|
|
12846
|
+
await discoverTools(gatewayPort);
|
|
12519
12847
|
await gateway.start();
|
|
12520
12848
|
logger.info("网关 HTTP 服务启动完成", {
|
|
12521
12849
|
host: gatewayHost,
|
|
@@ -12557,13 +12885,29 @@ async function main() {
|
|
|
12557
12885
|
messageCount: payload.messageCount,
|
|
12558
12886
|
promptLength: payload.prompt.length
|
|
12559
12887
|
});
|
|
12888
|
+
const allImageResources = payload.messages.flatMap((m) => (m.resources ?? []).filter((r) => r.type === "image"));
|
|
12889
|
+
const lastImageMsg = [...payload.messages].reverse().find((m) => m.resources?.some((r) => r.type === "image"));
|
|
12890
|
+
let evalPrompt = payload.prompt;
|
|
12891
|
+
if (allImageResources.length > 0 && lastImageMsg) {
|
|
12892
|
+
evalPrompt = await buildMultimodalPrompt({
|
|
12893
|
+
textPrompt: payload.prompt,
|
|
12894
|
+
imageResources: allImageResources,
|
|
12895
|
+
account,
|
|
12896
|
+
messageId: lastImageMsg.messageId
|
|
12897
|
+
});
|
|
12898
|
+
logger.info("teammate 评估已构建多模态 prompt", {
|
|
12899
|
+
chatId: payload.chatId,
|
|
12900
|
+
imageCount: allImageResources.length,
|
|
12901
|
+
isMultimodal: Array.isArray(evalPrompt)
|
|
12902
|
+
});
|
|
12903
|
+
}
|
|
12560
12904
|
const { status: queueStatus } = enqueueFeishuChatTask({
|
|
12561
12905
|
accountId: "default",
|
|
12562
12906
|
chatId: payload.chatId,
|
|
12563
12907
|
task: async () => {
|
|
12564
12908
|
const result = await dispatchTeammateEval({
|
|
12565
12909
|
chatId: payload.chatId,
|
|
12566
|
-
prompt:
|
|
12910
|
+
prompt: evalPrompt,
|
|
12567
12911
|
account,
|
|
12568
12912
|
sessionRouter,
|
|
12569
12913
|
processManager: runtimeAdapter,
|
|
@@ -12652,7 +12996,8 @@ async function main() {
|
|
|
12652
12996
|
teammateBuffer.push(parsed.chatId, {
|
|
12653
12997
|
formattedText,
|
|
12654
12998
|
bufferedAt: Date.now(),
|
|
12655
|
-
messageId: msgId
|
|
12999
|
+
messageId: msgId,
|
|
13000
|
+
resources: parsed.resources.length > 0 ? parsed.resources : void 0
|
|
12656
13001
|
});
|
|
12657
13002
|
logger.info("回复策略: teammate 消息已写入缓冲", {
|
|
12658
13003
|
msgId,
|