@vibe-lark/larkpal 0.1.25 → 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 +534 -487
- 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
|
});
|
|
@@ -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) {
|
|
@@ -2197,40 +2198,43 @@ async function runBatchTasks(taskConfigs, concurrency, processManager) {
|
|
|
2197
2198
|
logger$6.info("批量任务全部完成", { totalTasks: taskConfigs.length });
|
|
2198
2199
|
}
|
|
2199
2200
|
//#endregion
|
|
2200
|
-
//#region src/gateway/
|
|
2201
|
+
//#region src/gateway/agent-completion-handler.ts
|
|
2201
2202
|
/**
|
|
2202
|
-
* AI
|
|
2203
|
+
* 通用 AI 补全路由 — Agent Gateway 统一能力接口
|
|
2203
2204
|
*
|
|
2204
|
-
* 提供 POST /api/
|
|
2205
|
-
* 内部通过 CC(Claude Code)RuntimeAdapter
|
|
2205
|
+
* 提供 POST /api/agent/completion 接口,接收任意外部服务的 AI 任务请求。
|
|
2206
|
+
* 内部通过 CC(Claude Code)RuntimeAdapter 执行。
|
|
2207
|
+
*
|
|
2208
|
+
* 本模块是通用的 AI 能力网关接口,不包含任何特定业务逻辑。
|
|
2209
|
+
* 各接入方(如 rd-assistant-api 等)自行构造 prompt 和解析结果。
|
|
2206
2210
|
*
|
|
2207
2211
|
* 支持两种模式:
|
|
2208
|
-
* - 同步模式(默认):等待 CC
|
|
2209
|
-
* -
|
|
2212
|
+
* - 同步模式(默认):等待 CC 完成后直接返回结果文本
|
|
2213
|
+
* - 异步模式:立即返回 accepted,完成后通过 X-Callback-Url 回调通知
|
|
2210
2214
|
*
|
|
2211
2215
|
* 鉴权:通过 X-Internal-Secret 头验证服务间调用身份
|
|
2212
2216
|
*/
|
|
2213
|
-
const logger$5 = larkLogger("gateway/
|
|
2217
|
+
const logger$5 = larkLogger("gateway/agent-completion");
|
|
2214
2218
|
/** 内部通信密钥,从环境变量读取 */
|
|
2215
2219
|
const INTERNAL_SECRET = process.env.LARKPAL_API_SECRET || "dev-internal-secret";
|
|
2216
|
-
/** CC
|
|
2217
|
-
const
|
|
2220
|
+
/** CC 执行时的工作目录(使用临时目录) */
|
|
2221
|
+
const COMPLETION_CWD = process.env.LARKPAL_COMPLETION_CWD || "/tmp/larkpal-completion";
|
|
2218
2222
|
/**
|
|
2219
|
-
*
|
|
2223
|
+
* 创建通用 AI 补全路由
|
|
2220
2224
|
*
|
|
2221
|
-
* @param runtimeAdapter - CC
|
|
2225
|
+
* @param runtimeAdapter - CC 运行时适配器实例
|
|
2222
2226
|
*/
|
|
2223
|
-
function
|
|
2227
|
+
function createAgentCompletionRouter(runtimeAdapter) {
|
|
2224
2228
|
const router = Router();
|
|
2225
|
-
router.post("/api/
|
|
2226
|
-
|
|
2229
|
+
router.post("/api/agent/completion", (req, res) => {
|
|
2230
|
+
handleCompletion(req, res, runtimeAdapter);
|
|
2227
2231
|
});
|
|
2228
2232
|
return router;
|
|
2229
2233
|
}
|
|
2230
|
-
async function
|
|
2234
|
+
async function handleCompletion(req, res, runtimeAdapter) {
|
|
2231
2235
|
const secret = req.headers["x-internal-secret"];
|
|
2232
2236
|
if (secret !== INTERNAL_SECRET) {
|
|
2233
|
-
logger$5.warn("
|
|
2237
|
+
logger$5.warn("Agent completion 鉴权失败", { providedSecret: secret?.slice(0, 8) + "..." });
|
|
2234
2238
|
res.status(401).json({
|
|
2235
2239
|
code: 1,
|
|
2236
2240
|
message: "鉴权失败"
|
|
@@ -2239,70 +2243,71 @@ async function handleExtract(req, res, runtimeAdapter) {
|
|
|
2239
2243
|
}
|
|
2240
2244
|
const callbackUrl = req.headers["x-callback-url"];
|
|
2241
2245
|
const body = req.body;
|
|
2242
|
-
if (!body.taskId || !body.
|
|
2243
|
-
logger$5.warn("
|
|
2246
|
+
if (!body.taskId || !body.prompt) {
|
|
2247
|
+
logger$5.warn("Agent completion 参数不完整", {
|
|
2244
2248
|
hasTaskId: !!body.taskId,
|
|
2245
|
-
|
|
2246
|
-
hasFileBase64: !!body.fileBase64,
|
|
2247
|
-
hasModuleName: !!body.moduleName
|
|
2249
|
+
hasPrompt: !!body.prompt
|
|
2248
2250
|
});
|
|
2249
2251
|
res.status(400).json({
|
|
2250
2252
|
code: 1,
|
|
2251
|
-
message: "缺少必要参数: taskId,
|
|
2253
|
+
message: "缺少必要参数: taskId, prompt"
|
|
2252
2254
|
});
|
|
2253
2255
|
return;
|
|
2254
2256
|
}
|
|
2255
|
-
logger$5.info("收到
|
|
2257
|
+
logger$5.info("收到 Agent completion 请求", {
|
|
2256
2258
|
taskId: body.taskId,
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
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
|
|
2263
2265
|
});
|
|
2264
|
-
const prompt = buildExtractionPrompt(body);
|
|
2265
2266
|
try {
|
|
2266
2267
|
const { mkdir, writeFile } = await import("node:fs/promises");
|
|
2267
2268
|
const { join } = await import("node:path");
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
const
|
|
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, {
|
|
2280
2284
|
sessionId,
|
|
2281
|
-
cwd:
|
|
2282
|
-
prompt,
|
|
2283
|
-
maxTurns
|
|
2285
|
+
cwd: taskDir,
|
|
2286
|
+
prompt: body.prompt,
|
|
2287
|
+
maxTurns
|
|
2284
2288
|
});
|
|
2285
|
-
|
|
2286
|
-
try {
|
|
2289
|
+
if (resultFileName) try {
|
|
2287
2290
|
const { readFile } = await import("node:fs/promises");
|
|
2288
|
-
const
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
+
const resultFilePath = join(taskDir, resultFileName);
|
|
2292
|
+
const fileContent = await readFile(resultFilePath, "utf-8");
|
|
2293
|
+
logger$5.info("从结果文件读取输出", {
|
|
2294
|
+
resultFilePath,
|
|
2291
2295
|
contentLength: fileContent.length
|
|
2292
2296
|
});
|
|
2293
|
-
|
|
2297
|
+
finalResult = fileContent;
|
|
2294
2298
|
} catch {
|
|
2295
|
-
logger$5.info("
|
|
2296
|
-
extractedData = parseExtractionResult(result);
|
|
2299
|
+
logger$5.info("结果文件不存在,使用 CC 文本输出");
|
|
2297
2300
|
}
|
|
2298
2301
|
try {
|
|
2299
|
-
const {
|
|
2300
|
-
await
|
|
2301
|
-
|
|
2302
|
+
const { rm } = await import("node:fs/promises");
|
|
2303
|
+
await rm(taskDir, {
|
|
2304
|
+
recursive: true,
|
|
2305
|
+
force: true
|
|
2306
|
+
});
|
|
2302
2307
|
} catch {}
|
|
2303
|
-
logger$5.info("
|
|
2308
|
+
logger$5.info("Agent completion 完成", {
|
|
2304
2309
|
taskId: body.taskId,
|
|
2305
|
-
|
|
2310
|
+
resultLength: finalResult.length
|
|
2306
2311
|
});
|
|
2307
2312
|
if (callbackUrl) {
|
|
2308
2313
|
res.status(200).json({
|
|
@@ -2310,16 +2315,16 @@ async function handleExtract(req, res, runtimeAdapter) {
|
|
|
2310
2315
|
message: "accepted",
|
|
2311
2316
|
mode: "async"
|
|
2312
2317
|
});
|
|
2313
|
-
await sendCallback(callbackUrl, body.taskId, "success",
|
|
2318
|
+
await sendCallback(callbackUrl, body.taskId, "success", finalResult);
|
|
2314
2319
|
} else res.status(200).json({
|
|
2315
2320
|
code: 0,
|
|
2316
2321
|
message: "ok",
|
|
2317
2322
|
mode: "sync",
|
|
2318
|
-
|
|
2323
|
+
result: finalResult
|
|
2319
2324
|
});
|
|
2320
2325
|
} catch (err) {
|
|
2321
2326
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2322
|
-
logger$5.error("
|
|
2327
|
+
logger$5.error("Agent completion 执行失败", {
|
|
2323
2328
|
taskId: body.taskId,
|
|
2324
2329
|
error: errorMsg
|
|
2325
2330
|
});
|
|
@@ -2332,63 +2337,11 @@ async function handleExtract(req, res, runtimeAdapter) {
|
|
|
2332
2337
|
await sendCallback(callbackUrl, body.taskId, "failed", void 0, errorMsg);
|
|
2333
2338
|
} else res.status(500).json({
|
|
2334
2339
|
code: 1,
|
|
2335
|
-
message:
|
|
2340
|
+
message: `执行失败: ${errorMsg}`
|
|
2336
2341
|
});
|
|
2337
2342
|
}
|
|
2338
2343
|
}
|
|
2339
2344
|
/**
|
|
2340
|
-
* 构造用于数据提取的结构化 Prompt
|
|
2341
|
-
*/
|
|
2342
|
-
function buildExtractionPrompt(body) {
|
|
2343
|
-
const indicatorsList = (body.indicators || []).map((ind, i) => ` ${i + 1}. ${ind.name}${ind.unit ? ` (单位: ${ind.unit})` : ""}${ind.format ? ` [格式: ${ind.format}]` : ""}`).join("\n");
|
|
2344
|
-
const fileExt = body.fileName.split(".").pop() || "";
|
|
2345
|
-
return `你是一个精确的数据提取助手。请从提供的文件中提取结构化性能测试数据。
|
|
2346
|
-
|
|
2347
|
-
## 任务说明
|
|
2348
|
-
- 文件名: ${body.fileName}
|
|
2349
|
-
- 性能模块: ${body.moduleName}
|
|
2350
|
-
- 提取方式: ${body.extractionType}
|
|
2351
|
-
|
|
2352
|
-
## 需要提取的指标
|
|
2353
|
-
${indicatorsList}
|
|
2354
|
-
|
|
2355
|
-
## 文件路径
|
|
2356
|
-
文件已保存在当前目录: ./${body.taskId}.${fileExt}
|
|
2357
|
-
|
|
2358
|
-
## 输出要求
|
|
2359
|
-
请读取文件内容,提取出所有样品/配方的性能数据。
|
|
2360
|
-
|
|
2361
|
-
**必须以严格 JSON 格式输出**,不要包含任何其他文字。输出格式如下:
|
|
2362
|
-
\`\`\`json
|
|
2363
|
-
[
|
|
2364
|
-
{
|
|
2365
|
-
"formulaNo": "样品/配方编号(如果文件中有)",
|
|
2366
|
-
"indicators": {
|
|
2367
|
-
"指标名称1": 数值或字符串,
|
|
2368
|
-
"指标名称2": 数值或字符串
|
|
2369
|
-
},
|
|
2370
|
-
"testConditions": {
|
|
2371
|
-
"测试温度": "23°C",
|
|
2372
|
-
"测试标准": "相关标准号"
|
|
2373
|
-
},
|
|
2374
|
-
"confidence": 0.95,
|
|
2375
|
-
"notes": "备注信息(如有异常值或不确定项)"
|
|
2376
|
-
}
|
|
2377
|
-
]
|
|
2378
|
-
\`\`\`
|
|
2379
|
-
|
|
2380
|
-
## 注意事项
|
|
2381
|
-
1. confidence 为 0-1 之间的浮点数,表示对该条数据提取准确性的置信度
|
|
2382
|
-
2. 如果文件中某个指标的值模糊或无法确定,将 confidence 降低并在 notes 中说明
|
|
2383
|
-
3. 如果文件是图片(OCR),先描述图片内容再提取数据
|
|
2384
|
-
4. 每个独立的样品/配方/实验组应作为数组中的一个元素
|
|
2385
|
-
5. 数值类指标请直接输出数字类型(不要带单位),单位已在指标定义中给出
|
|
2386
|
-
|
|
2387
|
-
请直接读取文件并输出 JSON 结果。
|
|
2388
|
-
|
|
2389
|
-
**重要:你必须将最终的 JSON 结果写入文件 \`./${body.taskId}-result.json\`(只写纯 JSON 数组,不要包含 markdown 代码块标记)。**`;
|
|
2390
|
-
}
|
|
2391
|
-
/**
|
|
2392
2345
|
* 调用 CC 执行 prompt 并等待完成
|
|
2393
2346
|
*/
|
|
2394
2347
|
async function executeAndWaitResult(runtimeAdapter, config) {
|
|
@@ -2417,55 +2370,15 @@ async function executeAndWaitResult(runtimeAdapter, config) {
|
|
|
2417
2370
|
});
|
|
2418
2371
|
}
|
|
2419
2372
|
/**
|
|
2420
|
-
* 从 CC 输出中解析结构化数据
|
|
2421
|
-
*/
|
|
2422
|
-
function parseExtractionResult(rawOutput) {
|
|
2423
|
-
logger$5.info("解析 CC 输出", {
|
|
2424
|
-
outputLength: rawOutput.length,
|
|
2425
|
-
preview: rawOutput.slice(0, 200)
|
|
2426
|
-
});
|
|
2427
|
-
const codeBlockMatch = rawOutput.match(/```json\s*\n?([\s\S]*?)```/);
|
|
2428
|
-
if (codeBlockMatch) try {
|
|
2429
|
-
return parseAndValidateJson(codeBlockMatch[1].trim());
|
|
2430
|
-
} catch {}
|
|
2431
|
-
const arrayMatch = rawOutput.match(/\[[\s\S]*\]/);
|
|
2432
|
-
if (arrayMatch) try {
|
|
2433
|
-
return parseAndValidateJson(arrayMatch[0]);
|
|
2434
|
-
} catch {}
|
|
2435
|
-
const objMatch = rawOutput.match(/\{[\s\S]*\}/);
|
|
2436
|
-
if (objMatch) try {
|
|
2437
|
-
return parseAndValidateJson(`[${objMatch[0]}]`);
|
|
2438
|
-
} catch {}
|
|
2439
|
-
try {
|
|
2440
|
-
return parseAndValidateJson(rawOutput.trim());
|
|
2441
|
-
} catch {
|
|
2442
|
-
logger$5.warn("无法从 CC 输出中解析 JSON,返回空结果", { output: rawOutput.slice(0, 500) });
|
|
2443
|
-
return [];
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
2446
|
-
/**
|
|
2447
|
-
* 解析并校验 JSON 格式的提取结果
|
|
2448
|
-
*/
|
|
2449
|
-
function parseAndValidateJson(jsonStr) {
|
|
2450
|
-
const parsed = JSON.parse(jsonStr);
|
|
2451
|
-
return (Array.isArray(parsed) ? parsed : [parsed]).map((item) => ({
|
|
2452
|
-
formulaNo: item.formulaNo || item.formula_no || item.sampleId || item.sample_id,
|
|
2453
|
-
indicators: item.indicators || item.values || {},
|
|
2454
|
-
testConditions: item.testConditions || item.test_conditions || item.conditions,
|
|
2455
|
-
confidence: typeof item.confidence === "number" ? Math.max(0, Math.min(1, item.confidence)) : .8,
|
|
2456
|
-
notes: item.notes || item.remarks
|
|
2457
|
-
}));
|
|
2458
|
-
}
|
|
2459
|
-
/**
|
|
2460
2373
|
* 向调用方发送回调通知
|
|
2461
2374
|
*/
|
|
2462
|
-
async function sendCallback(callbackUrl, taskId, status,
|
|
2375
|
+
async function sendCallback(callbackUrl, taskId, status, result, error) {
|
|
2463
2376
|
try {
|
|
2464
2377
|
logger$5.info("发送回调通知", {
|
|
2465
2378
|
callbackUrl,
|
|
2466
2379
|
taskId,
|
|
2467
2380
|
status,
|
|
2468
|
-
|
|
2381
|
+
resultLength: result?.length
|
|
2469
2382
|
});
|
|
2470
2383
|
const response = await fetch(callbackUrl, {
|
|
2471
2384
|
method: "POST",
|
|
@@ -2476,7 +2389,7 @@ async function sendCallback(callbackUrl, taskId, status, data, error) {
|
|
|
2476
2389
|
body: JSON.stringify({
|
|
2477
2390
|
taskId,
|
|
2478
2391
|
status,
|
|
2479
|
-
|
|
2392
|
+
result,
|
|
2480
2393
|
error
|
|
2481
2394
|
})
|
|
2482
2395
|
});
|
|
@@ -2952,7 +2865,7 @@ function createHooksRouter() {
|
|
|
2952
2865
|
* 提供 /api/scheduled-tasks 的 RESTful CRUD 接口。
|
|
2953
2866
|
* 每个请求和响应都记录详细日志,便于后续错误排查。
|
|
2954
2867
|
*/
|
|
2955
|
-
const log$
|
|
2868
|
+
const log$24 = larkLogger("gateway/scheduler-handler");
|
|
2956
2869
|
/**
|
|
2957
2870
|
* 创建定时任务路由
|
|
2958
2871
|
*
|
|
@@ -2963,16 +2876,16 @@ function createSchedulerRouter(taskManager) {
|
|
|
2963
2876
|
const router = Router();
|
|
2964
2877
|
router.get("/api/scheduled-tasks", (req, res) => {
|
|
2965
2878
|
const requestId = req.headers["x-request-id"];
|
|
2966
|
-
log$
|
|
2879
|
+
log$24.info("请求列出所有定时任务", { requestId });
|
|
2967
2880
|
try {
|
|
2968
2881
|
const tasks = taskManager.listTasks();
|
|
2969
|
-
log$
|
|
2882
|
+
log$24.info("返回定时任务列表", {
|
|
2970
2883
|
requestId,
|
|
2971
2884
|
count: tasks.length
|
|
2972
2885
|
});
|
|
2973
2886
|
res.json({ tasks });
|
|
2974
2887
|
} catch (err) {
|
|
2975
|
-
log$
|
|
2888
|
+
log$24.error("列出定时任务失败", {
|
|
2976
2889
|
requestId,
|
|
2977
2890
|
error: String(err)
|
|
2978
2891
|
});
|
|
@@ -2985,13 +2898,13 @@ function createSchedulerRouter(taskManager) {
|
|
|
2985
2898
|
router.post("/api/scheduled-tasks", (req, res) => {
|
|
2986
2899
|
const requestId = req.headers["x-request-id"];
|
|
2987
2900
|
const body = req.body;
|
|
2988
|
-
log$
|
|
2901
|
+
log$24.info("请求创建定时任务", {
|
|
2989
2902
|
requestId,
|
|
2990
2903
|
body: JSON.stringify(body)
|
|
2991
2904
|
});
|
|
2992
2905
|
const { name, cron: cronExpr, session_id, cwd, prompt, example_output, constraints } = body;
|
|
2993
2906
|
if (!name || typeof name !== "string") {
|
|
2994
|
-
log$
|
|
2907
|
+
log$24.warn("创建任务参数缺失: name", { requestId });
|
|
2995
2908
|
res.status(400).json({
|
|
2996
2909
|
error: "参数错误",
|
|
2997
2910
|
detail: "name 为必填字符串"
|
|
@@ -2999,7 +2912,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
2999
2912
|
return;
|
|
3000
2913
|
}
|
|
3001
2914
|
if (!cronExpr || typeof cronExpr !== "string") {
|
|
3002
|
-
log$
|
|
2915
|
+
log$24.warn("创建任务参数缺失: cron", { requestId });
|
|
3003
2916
|
res.status(400).json({
|
|
3004
2917
|
error: "参数错误",
|
|
3005
2918
|
detail: "cron 为必填字符串"
|
|
@@ -3007,7 +2920,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
3007
2920
|
return;
|
|
3008
2921
|
}
|
|
3009
2922
|
if (!session_id || typeof session_id !== "string") {
|
|
3010
|
-
log$
|
|
2923
|
+
log$24.warn("创建任务参数缺失: session_id", { requestId });
|
|
3011
2924
|
res.status(400).json({
|
|
3012
2925
|
error: "参数错误",
|
|
3013
2926
|
detail: "session_id 为必填字符串"
|
|
@@ -3015,7 +2928,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
3015
2928
|
return;
|
|
3016
2929
|
}
|
|
3017
2930
|
if (!cwd || typeof cwd !== "string") {
|
|
3018
|
-
log$
|
|
2931
|
+
log$24.warn("创建任务参数缺失: cwd", { requestId });
|
|
3019
2932
|
res.status(400).json({
|
|
3020
2933
|
error: "参数错误",
|
|
3021
2934
|
detail: "cwd 为必填字符串"
|
|
@@ -3023,7 +2936,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
3023
2936
|
return;
|
|
3024
2937
|
}
|
|
3025
2938
|
if (!prompt || typeof prompt !== "string") {
|
|
3026
|
-
log$
|
|
2939
|
+
log$24.warn("创建任务参数缺失: prompt", { requestId });
|
|
3027
2940
|
res.status(400).json({
|
|
3028
2941
|
error: "参数错误",
|
|
3029
2942
|
detail: "prompt 为必填字符串"
|
|
@@ -3031,7 +2944,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
3031
2944
|
return;
|
|
3032
2945
|
}
|
|
3033
2946
|
if (!cron.validate(cronExpr)) {
|
|
3034
|
-
log$
|
|
2947
|
+
log$24.warn("cron 表达式不合法", {
|
|
3035
2948
|
requestId,
|
|
3036
2949
|
cron: cronExpr
|
|
3037
2950
|
});
|
|
@@ -3052,7 +2965,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
3052
2965
|
constraints
|
|
3053
2966
|
};
|
|
3054
2967
|
const task = taskManager.createTask(params);
|
|
3055
|
-
log$
|
|
2968
|
+
log$24.info("定时任务创建成功", {
|
|
3056
2969
|
requestId,
|
|
3057
2970
|
taskId: task.id,
|
|
3058
2971
|
name: task.name,
|
|
@@ -3060,7 +2973,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
3060
2973
|
});
|
|
3061
2974
|
res.status(201).json({ task });
|
|
3062
2975
|
} catch (err) {
|
|
3063
|
-
log$
|
|
2976
|
+
log$24.error("创建定时任务失败", {
|
|
3064
2977
|
requestId,
|
|
3065
2978
|
error: String(err)
|
|
3066
2979
|
});
|
|
@@ -3074,13 +2987,13 @@ function createSchedulerRouter(taskManager) {
|
|
|
3074
2987
|
const requestId = req.headers["x-request-id"];
|
|
3075
2988
|
const { id } = req.params;
|
|
3076
2989
|
const body = req.body;
|
|
3077
|
-
log$
|
|
2990
|
+
log$24.info("请求更新定时任务", {
|
|
3078
2991
|
requestId,
|
|
3079
2992
|
taskId: id,
|
|
3080
2993
|
body: JSON.stringify(body)
|
|
3081
2994
|
});
|
|
3082
2995
|
if (!taskManager.getTask(id)) {
|
|
3083
|
-
log$
|
|
2996
|
+
log$24.warn("更新的任务不存在", {
|
|
3084
2997
|
requestId,
|
|
3085
2998
|
taskId: id
|
|
3086
2999
|
});
|
|
@@ -3092,7 +3005,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
3092
3005
|
}
|
|
3093
3006
|
if (body.cron !== void 0) {
|
|
3094
3007
|
if (typeof body.cron !== "string" || !cron.validate(body.cron)) {
|
|
3095
|
-
log$
|
|
3008
|
+
log$24.warn("更新的 cron 表达式不合法", {
|
|
3096
3009
|
requestId,
|
|
3097
3010
|
taskId: id,
|
|
3098
3011
|
cron: body.cron
|
|
@@ -3113,7 +3026,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
3113
3026
|
if (body.constraints !== void 0) updates.constraints = body.constraints;
|
|
3114
3027
|
if (body.enabled !== void 0) updates.enabled = body.enabled;
|
|
3115
3028
|
const task = taskManager.updateTask(id, updates);
|
|
3116
|
-
log$
|
|
3029
|
+
log$24.info("定时任务更新成功", {
|
|
3117
3030
|
requestId,
|
|
3118
3031
|
taskId: task.id,
|
|
3119
3032
|
name: task.name,
|
|
@@ -3121,7 +3034,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
3121
3034
|
});
|
|
3122
3035
|
res.json({ task });
|
|
3123
3036
|
} catch (err) {
|
|
3124
|
-
log$
|
|
3037
|
+
log$24.error("更新定时任务失败", {
|
|
3125
3038
|
requestId,
|
|
3126
3039
|
taskId: id,
|
|
3127
3040
|
error: String(err)
|
|
@@ -3135,12 +3048,12 @@ function createSchedulerRouter(taskManager) {
|
|
|
3135
3048
|
router.delete("/api/scheduled-tasks/:id", (req, res) => {
|
|
3136
3049
|
const requestId = req.headers["x-request-id"];
|
|
3137
3050
|
const id = String(req.params.id);
|
|
3138
|
-
log$
|
|
3051
|
+
log$24.info("请求删除定时任务", {
|
|
3139
3052
|
requestId,
|
|
3140
3053
|
taskId: id
|
|
3141
3054
|
});
|
|
3142
3055
|
if (!taskManager.getTask(id)) {
|
|
3143
|
-
log$
|
|
3056
|
+
log$24.warn("删除的任务不存在", {
|
|
3144
3057
|
requestId,
|
|
3145
3058
|
taskId: id
|
|
3146
3059
|
});
|
|
@@ -3152,7 +3065,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
3152
3065
|
}
|
|
3153
3066
|
try {
|
|
3154
3067
|
const deleted = taskManager.deleteTask(id);
|
|
3155
|
-
log$
|
|
3068
|
+
log$24.info("定时任务删除结果", {
|
|
3156
3069
|
requestId,
|
|
3157
3070
|
taskId: id,
|
|
3158
3071
|
deleted
|
|
@@ -3162,7 +3075,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
3162
3075
|
deleted: true
|
|
3163
3076
|
});
|
|
3164
3077
|
} catch (err) {
|
|
3165
|
-
log$
|
|
3078
|
+
log$24.error("删除定时任务失败", {
|
|
3166
3079
|
requestId,
|
|
3167
3080
|
taskId: id,
|
|
3168
3081
|
error: String(err)
|
|
@@ -3192,7 +3105,7 @@ function createSchedulerRouter(taskManager) {
|
|
|
3192
3105
|
* - GET /open-apis/application/v6/applications/me?lang=zh_cn(需要 application:application:self_manage 权限,返回完整信息)
|
|
3193
3106
|
* 如果后者无权限则 fallback 到前者
|
|
3194
3107
|
*/
|
|
3195
|
-
const log$
|
|
3108
|
+
const log$23 = larkLogger("core/app-info-sync");
|
|
3196
3109
|
/**
|
|
3197
3110
|
* 从飞书获取应用信息
|
|
3198
3111
|
*
|
|
@@ -3203,7 +3116,7 @@ async function fetchAppInfo(credentials) {
|
|
|
3203
3116
|
const { appId, appSecret } = credentials;
|
|
3204
3117
|
const token = await getTenantAccessToken(appId, appSecret);
|
|
3205
3118
|
if (!token) {
|
|
3206
|
-
log$
|
|
3119
|
+
log$23.error("获取 tenant_access_token 失败,无法同步应用信息");
|
|
3207
3120
|
return null;
|
|
3208
3121
|
}
|
|
3209
3122
|
const fullInfo = await fetchFromApplicationApi(token);
|
|
@@ -3214,13 +3127,13 @@ async function fetchAppInfo(credentials) {
|
|
|
3214
3127
|
async function fetchFromApplicationApi(token) {
|
|
3215
3128
|
try {
|
|
3216
3129
|
const data = await (await fetch("https://open.feishu.cn/open-apis/application/v6/applications/me?lang=zh_cn", { headers: { Authorization: `Bearer ${token}` } })).json();
|
|
3217
|
-
log$
|
|
3130
|
+
log$23.info("application/v6 API 响应", {
|
|
3218
3131
|
code: data.code,
|
|
3219
3132
|
msg: data.msg,
|
|
3220
3133
|
hasApp: !!data.data?.app
|
|
3221
3134
|
});
|
|
3222
3135
|
if (data.code !== 0 || !data.data?.app) {
|
|
3223
|
-
log$
|
|
3136
|
+
log$23.warn("application/v6 API 返回非零或无数据,将 fallback", {
|
|
3224
3137
|
code: data.code,
|
|
3225
3138
|
msg: data.msg
|
|
3226
3139
|
});
|
|
@@ -3235,7 +3148,7 @@ async function fetchFromApplicationApi(token) {
|
|
|
3235
3148
|
helpDocUrl: zhInfo?.help_use
|
|
3236
3149
|
};
|
|
3237
3150
|
} catch (err) {
|
|
3238
|
-
log$
|
|
3151
|
+
log$23.warn("application/v6 API 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
3239
3152
|
return null;
|
|
3240
3153
|
}
|
|
3241
3154
|
}
|
|
@@ -3243,13 +3156,13 @@ async function fetchFromApplicationApi(token) {
|
|
|
3243
3156
|
async function fetchFromBotApi(token) {
|
|
3244
3157
|
try {
|
|
3245
3158
|
const data = await (await fetch("https://open.feishu.cn/open-apis/bot/v3/info", { headers: { Authorization: `Bearer ${token}` } })).json();
|
|
3246
|
-
log$
|
|
3159
|
+
log$23.info("bot/v3/info API 响应", {
|
|
3247
3160
|
code: data.code,
|
|
3248
3161
|
msg: data.msg,
|
|
3249
3162
|
hasBot: !!data.bot
|
|
3250
3163
|
});
|
|
3251
3164
|
if (data.code !== 0 || !data.bot) {
|
|
3252
|
-
log$
|
|
3165
|
+
log$23.error("bot/v3/info API 失败", {
|
|
3253
3166
|
code: data.code,
|
|
3254
3167
|
msg: data.msg
|
|
3255
3168
|
});
|
|
@@ -3260,7 +3173,7 @@ async function fetchFromBotApi(token) {
|
|
|
3260
3173
|
avatarUrl: data.bot.avatar_url
|
|
3261
3174
|
};
|
|
3262
3175
|
} catch (err) {
|
|
3263
|
-
log$
|
|
3176
|
+
log$23.error("bot/v3/info API 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
3264
3177
|
return null;
|
|
3265
3178
|
}
|
|
3266
3179
|
}
|
|
@@ -3276,7 +3189,7 @@ async function getTenantAccessToken(appId, appSecret) {
|
|
|
3276
3189
|
})
|
|
3277
3190
|
})).json();
|
|
3278
3191
|
if (data.code !== 0 || !data.tenant_access_token) {
|
|
3279
|
-
log$
|
|
3192
|
+
log$23.error("获取 tenant_access_token 失败", {
|
|
3280
3193
|
code: data.code,
|
|
3281
3194
|
msg: data.msg
|
|
3282
3195
|
});
|
|
@@ -3284,7 +3197,7 @@ async function getTenantAccessToken(appId, appSecret) {
|
|
|
3284
3197
|
}
|
|
3285
3198
|
return data.tenant_access_token;
|
|
3286
3199
|
} catch (err) {
|
|
3287
|
-
log$
|
|
3200
|
+
log$23.error("获取 tenant_access_token 异常", { error: err instanceof Error ? err.message : String(err) });
|
|
3288
3201
|
return null;
|
|
3289
3202
|
}
|
|
3290
3203
|
}
|
|
@@ -3322,10 +3235,10 @@ function parseDocTokenFromUrl(url) {
|
|
|
3322
3235
|
async function fetchDocContent(docUrl, accessToken) {
|
|
3323
3236
|
const parsed = parseDocTokenFromUrl(docUrl);
|
|
3324
3237
|
if (!parsed) {
|
|
3325
|
-
log$
|
|
3238
|
+
log$23.warn("无法从 URL 中解析文档 token", { url: docUrl });
|
|
3326
3239
|
return null;
|
|
3327
3240
|
}
|
|
3328
|
-
log$
|
|
3241
|
+
log$23.info("开始读取人设文档", {
|
|
3329
3242
|
url: docUrl,
|
|
3330
3243
|
type: parsed.type,
|
|
3331
3244
|
token: parsed.token
|
|
@@ -3333,18 +3246,18 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
3333
3246
|
let docToken = parsed.token;
|
|
3334
3247
|
if (parsed.type === "wiki") {
|
|
3335
3248
|
const realToken = await resolveWikiNodeToDocToken(parsed.token, accessToken);
|
|
3336
|
-
if (!realToken) log$
|
|
3249
|
+
if (!realToken) log$23.warn("wiki 节点解析失败,尝试直接使用 token 读取");
|
|
3337
3250
|
else docToken = realToken;
|
|
3338
3251
|
}
|
|
3339
3252
|
try {
|
|
3340
3253
|
const data = await (await fetch(`https://open.feishu.cn/open-apis/docx/v1/documents/${docToken}/raw_content`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
|
|
3341
|
-
log$
|
|
3254
|
+
log$23.info("文档 raw_content API 响应", {
|
|
3342
3255
|
code: data.code,
|
|
3343
3256
|
msg: data.msg,
|
|
3344
3257
|
contentLength: data.data?.content?.length
|
|
3345
3258
|
});
|
|
3346
3259
|
if (data.code !== 0 || !data.data?.content) {
|
|
3347
|
-
log$
|
|
3260
|
+
log$23.warn("读取文档内容失败", {
|
|
3348
3261
|
code: data.code,
|
|
3349
3262
|
msg: data.msg,
|
|
3350
3263
|
docToken
|
|
@@ -3353,7 +3266,7 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
3353
3266
|
}
|
|
3354
3267
|
return data.data.content.trim();
|
|
3355
3268
|
} catch (err) {
|
|
3356
|
-
log$
|
|
3269
|
+
log$23.error("读取文档内容异常", {
|
|
3357
3270
|
error: err instanceof Error ? err.message : String(err),
|
|
3358
3271
|
docToken
|
|
3359
3272
|
});
|
|
@@ -3364,7 +3277,7 @@ async function fetchDocContent(docUrl, accessToken) {
|
|
|
3364
3277
|
async function resolveWikiNodeToDocToken(wikiToken, accessToken) {
|
|
3365
3278
|
try {
|
|
3366
3279
|
const data = await (await fetch(`https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token=${wikiToken}`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
|
|
3367
|
-
log$
|
|
3280
|
+
log$23.info("wiki get_node API 响应", {
|
|
3368
3281
|
code: data.code,
|
|
3369
3282
|
msg: data.msg,
|
|
3370
3283
|
objType: data.data?.node?.obj_type
|
|
@@ -3372,7 +3285,7 @@ async function resolveWikiNodeToDocToken(wikiToken, accessToken) {
|
|
|
3372
3285
|
if (data.code !== 0 || !data.data?.node?.obj_token) return null;
|
|
3373
3286
|
return data.data.node.obj_token;
|
|
3374
3287
|
} catch (err) {
|
|
3375
|
-
log$
|
|
3288
|
+
log$23.warn("wiki get_node 请求异常", { error: err instanceof Error ? err.message : String(err) });
|
|
3376
3289
|
return null;
|
|
3377
3290
|
}
|
|
3378
3291
|
}
|
|
@@ -3394,7 +3307,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
3394
3307
|
const infoBlock = buildAppInfoBlock(appInfo);
|
|
3395
3308
|
if (!existsSync$1(claudeMdPath)) {
|
|
3396
3309
|
await writeFile$1(claudeMdPath, infoBlock + "\n\n" + getDefaultClaudeMdBody(), "utf-8");
|
|
3397
|
-
log$
|
|
3310
|
+
log$23.info("CLAUDE.md 已创建(含应用信息)", { appName: appInfo.appName });
|
|
3398
3311
|
return;
|
|
3399
3312
|
}
|
|
3400
3313
|
let content = await readFile$1(claudeMdPath, "utf-8");
|
|
@@ -3406,7 +3319,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
3406
3319
|
content = before + infoBlock + after;
|
|
3407
3320
|
} else content = infoBlock + "\n\n" + content;
|
|
3408
3321
|
await writeFile$1(claudeMdPath, content, "utf-8");
|
|
3409
|
-
log$
|
|
3322
|
+
log$23.info("CLAUDE.md 应用信息已同步", {
|
|
3410
3323
|
appName: appInfo.appName,
|
|
3411
3324
|
hasDescription: !!appInfo.description,
|
|
3412
3325
|
hasAvatar: !!appInfo.avatarUrl
|
|
@@ -3421,7 +3334,7 @@ async function syncAppInfoToClaudeMd(appInfo) {
|
|
|
3421
3334
|
async function syncPersonaDocToClaudeMd(personaContent) {
|
|
3422
3335
|
const claudeMdPath = join$1(homedir$1(), ".claude", "CLAUDE.md");
|
|
3423
3336
|
if (!existsSync$1(claudeMdPath)) {
|
|
3424
|
-
log$
|
|
3337
|
+
log$23.warn("CLAUDE.md 不存在,无法同步人设文档(需先同步应用信息)");
|
|
3425
3338
|
return;
|
|
3426
3339
|
}
|
|
3427
3340
|
let content = await readFile$1(claudeMdPath, "utf-8");
|
|
@@ -3442,7 +3355,7 @@ async function syncPersonaDocToClaudeMd(personaContent) {
|
|
|
3442
3355
|
content = before + personaBlock + after;
|
|
3443
3356
|
} else content = content.trimEnd() + "\n\n" + personaBlock + "\n";
|
|
3444
3357
|
await writeFile$1(claudeMdPath, content, "utf-8");
|
|
3445
|
-
log$
|
|
3358
|
+
log$23.info("CLAUDE.md 人设文档已同步", { contentLength: personaContent.length });
|
|
3446
3359
|
}
|
|
3447
3360
|
/** 构建应用信息标记区块 */
|
|
3448
3361
|
function buildAppInfoBlock(appInfo) {
|
|
@@ -3480,24 +3393,24 @@ function getDefaultClaudeMdBody() {
|
|
|
3480
3393
|
* @returns 同步后的应用信息,如果失败返回 null
|
|
3481
3394
|
*/
|
|
3482
3395
|
async function syncAppInfo(credentials) {
|
|
3483
|
-
log$
|
|
3396
|
+
log$23.info("开始同步应用信息", { appId: credentials.appId });
|
|
3484
3397
|
const appInfo = await fetchAppInfo(credentials);
|
|
3485
3398
|
if (!appInfo) {
|
|
3486
|
-
log$
|
|
3399
|
+
log$23.warn("获取应用信息失败,跳过同步");
|
|
3487
3400
|
return null;
|
|
3488
3401
|
}
|
|
3489
3402
|
await syncAppInfoToClaudeMd(appInfo);
|
|
3490
3403
|
if (appInfo.helpDocUrl) {
|
|
3491
|
-
log$
|
|
3404
|
+
log$23.info("检测到帮助文档 URL,尝试同步人设文档", { helpDocUrl: appInfo.helpDocUrl });
|
|
3492
3405
|
const token = await getTenantAccessToken(credentials.appId, credentials.appSecret);
|
|
3493
3406
|
if (token) {
|
|
3494
3407
|
const personaContent = await fetchDocContent(appInfo.helpDocUrl, token);
|
|
3495
3408
|
if (personaContent) await syncPersonaDocToClaudeMd(personaContent);
|
|
3496
|
-
else log$
|
|
3409
|
+
else log$23.warn("人设文档内容为空或读取失败,跳过同步");
|
|
3497
3410
|
}
|
|
3498
3411
|
}
|
|
3499
3412
|
await installSyncSkill();
|
|
3500
|
-
log$
|
|
3413
|
+
log$23.info("应用信息同步完成", {
|
|
3501
3414
|
appName: appInfo.appName,
|
|
3502
3415
|
description: appInfo.description?.substring(0, 50),
|
|
3503
3416
|
hasPersonaDoc: !!appInfo.helpDocUrl
|
|
@@ -3515,7 +3428,7 @@ async function installSyncSkill() {
|
|
|
3515
3428
|
if ((await readFile$1(SYNC_SKILL_PATH, "utf-8")).includes(`skill-version: ${SYNC_SKILL_VERSION}`)) return;
|
|
3516
3429
|
}
|
|
3517
3430
|
await writeFile$1(SYNC_SKILL_PATH, SYNC_SKILL_CONTENT, "utf-8");
|
|
3518
|
-
log$
|
|
3431
|
+
log$23.info("sync-app-info 技能已安装", { path: SYNC_SKILL_PATH });
|
|
3519
3432
|
}
|
|
3520
3433
|
const SYNC_SKILL_CONTENT = `---
|
|
3521
3434
|
skill-version: ${SYNC_SKILL_VERSION}
|
|
@@ -3605,7 +3518,7 @@ function notImplemented(_req, res) {
|
|
|
3605
3518
|
function registerRoutes(app, processManager, scheduledTaskManager, appCredentials) {
|
|
3606
3519
|
if (processManager) {
|
|
3607
3520
|
app.use(createExecuteRouter(processManager));
|
|
3608
|
-
app.use(
|
|
3521
|
+
app.use(createAgentCompletionRouter(processManager));
|
|
3609
3522
|
} else {
|
|
3610
3523
|
logger$1.warn("processManager 未注入,执行路由使用占位 handler");
|
|
3611
3524
|
app.post("/api/execute", notImplemented);
|
|
@@ -3623,6 +3536,7 @@ function registerRoutes(app, processManager, scheduledTaskManager, appCredential
|
|
|
3623
3536
|
app.delete("/api/scheduled-tasks/:id", notImplemented);
|
|
3624
3537
|
}
|
|
3625
3538
|
app.use("/api", createStatusRouter());
|
|
3539
|
+
app.use(createToolInvokeProxyRouter());
|
|
3626
3540
|
app.use("/hooks", createHooksRouter());
|
|
3627
3541
|
if (appCredentials) app.post("/api/sync-app-info", async (_req, res) => {
|
|
3628
3542
|
try {
|
|
@@ -3709,7 +3623,7 @@ function createGatewayServer(config) {
|
|
|
3709
3623
|
*
|
|
3710
3624
|
* 持久化文件: /workspace/config/scheduled-tasks.json
|
|
3711
3625
|
*/
|
|
3712
|
-
const log$
|
|
3626
|
+
const log$22 = larkLogger("gateway/scheduler");
|
|
3713
3627
|
/** 持久化文件路径 */
|
|
3714
3628
|
const PERSIST_PATH = "/workspace/config/scheduled-tasks.json";
|
|
3715
3629
|
/**
|
|
@@ -3727,7 +3641,7 @@ var ScheduledTaskManager = class {
|
|
|
3727
3641
|
processManager;
|
|
3728
3642
|
constructor(processManager) {
|
|
3729
3643
|
this.processManager = processManager;
|
|
3730
|
-
log$
|
|
3644
|
+
log$22.info("ScheduledTaskManager 已创建");
|
|
3731
3645
|
}
|
|
3732
3646
|
/**
|
|
3733
3647
|
* 创建定时任务
|
|
@@ -3747,7 +3661,7 @@ var ScheduledTaskManager = class {
|
|
|
3747
3661
|
enabled: true,
|
|
3748
3662
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3749
3663
|
};
|
|
3750
|
-
log$
|
|
3664
|
+
log$22.info("创建定时任务", {
|
|
3751
3665
|
taskId: task.id,
|
|
3752
3666
|
name: task.name,
|
|
3753
3667
|
cron: task.cron,
|
|
@@ -3783,7 +3697,7 @@ var ScheduledTaskManager = class {
|
|
|
3783
3697
|
if (updates.example_output !== void 0) task.example_output = updates.example_output;
|
|
3784
3698
|
if (updates.constraints !== void 0) task.constraints = updates.constraints;
|
|
3785
3699
|
if (updates.enabled !== void 0) task.enabled = updates.enabled;
|
|
3786
|
-
log$
|
|
3700
|
+
log$22.info("更新定时任务", {
|
|
3787
3701
|
taskId: id,
|
|
3788
3702
|
name: task.name,
|
|
3789
3703
|
cronChanged: oldCron !== task.cron,
|
|
@@ -3805,10 +3719,10 @@ var ScheduledTaskManager = class {
|
|
|
3805
3719
|
deleteTask(id) {
|
|
3806
3720
|
const task = this.tasks.get(id);
|
|
3807
3721
|
if (!task) {
|
|
3808
|
-
log$
|
|
3722
|
+
log$22.warn("尝试删除不存在的任务", { taskId: id });
|
|
3809
3723
|
return false;
|
|
3810
3724
|
}
|
|
3811
|
-
log$
|
|
3725
|
+
log$22.info("删除定时任务", {
|
|
3812
3726
|
taskId: id,
|
|
3813
3727
|
name: task.name
|
|
3814
3728
|
});
|
|
@@ -3823,28 +3737,28 @@ var ScheduledTaskManager = class {
|
|
|
3823
3737
|
* 启动时调用,读取 JSON 文件并为每个 enabled 的任务注册 cron。
|
|
3824
3738
|
*/
|
|
3825
3739
|
async loadFromDisk() {
|
|
3826
|
-
log$
|
|
3740
|
+
log$22.info("从磁盘加载定时任务", { path: PERSIST_PATH });
|
|
3827
3741
|
try {
|
|
3828
3742
|
const content = await readFile(PERSIST_PATH, "utf-8");
|
|
3829
3743
|
const data = JSON.parse(content);
|
|
3830
3744
|
if (!Array.isArray(data)) {
|
|
3831
|
-
log$
|
|
3745
|
+
log$22.warn("持久化文件格式异常,跳过加载", { path: PERSIST_PATH });
|
|
3832
3746
|
return;
|
|
3833
3747
|
}
|
|
3834
3748
|
for (const task of data) {
|
|
3835
3749
|
this.tasks.set(task.id, task);
|
|
3836
3750
|
if (task.enabled) this.registerCronJob(task);
|
|
3837
3751
|
}
|
|
3838
|
-
log$
|
|
3752
|
+
log$22.info("定时任务加载完成", {
|
|
3839
3753
|
total: data.length,
|
|
3840
3754
|
enabled: data.filter((t) => t.enabled).length
|
|
3841
3755
|
});
|
|
3842
3756
|
} catch (err) {
|
|
3843
3757
|
if (err.code === "ENOENT") {
|
|
3844
|
-
log$
|
|
3758
|
+
log$22.info("持久化文件不存在,跳过加载(首次启动)", { path: PERSIST_PATH });
|
|
3845
3759
|
return;
|
|
3846
3760
|
}
|
|
3847
|
-
log$
|
|
3761
|
+
log$22.error("加载定时任务失败", {
|
|
3848
3762
|
path: PERSIST_PATH,
|
|
3849
3763
|
error: String(err)
|
|
3850
3764
|
});
|
|
@@ -3860,12 +3774,12 @@ var ScheduledTaskManager = class {
|
|
|
3860
3774
|
try {
|
|
3861
3775
|
await mkdir(dirname(PERSIST_PATH), { recursive: true });
|
|
3862
3776
|
await writeFile(PERSIST_PATH, JSON.stringify(tasks, null, 2), "utf-8");
|
|
3863
|
-
log$
|
|
3777
|
+
log$22.info("定时任务已持久化到磁盘", {
|
|
3864
3778
|
path: PERSIST_PATH,
|
|
3865
3779
|
count: tasks.length
|
|
3866
3780
|
});
|
|
3867
3781
|
} catch (err) {
|
|
3868
|
-
log$
|
|
3782
|
+
log$22.error("持久化定时任务失败", {
|
|
3869
3783
|
path: PERSIST_PATH,
|
|
3870
3784
|
error: String(err)
|
|
3871
3785
|
});
|
|
@@ -3878,13 +3792,13 @@ var ScheduledTaskManager = class {
|
|
|
3878
3792
|
*/
|
|
3879
3793
|
registerCronJob(task) {
|
|
3880
3794
|
this.unregisterCronJob(task.id);
|
|
3881
|
-
log$
|
|
3795
|
+
log$22.info("注册 cron 调度", {
|
|
3882
3796
|
taskId: task.id,
|
|
3883
3797
|
name: task.name,
|
|
3884
3798
|
cron: task.cron
|
|
3885
3799
|
});
|
|
3886
3800
|
const job = cron.schedule(task.cron, () => {
|
|
3887
|
-
log$
|
|
3801
|
+
log$22.info("cron 触发任务执行", {
|
|
3888
3802
|
taskId: task.id,
|
|
3889
3803
|
name: task.name
|
|
3890
3804
|
});
|
|
@@ -3900,7 +3814,7 @@ var ScheduledTaskManager = class {
|
|
|
3900
3814
|
if (job) {
|
|
3901
3815
|
job.stop();
|
|
3902
3816
|
this.cronJobs.delete(taskId);
|
|
3903
|
-
log$
|
|
3817
|
+
log$22.info("已注销 cron 调度", { taskId });
|
|
3904
3818
|
}
|
|
3905
3819
|
}
|
|
3906
3820
|
/**
|
|
@@ -3910,10 +3824,10 @@ var ScheduledTaskManager = class {
|
|
|
3910
3824
|
*/
|
|
3911
3825
|
stopAll() {
|
|
3912
3826
|
const count = this.cronJobs.size;
|
|
3913
|
-
log$
|
|
3827
|
+
log$22.info("停止所有 cron 调度", { count });
|
|
3914
3828
|
for (const [taskId, job] of this.cronJobs) {
|
|
3915
3829
|
job.stop();
|
|
3916
|
-
log$
|
|
3830
|
+
log$22.debug("已停止 cron 调度", { taskId });
|
|
3917
3831
|
}
|
|
3918
3832
|
this.cronJobs.clear();
|
|
3919
3833
|
}
|
|
@@ -3927,17 +3841,17 @@ var ScheduledTaskManager = class {
|
|
|
3927
3841
|
async executeTask(taskId) {
|
|
3928
3842
|
const task = this.tasks.get(taskId);
|
|
3929
3843
|
if (!task) {
|
|
3930
|
-
log$
|
|
3844
|
+
log$22.warn("任务执行时未找到任务", { taskId });
|
|
3931
3845
|
return;
|
|
3932
3846
|
}
|
|
3933
3847
|
if (!task.enabled) {
|
|
3934
|
-
log$
|
|
3848
|
+
log$22.info("任务已禁用,跳过执行", {
|
|
3935
3849
|
taskId,
|
|
3936
3850
|
name: task.name
|
|
3937
3851
|
});
|
|
3938
3852
|
return;
|
|
3939
3853
|
}
|
|
3940
|
-
log$
|
|
3854
|
+
log$22.info("开始执行定时任务", {
|
|
3941
3855
|
taskId: task.id,
|
|
3942
3856
|
name: task.name,
|
|
3943
3857
|
sessionId: task.session_id,
|
|
@@ -3958,7 +3872,7 @@ var ScheduledTaskManager = class {
|
|
|
3958
3872
|
onResult: (result) => {
|
|
3959
3873
|
const summary = result.result ?? resultText;
|
|
3960
3874
|
task.last_result = summary.slice(0, 2e3);
|
|
3961
|
-
log$
|
|
3875
|
+
log$22.info("定时任务执行完成", {
|
|
3962
3876
|
taskId: task.id,
|
|
3963
3877
|
name: task.name,
|
|
3964
3878
|
subtype: result.subtype,
|
|
@@ -3971,7 +3885,7 @@ var ScheduledTaskManager = class {
|
|
|
3971
3885
|
},
|
|
3972
3886
|
onError: (error) => {
|
|
3973
3887
|
task.last_result = `执行失败: ${error.message}`;
|
|
3974
|
-
log$
|
|
3888
|
+
log$22.error("定时任务执行失败", {
|
|
3975
3889
|
taskId: task.id,
|
|
3976
3890
|
name: task.name,
|
|
3977
3891
|
error: error.message
|
|
@@ -3983,7 +3897,7 @@ var ScheduledTaskManager = class {
|
|
|
3983
3897
|
await this.processManager.executePrompt(config, callbacks);
|
|
3984
3898
|
} catch (err) {
|
|
3985
3899
|
task.last_result = `执行异常: ${String(err)}`;
|
|
3986
|
-
log$
|
|
3900
|
+
log$22.error("定时任务执行异常", {
|
|
3987
3901
|
taskId: task.id,
|
|
3988
3902
|
name: task.name,
|
|
3989
3903
|
error: String(err)
|
|
@@ -4276,7 +4190,7 @@ function getUserAgent() {
|
|
|
4276
4190
|
* - `LarkClient.fromCredentials(credentials)` — ephemeral instance (not cached)
|
|
4277
4191
|
* - `LarkClient.fromProvider(provider, opts)` — from ICredentialProvider (not cached)
|
|
4278
4192
|
*/
|
|
4279
|
-
const log$
|
|
4193
|
+
const log$20 = larkLogger("core/lark-client");
|
|
4280
4194
|
const GLOBAL_LARK_USER_AGENT_KEY = "LARK_USER_AGENT";
|
|
4281
4195
|
function installGlobalUserAgent() {
|
|
4282
4196
|
globalThis[GLOBAL_LARK_USER_AGENT_KEY] = getUserAgent();
|
|
@@ -4368,7 +4282,7 @@ var LarkClient = class LarkClient {
|
|
|
4368
4282
|
const existing = cache.get(account.accountId);
|
|
4369
4283
|
if (existing && existing.account.appId === account.appId && credentialsEqual(existing.account.appSecret, account.appSecret)) return existing;
|
|
4370
4284
|
if (existing) {
|
|
4371
|
-
log$
|
|
4285
|
+
log$20.info(`credentials changed, disposing stale instance`, { accountId: account.accountId });
|
|
4372
4286
|
existing.dispose();
|
|
4373
4287
|
}
|
|
4374
4288
|
const instance = new LarkClient(account);
|
|
@@ -4411,7 +4325,7 @@ var LarkClient = class LarkClient {
|
|
|
4411
4325
|
static fromProvider(provider, opts) {
|
|
4412
4326
|
const appId = provider.getAppId();
|
|
4413
4327
|
const appSecret = provider.getAppSecret();
|
|
4414
|
-
log$
|
|
4328
|
+
log$20.info("通过 ICredentialProvider 创建 LarkClient", {
|
|
4415
4329
|
appId,
|
|
4416
4330
|
accountId: opts?.accountId ?? "default",
|
|
4417
4331
|
brand: opts?.brand ?? "feishu"
|
|
@@ -4447,7 +4361,7 @@ var LarkClient = class LarkClient {
|
|
|
4447
4361
|
get sdk() {
|
|
4448
4362
|
if (!this._sdk) {
|
|
4449
4363
|
const { appId, appSecret } = this.requireCredentials();
|
|
4450
|
-
log$
|
|
4364
|
+
log$20.info("创建 Lark SDK 客户端实例", {
|
|
4451
4365
|
accountId: this.accountId,
|
|
4452
4366
|
appId,
|
|
4453
4367
|
brand: this.account.brand
|
|
@@ -4462,7 +4376,7 @@ var LarkClient = class LarkClient {
|
|
|
4462
4376
|
if (sdkAny.httpInstance?.interceptors) {
|
|
4463
4377
|
const accountId = this.accountId;
|
|
4464
4378
|
sdkAny.httpInstance.interceptors.request.use((req) => {
|
|
4465
|
-
log$
|
|
4379
|
+
log$20.debug("飞书 API 请求", {
|
|
4466
4380
|
accountId,
|
|
4467
4381
|
method: req.method,
|
|
4468
4382
|
url: req.url,
|
|
@@ -4472,7 +4386,7 @@ var LarkClient = class LarkClient {
|
|
|
4472
4386
|
return req;
|
|
4473
4387
|
}, void 0, { synchronous: true });
|
|
4474
4388
|
sdkAny.httpInstance.interceptors.response.use((res) => {
|
|
4475
|
-
log$
|
|
4389
|
+
log$20.debug("飞书 API 响应", {
|
|
4476
4390
|
accountId,
|
|
4477
4391
|
code: res?.code,
|
|
4478
4392
|
msg: res?.msg,
|
|
@@ -4480,7 +4394,7 @@ var LarkClient = class LarkClient {
|
|
|
4480
4394
|
});
|
|
4481
4395
|
return res;
|
|
4482
4396
|
}, (err) => {
|
|
4483
|
-
log$
|
|
4397
|
+
log$20.error("飞书 API 请求失败", {
|
|
4484
4398
|
accountId,
|
|
4485
4399
|
error: err instanceof Error ? err.message : String(err),
|
|
4486
4400
|
url: err?.config?.url
|
|
@@ -4567,7 +4481,7 @@ var LarkClient = class LarkClient {
|
|
|
4567
4481
|
dispatcher.register(handlers);
|
|
4568
4482
|
const { appId, appSecret } = this.requireCredentials();
|
|
4569
4483
|
if (this._wsClient) {
|
|
4570
|
-
log$
|
|
4484
|
+
log$20.warn(`closing previous WSClient before reconnect`, { accountId: this.accountId });
|
|
4571
4485
|
try {
|
|
4572
4486
|
this._wsClient.close({ force: true });
|
|
4573
4487
|
} catch {}
|
|
@@ -4584,7 +4498,7 @@ var LarkClient = class LarkClient {
|
|
|
4584
4498
|
wsClientAny.handleEventData = (data) => {
|
|
4585
4499
|
const msgType = data.headers?.find?.((h) => h.key === "type")?.value;
|
|
4586
4500
|
const eventType = data.headers?.find?.((h) => h.key === "event_type")?.value;
|
|
4587
|
-
log$
|
|
4501
|
+
log$20.info("WS 收到原始事件", {
|
|
4588
4502
|
accountId: this.accountId,
|
|
4589
4503
|
msgType,
|
|
4590
4504
|
eventType,
|
|
@@ -4610,14 +4524,14 @@ var LarkClient = class LarkClient {
|
|
|
4610
4524
|
/** Disconnect WebSocket but keep instance in cache. */
|
|
4611
4525
|
disconnect() {
|
|
4612
4526
|
if (this._wsClient) {
|
|
4613
|
-
log$
|
|
4527
|
+
log$20.info(`disconnecting WebSocket`, { accountId: this.accountId });
|
|
4614
4528
|
try {
|
|
4615
4529
|
this._wsClient.close({ force: true });
|
|
4616
4530
|
} catch {}
|
|
4617
4531
|
}
|
|
4618
4532
|
this._wsClient = null;
|
|
4619
4533
|
if (this.messageDedup) {
|
|
4620
|
-
log$
|
|
4534
|
+
log$20.info(`disposing message dedup`, {
|
|
4621
4535
|
accountId: this.accountId,
|
|
4622
4536
|
size: this.messageDedup.size
|
|
4623
4537
|
});
|
|
@@ -4923,7 +4837,7 @@ function sortTraceValue(value) {
|
|
|
4923
4837
|
}
|
|
4924
4838
|
//#endregion
|
|
4925
4839
|
//#region src/card/cc-stream-bridge.ts
|
|
4926
|
-
const log$
|
|
4840
|
+
const log$19 = larkLogger("card/cc-stream-bridge");
|
|
4927
4841
|
const CC_INTERNAL_PLACEHOLDER = "No response requested.";
|
|
4928
4842
|
/**
|
|
4929
4843
|
* CCStreamBridge — 将 CC 流事件桥接到 StreamingCardController
|
|
@@ -4948,7 +4862,7 @@ var CCStreamBridge = class {
|
|
|
4948
4862
|
sessionKey: options?.sessionKey
|
|
4949
4863
|
};
|
|
4950
4864
|
if (this.options.sessionKey) startToolUseTraceRun(this.options.sessionKey);
|
|
4951
|
-
log$
|
|
4865
|
+
log$19.info("CCStreamBridge 初始化", {
|
|
4952
4866
|
autoCompleteOnTurnEnd: this.options.autoCompleteOnTurnEnd,
|
|
4953
4867
|
sessionKey: this.options.sessionKey ?? "(none)"
|
|
4954
4868
|
});
|
|
@@ -4956,7 +4870,7 @@ var CCStreamBridge = class {
|
|
|
4956
4870
|
/** 文本增量 → 累积后调用 controller.onPartialReply */
|
|
4957
4871
|
onTextDelta(text) {
|
|
4958
4872
|
this.accumulatedText += text;
|
|
4959
|
-
log$
|
|
4873
|
+
log$19.debug("textDelta 事件", {
|
|
4960
4874
|
deltaLen: text.length,
|
|
4961
4875
|
totalLen: this.accumulatedText.length
|
|
4962
4876
|
});
|
|
@@ -4965,7 +4879,7 @@ var CCStreamBridge = class {
|
|
|
4965
4879
|
/** 思考增量 → 累积后调用 controller.onReasoningStream */
|
|
4966
4880
|
onThinkingDelta(text) {
|
|
4967
4881
|
this.accumulatedThinkingText += text;
|
|
4968
|
-
log$
|
|
4882
|
+
log$19.debug("thinkingDelta 事件", {
|
|
4969
4883
|
deltaLen: text.length,
|
|
4970
4884
|
totalLen: this.accumulatedThinkingText.length
|
|
4971
4885
|
});
|
|
@@ -4977,7 +4891,7 @@ var CCStreamBridge = class {
|
|
|
4977
4891
|
const displayName = getToolDisplayName(toolName);
|
|
4978
4892
|
const toolParams = typeof _toolInput === "object" && _toolInput !== null ? _toolInput : void 0;
|
|
4979
4893
|
const hasParams = toolParams && Object.keys(toolParams).length > 0;
|
|
4980
|
-
log$
|
|
4894
|
+
log$19.info("toolUseStart 事件", {
|
|
4981
4895
|
toolName,
|
|
4982
4896
|
displayName,
|
|
4983
4897
|
activeToolsCount: this.activeTools.size,
|
|
@@ -4990,7 +4904,7 @@ var CCStreamBridge = class {
|
|
|
4990
4904
|
toolName,
|
|
4991
4905
|
toolParams
|
|
4992
4906
|
})) {
|
|
4993
|
-
log$
|
|
4907
|
+
log$19.info("toolUseStart: 更新已有步骤的 params", { toolName });
|
|
4994
4908
|
this.controller.onToolStart({
|
|
4995
4909
|
name: toolName,
|
|
4996
4910
|
phase: "start"
|
|
@@ -5014,7 +4928,7 @@ var CCStreamBridge = class {
|
|
|
5014
4928
|
const toolName = this.activeTools.get(toolUseId) ?? this.lastToolName ?? "unknown";
|
|
5015
4929
|
const displayName = getToolDisplayName(toolName);
|
|
5016
4930
|
this.activeTools.delete(toolUseId);
|
|
5017
|
-
log$
|
|
4931
|
+
log$19.info("toolResult 事件", {
|
|
5018
4932
|
toolUseId,
|
|
5019
4933
|
toolName,
|
|
5020
4934
|
displayName,
|
|
@@ -5029,7 +4943,7 @@ var CCStreamBridge = class {
|
|
|
5029
4943
|
}
|
|
5030
4944
|
/** 工具执行进度 → 记录日志 */
|
|
5031
4945
|
onToolProgress(toolName, elapsedSeconds) {
|
|
5032
|
-
log$
|
|
4946
|
+
log$19.debug("toolProgress 事件", {
|
|
5033
4947
|
toolName,
|
|
5034
4948
|
displayName: getToolDisplayName(toolName),
|
|
5035
4949
|
elapsedSeconds
|
|
@@ -5037,7 +4951,7 @@ var CCStreamBridge = class {
|
|
|
5037
4951
|
}
|
|
5038
4952
|
/** 轮次结束 → 仅在 end_turn 时标记完成 + 触发 onIdle */
|
|
5039
4953
|
onTurnEnd(stopReason) {
|
|
5040
|
-
log$
|
|
4954
|
+
log$19.info("turnEnd 事件", {
|
|
5041
4955
|
stopReason,
|
|
5042
4956
|
accumulatedTextLen: this.accumulatedText.length,
|
|
5043
4957
|
accumulatedThinkingTextLen: this.accumulatedThinkingText.length
|
|
@@ -5045,7 +4959,7 @@ var CCStreamBridge = class {
|
|
|
5045
4959
|
if (this.options.autoCompleteOnTurnEnd && stopReason === "end_turn") {
|
|
5046
4960
|
const trimmedText = this.accumulatedText.trim();
|
|
5047
4961
|
if (trimmedText === CC_INTERNAL_PLACEHOLDER || trimmedText === "") {
|
|
5048
|
-
log$
|
|
4962
|
+
log$19.info("检测到 CC 内部占位消息,静默丢弃", {
|
|
5049
4963
|
text: trimmedText.slice(0, 50),
|
|
5050
4964
|
sessionKey: this.options.sessionKey
|
|
5051
4965
|
});
|
|
@@ -5058,7 +4972,7 @@ var CCStreamBridge = class {
|
|
|
5058
4972
|
}
|
|
5059
4973
|
/** 最终结果 → 兜底最终化 + 错误处理 */
|
|
5060
4974
|
onResult(data) {
|
|
5061
|
-
log$
|
|
4975
|
+
log$19.info("result 事件", {
|
|
5062
4976
|
subtype: data.subtype,
|
|
5063
4977
|
isError: data.isError,
|
|
5064
4978
|
durationMs: data.durationMs,
|
|
@@ -5070,7 +4984,7 @@ var CCStreamBridge = class {
|
|
|
5070
4984
|
const pm = ClaudeCodeAdapter.getInstance()?.getProcessManager();
|
|
5071
4985
|
const sessionId = this.options.sessionKey;
|
|
5072
4986
|
if (sessionId && pm ? pm.consumeAborted(sessionId) : false) {
|
|
5073
|
-
log$
|
|
4987
|
+
log$19.info("用户主动中断,按正常完成处理", {
|
|
5074
4988
|
subtype: data.subtype,
|
|
5075
4989
|
sessionId
|
|
5076
4990
|
});
|
|
@@ -5079,7 +4993,7 @@ var CCStreamBridge = class {
|
|
|
5079
4993
|
this.controller.onIdle();
|
|
5080
4994
|
} else {
|
|
5081
4995
|
const errorMessage = data.result ?? `CC 执行失败: ${data.subtype}`;
|
|
5082
|
-
log$
|
|
4996
|
+
log$19.error("CC 执行返回错误", {
|
|
5083
4997
|
subtype: data.subtype,
|
|
5084
4998
|
errorMessage: errorMessage.slice(0, 200)
|
|
5085
4999
|
});
|
|
@@ -5096,7 +5010,7 @@ var CCStreamBridge = class {
|
|
|
5096
5010
|
* 内部复用上面的直接调用方法。
|
|
5097
5011
|
*/
|
|
5098
5012
|
bindParser(parser) {
|
|
5099
|
-
log$
|
|
5013
|
+
log$19.info("绑定 CCStreamParser 事件到卡片控制器");
|
|
5100
5014
|
parser.on("textDelta", (text) => this.onTextDelta(text));
|
|
5101
5015
|
parser.on("thinkingDelta", (text) => this.onThinkingDelta(text));
|
|
5102
5016
|
parser.on("toolUseStart", (toolName, toolInput) => this.onToolUseStart(toolName, toolInput));
|
|
@@ -5104,7 +5018,7 @@ var CCStreamBridge = class {
|
|
|
5104
5018
|
parser.on("toolProgress", (toolName, elapsedSeconds) => this.onToolProgress(toolName, elapsedSeconds));
|
|
5105
5019
|
parser.on("turnEnd", (stopReason) => this.onTurnEnd(stopReason));
|
|
5106
5020
|
parser.on("result", (data) => this.onResult(data));
|
|
5107
|
-
log$
|
|
5021
|
+
log$19.info("CCStreamParser 事件绑定完成");
|
|
5108
5022
|
}
|
|
5109
5023
|
};
|
|
5110
5024
|
//#endregion
|
|
@@ -7120,7 +7034,7 @@ function resolveLarkSdk(cfg, accountId) {
|
|
|
7120
7034
|
if (cached) return cached.sdk;
|
|
7121
7035
|
return LarkClient.fromCfg(cfg, accountId).sdk;
|
|
7122
7036
|
}
|
|
7123
|
-
const log$
|
|
7037
|
+
const log$18 = larkLogger("card/cardkit");
|
|
7124
7038
|
/**
|
|
7125
7039
|
* 记录 CardKit API 响应日志,检测错误码并抛出异常。
|
|
7126
7040
|
*
|
|
@@ -7130,13 +7044,13 @@ const log$17 = larkLogger("card/cardkit");
|
|
|
7130
7044
|
function logCardKitResponse(params) {
|
|
7131
7045
|
const { resp, api, context } = params;
|
|
7132
7046
|
const { code, msg } = resp;
|
|
7133
|
-
log$
|
|
7047
|
+
log$18.info(`cardkit ${api} response`, {
|
|
7134
7048
|
code,
|
|
7135
7049
|
msg,
|
|
7136
7050
|
context
|
|
7137
7051
|
});
|
|
7138
7052
|
if (code && code !== 0) {
|
|
7139
|
-
log$
|
|
7053
|
+
log$18.warn(`cardkit ${api} FAILED`, {
|
|
7140
7054
|
code,
|
|
7141
7055
|
msg,
|
|
7142
7056
|
context,
|
|
@@ -7547,7 +7461,7 @@ function validateLocalMediaRoots(filePath, localRoots) {
|
|
|
7547
7461
|
* Feishu messages, uploading media to the Feishu IM storage, and
|
|
7548
7462
|
* sending image / file messages to chats.
|
|
7549
7463
|
*/
|
|
7550
|
-
const log$
|
|
7464
|
+
const log$17 = larkLogger("outbound/media");
|
|
7551
7465
|
/**
|
|
7552
7466
|
* Upload an image to Feishu IM storage.
|
|
7553
7467
|
*
|
|
@@ -7615,7 +7529,7 @@ async function validateRemoteUrl(raw) {
|
|
|
7615
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}"`);
|
|
7616
7530
|
} catch (err) {
|
|
7617
7531
|
if (err instanceof Error && err.message.includes("SSRF protection")) throw err;
|
|
7618
|
-
log$
|
|
7532
|
+
log$17.warn(`[feishu-media] DNS resolution failed for "${hostname}": ${err}`);
|
|
7619
7533
|
}
|
|
7620
7534
|
}
|
|
7621
7535
|
/**
|
|
@@ -7633,21 +7547,21 @@ async function fetchMediaBuffer(urlOrPath, localRoots) {
|
|
|
7633
7547
|
if (localRoots !== void 0) validateLocalMediaRoots(filePath, localRoots);
|
|
7634
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.`);
|
|
7635
7549
|
const buf = fs.readFileSync(filePath);
|
|
7636
|
-
log$
|
|
7550
|
+
log$17.debug(`local file read: "${filePath}", ${buf.length} bytes`);
|
|
7637
7551
|
return buf;
|
|
7638
7552
|
}
|
|
7639
7553
|
await validateRemoteUrl(raw);
|
|
7640
7554
|
const FETCH_TIMEOUT_MS = 3e4;
|
|
7641
|
-
log$
|
|
7555
|
+
log$17.info(`fetching remote media: ${raw}`);
|
|
7642
7556
|
const response = await fetch(raw, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
|
|
7643
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.`);
|
|
7644
7558
|
const arrayBuffer = await response.arrayBuffer();
|
|
7645
|
-
log$
|
|
7559
|
+
log$17.debug(`remote media fetched: ${raw}, ${arrayBuffer.byteLength} bytes`);
|
|
7646
7560
|
return Buffer.from(arrayBuffer);
|
|
7647
7561
|
}
|
|
7648
7562
|
//#endregion
|
|
7649
7563
|
//#region src/card/image-resolver.ts
|
|
7650
|
-
const log$
|
|
7564
|
+
const log$16 = larkLogger("card/image-resolver");
|
|
7651
7565
|
/** Matches complete markdown image syntax: `` */
|
|
7652
7566
|
const IMAGE_RE = /!\[([^\]]*)\]\(([^)\s]+)\)/g;
|
|
7653
7567
|
var ImageResolver = class {
|
|
@@ -7693,14 +7607,14 @@ var ImageResolver = class {
|
|
|
7693
7607
|
async resolveImagesAwait(text, timeoutMs) {
|
|
7694
7608
|
this.resolveImages(text);
|
|
7695
7609
|
if (this.pending.size > 0) {
|
|
7696
|
-
log$
|
|
7610
|
+
log$16.info("resolveImagesAwait: waiting for uploads", {
|
|
7697
7611
|
count: this.pending.size,
|
|
7698
7612
|
timeoutMs
|
|
7699
7613
|
});
|
|
7700
7614
|
const allUploads = Promise.all(this.pending.values());
|
|
7701
7615
|
const timeout = new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
7702
7616
|
await Promise.race([allUploads, timeout]);
|
|
7703
|
-
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 });
|
|
7704
7618
|
}
|
|
7705
7619
|
return this.resolveImages(text);
|
|
7706
7620
|
}
|
|
@@ -7710,7 +7624,7 @@ var ImageResolver = class {
|
|
|
7710
7624
|
}
|
|
7711
7625
|
async doUpload(url) {
|
|
7712
7626
|
try {
|
|
7713
|
-
log$
|
|
7627
|
+
log$16.info("uploading image", { url });
|
|
7714
7628
|
const buffer = await fetchRemoteImageBuffer(url);
|
|
7715
7629
|
const { imageKey } = await uploadImageLark({
|
|
7716
7630
|
cfg: this.cfg,
|
|
@@ -7718,7 +7632,7 @@ var ImageResolver = class {
|
|
|
7718
7632
|
imageType: "message",
|
|
7719
7633
|
accountId: this.accountId
|
|
7720
7634
|
});
|
|
7721
|
-
log$
|
|
7635
|
+
log$16.info("image uploaded", {
|
|
7722
7636
|
url,
|
|
7723
7637
|
imageKey
|
|
7724
7638
|
});
|
|
@@ -7727,7 +7641,7 @@ var ImageResolver = class {
|
|
|
7727
7641
|
this.onImageResolved();
|
|
7728
7642
|
return imageKey;
|
|
7729
7643
|
} catch (err) {
|
|
7730
|
-
log$
|
|
7644
|
+
log$16.warn("image upload failed", {
|
|
7731
7645
|
url,
|
|
7732
7646
|
error: String(err)
|
|
7733
7647
|
});
|
|
@@ -7748,7 +7662,7 @@ var ImageResolver = class {
|
|
|
7748
7662
|
* Encapsulates the terminateDueToUnavailable / shouldSkipForUnavailable
|
|
7749
7663
|
* logic previously scattered as closures in reply-dispatcher.ts.
|
|
7750
7664
|
*/
|
|
7751
|
-
const log$
|
|
7665
|
+
const log$15 = larkLogger("card/unavailable-guard");
|
|
7752
7666
|
var UnavailableGuard = class {
|
|
7753
7667
|
terminated = false;
|
|
7754
7668
|
replyToMessageId;
|
|
@@ -7801,7 +7715,7 @@ var UnavailableGuard = class {
|
|
|
7801
7715
|
this.terminated = true;
|
|
7802
7716
|
this.onTerminate();
|
|
7803
7717
|
const affectedMessageId = fromError?.messageId ?? this.replyToMessageId ?? cardMessageId ?? "unknown";
|
|
7804
|
-
log$
|
|
7718
|
+
log$15.warn("reply pipeline terminated by unavailable message", {
|
|
7805
7719
|
source,
|
|
7806
7720
|
apiCode,
|
|
7807
7721
|
messageId: affectedMessageId
|
|
@@ -7838,7 +7752,7 @@ function getActiveCard(sessionId) {
|
|
|
7838
7752
|
* Delegates throttling to FlushController and message-unavailable
|
|
7839
7753
|
* detection to UnavailableGuard.
|
|
7840
7754
|
*/
|
|
7841
|
-
const log$
|
|
7755
|
+
const log$14 = larkLogger("card/streaming");
|
|
7842
7756
|
var StreamingCardController = class StreamingCardController {
|
|
7843
7757
|
phase = "idle";
|
|
7844
7758
|
cardKit = {
|
|
@@ -7926,7 +7840,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7926
7840
|
}
|
|
7927
7841
|
}
|
|
7928
7842
|
if (!entry) {
|
|
7929
|
-
log$
|
|
7843
|
+
log$14.debug("footer metrics lookup: session entry missing", {
|
|
7930
7844
|
sessionKey: this.deps.sessionKey,
|
|
7931
7845
|
candidateKeys,
|
|
7932
7846
|
storePath,
|
|
@@ -7944,7 +7858,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7944
7858
|
contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
|
|
7945
7859
|
model: typeof entry.model === "string" ? entry.model : void 0
|
|
7946
7860
|
};
|
|
7947
|
-
log$
|
|
7861
|
+
log$14.debug("footer metrics lookup: session entry found", {
|
|
7948
7862
|
sessionKey: this.deps.sessionKey,
|
|
7949
7863
|
matchedKey,
|
|
7950
7864
|
storePath,
|
|
@@ -7969,7 +7883,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7969
7883
|
}
|
|
7970
7884
|
}
|
|
7971
7885
|
if (!entry) {
|
|
7972
|
-
log$
|
|
7886
|
+
log$14.debug("footer metrics lookup: session entry missing", {
|
|
7973
7887
|
sessionKey: this.deps.sessionKey,
|
|
7974
7888
|
candidateKeys,
|
|
7975
7889
|
storePath,
|
|
@@ -7987,7 +7901,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7987
7901
|
contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : void 0,
|
|
7988
7902
|
model: typeof entry.model === "string" ? entry.model : void 0
|
|
7989
7903
|
};
|
|
7990
|
-
log$
|
|
7904
|
+
log$14.debug("footer metrics lookup: session entry found", {
|
|
7991
7905
|
sessionKey: this.deps.sessionKey,
|
|
7992
7906
|
matchedKey,
|
|
7993
7907
|
storePath,
|
|
@@ -7995,7 +7909,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
7995
7909
|
});
|
|
7996
7910
|
return metrics;
|
|
7997
7911
|
} catch (err) {
|
|
7998
|
-
log$
|
|
7912
|
+
log$14.warn("footer metrics lookup failed", {
|
|
7999
7913
|
error: String(err),
|
|
8000
7914
|
sessionKey: this.deps.sessionKey
|
|
8001
7915
|
});
|
|
@@ -8102,7 +8016,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8102
8016
|
const from = this.phase;
|
|
8103
8017
|
if (from === to) return false;
|
|
8104
8018
|
if (!PHASE_TRANSITIONS[from].has(to)) {
|
|
8105
|
-
log$
|
|
8019
|
+
log$14.warn("phase transition rejected", {
|
|
8106
8020
|
from,
|
|
8107
8021
|
to,
|
|
8108
8022
|
source
|
|
@@ -8110,7 +8024,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8110
8024
|
return false;
|
|
8111
8025
|
}
|
|
8112
8026
|
this.phase = to;
|
|
8113
|
-
log$
|
|
8027
|
+
log$14.info("phase transition", {
|
|
8114
8028
|
from,
|
|
8115
8029
|
to,
|
|
8116
8030
|
source,
|
|
@@ -8230,7 +8144,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8230
8144
|
this.reasoning.dirty = true;
|
|
8231
8145
|
}
|
|
8232
8146
|
const text = split.answerText ?? stripReasoningTags(rawText);
|
|
8233
|
-
log$
|
|
8147
|
+
log$14.debug("onPartialReply", { len: text.length });
|
|
8234
8148
|
if (!text) return;
|
|
8235
8149
|
this.captureToolUseElapsed();
|
|
8236
8150
|
if (!this.reasoning.reasoningStartTime) this.reasoning.reasoningStartTime = Date.now();
|
|
@@ -8242,7 +8156,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8242
8156
|
this.text.lastPartialText = text;
|
|
8243
8157
|
this.text.accumulatedText = this.text.streamingPrefix ? this.text.streamingPrefix + "\n\n" + text : text;
|
|
8244
8158
|
if (!this.text.streamingPrefix && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim())) {
|
|
8245
|
-
log$
|
|
8159
|
+
log$14.debug("onPartialReply: buffering NO_REPLY prefix");
|
|
8246
8160
|
return;
|
|
8247
8161
|
}
|
|
8248
8162
|
await this.ensureCardCreated();
|
|
@@ -8252,7 +8166,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8252
8166
|
}
|
|
8253
8167
|
async onError(err, info) {
|
|
8254
8168
|
if (this.guard.terminate("onError", err)) return;
|
|
8255
|
-
log$
|
|
8169
|
+
log$14.error(`${info.kind} reply failed`, { error: String(err) });
|
|
8256
8170
|
this.captureToolUseElapsed();
|
|
8257
8171
|
this.finalizeCard("onError", "error");
|
|
8258
8172
|
await this.flush.waitForFlush();
|
|
@@ -8308,7 +8222,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8308
8222
|
if (this.cardKit.cardMessageId) {
|
|
8309
8223
|
const isNoReplyLeak = !this.text.completedText && SILENT_REPLY_TOKEN.startsWith(this.text.accumulatedText.trim());
|
|
8310
8224
|
const displayText = this.text.completedText || (isNoReplyLeak ? "" : this.text.accumulatedText) || "Done.";
|
|
8311
|
-
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");
|
|
8312
8226
|
const resolvedDisplayText = await this.imageResolver.resolveImagesAwait(displayText, 15e3);
|
|
8313
8227
|
const idleToolUseDisplay = this.computeToolUseDisplay();
|
|
8314
8228
|
const terminalContent = prepareTerminalCardContent({
|
|
@@ -8336,7 +8250,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8336
8250
|
const rewindSessionId = this.deps.sessionKey;
|
|
8337
8251
|
const rewindMessageId = ClaudeCodeAdapter.getInstance()?.getProcessManager()?.getLastUserMessageId(this.deps.sessionKey);
|
|
8338
8252
|
const rewindHasFileChanges = this.hasFileChangingTools(idleToolUseDisplay?.steps);
|
|
8339
|
-
log$
|
|
8253
|
+
log$14.info("onIdle: rewind button conditions", {
|
|
8340
8254
|
sessionId: rewindSessionId,
|
|
8341
8255
|
messageId: rewindMessageId,
|
|
8342
8256
|
hasFileChanges: rewindHasFileChanges,
|
|
@@ -8349,7 +8263,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8349
8263
|
if (idleEffectiveCardId) {
|
|
8350
8264
|
const seqBeforeClose = this.cardKit.cardKitSequence;
|
|
8351
8265
|
this.cardKit.cardKitSequence += 1;
|
|
8352
|
-
log$
|
|
8266
|
+
log$14.info("onIdle: closing streaming mode", {
|
|
8353
8267
|
attempt,
|
|
8354
8268
|
seqBefore: seqBeforeClose,
|
|
8355
8269
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -8363,7 +8277,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8363
8277
|
});
|
|
8364
8278
|
const seqBeforeUpdate = this.cardKit.cardKitSequence;
|
|
8365
8279
|
this.cardKit.cardKitSequence += 1;
|
|
8366
|
-
log$
|
|
8280
|
+
log$14.info("onIdle: updating final card", {
|
|
8367
8281
|
attempt,
|
|
8368
8282
|
seqBefore: seqBeforeUpdate,
|
|
8369
8283
|
seqAfter: this.cardKit.cardKitSequence
|
|
@@ -8381,33 +8295,33 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8381
8295
|
card: completeCard,
|
|
8382
8296
|
accountId: this.deps.accountId
|
|
8383
8297
|
});
|
|
8384
|
-
log$
|
|
8298
|
+
log$14.info("reply completed, card finalized", {
|
|
8385
8299
|
elapsedMs: this.elapsed(),
|
|
8386
8300
|
isCardKit: !!idleEffectiveCardId,
|
|
8387
8301
|
attempt
|
|
8388
8302
|
});
|
|
8389
8303
|
break;
|
|
8390
8304
|
} catch (retryErr) {
|
|
8391
|
-
log$
|
|
8305
|
+
log$14.warn("final card update attempt failed", {
|
|
8392
8306
|
attempt,
|
|
8393
8307
|
maxRetries: MAX_FINAL_UPDATE_RETRIES,
|
|
8394
8308
|
error: String(retryErr)
|
|
8395
8309
|
});
|
|
8396
8310
|
if (attempt < MAX_FINAL_UPDATE_RETRIES) await new Promise((resolve) => setTimeout(resolve, FINAL_UPDATE_RETRY_DELAY_MS * attempt));
|
|
8397
|
-
else log$
|
|
8311
|
+
else log$14.error("final card update exhausted all retries, card may remain in streaming state", {
|
|
8398
8312
|
cardId: idleEffectiveCardId,
|
|
8399
8313
|
messageId: this.cardKit.cardMessageId
|
|
8400
8314
|
});
|
|
8401
8315
|
}
|
|
8402
8316
|
}
|
|
8403
8317
|
} catch (err) {
|
|
8404
|
-
log$
|
|
8318
|
+
log$14.error("final card update failed (outer)", { error: String(err) });
|
|
8405
8319
|
} finally {
|
|
8406
8320
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
8407
8321
|
}
|
|
8408
8322
|
}
|
|
8409
8323
|
markFullyComplete() {
|
|
8410
|
-
log$
|
|
8324
|
+
log$14.debug("markFullyComplete", {
|
|
8411
8325
|
completedTextLen: this.text.completedText.length,
|
|
8412
8326
|
accumulatedTextLen: this.text.accumulatedText.length
|
|
8413
8327
|
});
|
|
@@ -8446,7 +8360,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8446
8360
|
footerMetrics
|
|
8447
8361
|
});
|
|
8448
8362
|
await this.closeStreamingAndUpdate(effectiveCardId, abortCardContent, "abortCard");
|
|
8449
|
-
log$
|
|
8363
|
+
log$14.info("abortCard completed", { effectiveCardId });
|
|
8450
8364
|
} else if (this.cardKit.cardMessageId) {
|
|
8451
8365
|
const abortCard = buildCardContent("complete", {
|
|
8452
8366
|
text: terminalContent.text,
|
|
@@ -8467,10 +8381,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8467
8381
|
card: abortCard,
|
|
8468
8382
|
accountId: this.deps.accountId
|
|
8469
8383
|
});
|
|
8470
|
-
log$
|
|
8384
|
+
log$14.info("abortCard completed (IM fallback)", { messageId: this.cardKit.cardMessageId });
|
|
8471
8385
|
}
|
|
8472
8386
|
} catch (err) {
|
|
8473
|
-
log$
|
|
8387
|
+
log$14.warn("abortCard failed", { error: String(err) });
|
|
8474
8388
|
} finally {
|
|
8475
8389
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
8476
8390
|
}
|
|
@@ -8485,10 +8399,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8485
8399
|
async forceCloseStreaming() {
|
|
8486
8400
|
const effectiveCardId = this.cardKit.cardKitCardId ?? this.cardKit.originalCardKitCardId;
|
|
8487
8401
|
if (!effectiveCardId && !this.cardKit.cardMessageId) {
|
|
8488
|
-
log$
|
|
8402
|
+
log$14.warn("forceCloseStreaming: no card to close");
|
|
8489
8403
|
return;
|
|
8490
8404
|
}
|
|
8491
|
-
log$
|
|
8405
|
+
log$14.info("forceCloseStreaming: 强制终态化卡片", {
|
|
8492
8406
|
cardId: effectiveCardId,
|
|
8493
8407
|
messageId: this.cardKit.cardMessageId,
|
|
8494
8408
|
phase: this.phase
|
|
@@ -8538,10 +8452,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8538
8452
|
card: completeCard,
|
|
8539
8453
|
accountId: this.deps.accountId
|
|
8540
8454
|
});
|
|
8541
|
-
log$
|
|
8455
|
+
log$14.info("forceCloseStreaming: 卡片已强制终态化");
|
|
8542
8456
|
if (!this.isTerminalPhase) this.finalizeCard("forceCloseStreaming", "abort");
|
|
8543
8457
|
} catch (err) {
|
|
8544
|
-
log$
|
|
8458
|
+
log$14.error("forceCloseStreaming failed", { error: String(err) });
|
|
8545
8459
|
} finally {
|
|
8546
8460
|
unregisterActiveCard(this.deps.sessionKey);
|
|
8547
8461
|
clearToolUseTraceRun(this.deps.sessionKey);
|
|
@@ -8565,7 +8479,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8565
8479
|
this.disposeShutdownHook = null;
|
|
8566
8480
|
return;
|
|
8567
8481
|
}
|
|
8568
|
-
log$
|
|
8482
|
+
log$14.info("reusing placeholder card", {
|
|
8569
8483
|
cardId: this.deps.placeholderCardId,
|
|
8570
8484
|
messageId: this.deps.placeholderMessageId
|
|
8571
8485
|
});
|
|
@@ -8589,7 +8503,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8589
8503
|
accountId: this.deps.accountId
|
|
8590
8504
|
});
|
|
8591
8505
|
if (this.isStaleCreate(epoch)) {
|
|
8592
|
-
log$
|
|
8506
|
+
log$14.info("ensureCardCreated: stale epoch after createCardEntity, bailing out", {
|
|
8593
8507
|
epoch,
|
|
8594
8508
|
phase: this.phase
|
|
8595
8509
|
});
|
|
@@ -8600,7 +8514,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8600
8514
|
this.cardKit.originalCardKitCardId = cId;
|
|
8601
8515
|
this.cardKit.cardKitSequence = 1;
|
|
8602
8516
|
this.disposeShutdownHook = registerShutdownHook(`streaming-card:${cId}`, () => this.abortCard());
|
|
8603
|
-
log$
|
|
8517
|
+
log$14.info("created CardKit entity", {
|
|
8604
8518
|
cardId: cId,
|
|
8605
8519
|
initialSequence: this.cardKit.cardKitSequence
|
|
8606
8520
|
});
|
|
@@ -8613,7 +8527,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8613
8527
|
accountId: this.deps.accountId
|
|
8614
8528
|
});
|
|
8615
8529
|
if (this.isStaleCreate(epoch)) {
|
|
8616
|
-
log$
|
|
8530
|
+
log$14.info("ensureCardCreated: stale epoch after sendCardByCardId, bailing out", {
|
|
8617
8531
|
epoch,
|
|
8618
8532
|
phase: this.phase
|
|
8619
8533
|
});
|
|
@@ -8628,13 +8542,13 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8628
8542
|
this.disposeShutdownHook = null;
|
|
8629
8543
|
return;
|
|
8630
8544
|
}
|
|
8631
|
-
log$
|
|
8545
|
+
log$14.info("sent CardKit card", { messageId: result.messageId });
|
|
8632
8546
|
} else throw new Error("card.create returned empty card_id");
|
|
8633
8547
|
} catch (cardKitErr) {
|
|
8634
8548
|
if (this.isStaleCreate(epoch)) return;
|
|
8635
8549
|
if (this.guard.terminate("ensureCardCreated.cardkitFlow", cardKitErr)) return;
|
|
8636
8550
|
const apiDetail = extractApiDetail(cardKitErr);
|
|
8637
|
-
log$
|
|
8551
|
+
log$14.warn("CardKit flow failed, falling back to IM", { apiDetail });
|
|
8638
8552
|
this.cardKit.cardKitCardId = null;
|
|
8639
8553
|
this.cardKit.originalCardKitCardId = null;
|
|
8640
8554
|
const fallbackCard = buildCardContent("streaming", { showToolUse: this.deps.toolUseDisplay.showToolUse });
|
|
@@ -8647,7 +8561,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8647
8561
|
accountId: this.deps.accountId
|
|
8648
8562
|
});
|
|
8649
8563
|
if (this.isStaleCreate(epoch)) {
|
|
8650
|
-
log$
|
|
8564
|
+
log$14.info("ensureCardCreated: stale epoch after IM fallback send, bailing out", {
|
|
8651
8565
|
epoch,
|
|
8652
8566
|
phase: this.phase
|
|
8653
8567
|
});
|
|
@@ -8656,12 +8570,12 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8656
8570
|
this.cardKit.cardMessageId = result.messageId;
|
|
8657
8571
|
this.flush.setCardMessageReady(true);
|
|
8658
8572
|
if (!this.transition("streaming", "ensureCardCreated.imFallback")) return;
|
|
8659
|
-
log$
|
|
8573
|
+
log$14.info("sent fallback IM card", { messageId: result.messageId });
|
|
8660
8574
|
}
|
|
8661
8575
|
} catch (err) {
|
|
8662
8576
|
if (this.isStaleCreate(epoch)) return;
|
|
8663
8577
|
if (this.guard.terminate("ensureCardCreated.outer", err)) return;
|
|
8664
|
-
log$
|
|
8578
|
+
log$14.warn("thinking card failed, falling back to static", { error: String(err) });
|
|
8665
8579
|
this.transition("creation_failed", "ensureCardCreated.outer", "creation_failed");
|
|
8666
8580
|
}
|
|
8667
8581
|
})();
|
|
@@ -8670,10 +8584,10 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8670
8584
|
async performFlush() {
|
|
8671
8585
|
if (!this.cardKit.cardMessageId || this.isTerminalPhase) return;
|
|
8672
8586
|
if (!this.cardKit.cardKitCardId && this.cardKit.originalCardKitCardId) {
|
|
8673
|
-
log$
|
|
8587
|
+
log$14.debug("performFlush: skipping (CardKit streaming disabled, awaiting final update)");
|
|
8674
8588
|
return;
|
|
8675
8589
|
}
|
|
8676
|
-
log$
|
|
8590
|
+
log$14.debug("flushCardUpdate: enter", {
|
|
8677
8591
|
seq: this.cardKit.cardKitSequence,
|
|
8678
8592
|
isCardKit: !!this.cardKit.cardKitCardId
|
|
8679
8593
|
});
|
|
@@ -8695,7 +8609,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8695
8609
|
reasoningText: this.reasoning.accumulatedReasoningText || void 0
|
|
8696
8610
|
});
|
|
8697
8611
|
this.cardKit.cardKitSequence += 1;
|
|
8698
|
-
log$
|
|
8612
|
+
log$14.debug("flushCardUpdate: full card update (dirty)", {
|
|
8699
8613
|
seq: this.cardKit.cardKitSequence,
|
|
8700
8614
|
stepCount: display?.stepCount,
|
|
8701
8615
|
hasReasoning: !!this.reasoning.accumulatedReasoningText
|
|
@@ -8711,7 +8625,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8711
8625
|
} else if (resolvedText !== this.text.lastFlushedText) {
|
|
8712
8626
|
const prevSeq = this.cardKit.cardKitSequence;
|
|
8713
8627
|
this.cardKit.cardKitSequence += 1;
|
|
8714
|
-
log$
|
|
8628
|
+
log$14.debug("flushCardUpdate: answer seq bump", {
|
|
8715
8629
|
seqBefore: prevSeq,
|
|
8716
8630
|
seqAfter: this.cardKit.cardKitSequence
|
|
8717
8631
|
});
|
|
@@ -8726,7 +8640,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8726
8640
|
this.text.lastFlushedText = resolvedText;
|
|
8727
8641
|
}
|
|
8728
8642
|
} else {
|
|
8729
|
-
log$
|
|
8643
|
+
log$14.debug("flushCardUpdate: IM patch fallback");
|
|
8730
8644
|
const flushDisplay = this.computeToolUseDisplay();
|
|
8731
8645
|
const card = buildCardContent("streaming", {
|
|
8732
8646
|
text: this.reasoning.isReasoningPhase ? "" : resolvedText,
|
|
@@ -8746,31 +8660,31 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8746
8660
|
if (this.guard.terminate("flushCardUpdate", err)) return;
|
|
8747
8661
|
const apiCode = extractLarkApiCode(err);
|
|
8748
8662
|
if (isCardRateLimitError(err)) {
|
|
8749
|
-
log$
|
|
8663
|
+
log$14.info("flushCardUpdate: rate limited (230020), skipping", { seq: this.cardKit.cardKitSequence });
|
|
8750
8664
|
return;
|
|
8751
8665
|
}
|
|
8752
8666
|
if (isCardTableLimitError(err)) {
|
|
8753
|
-
log$
|
|
8667
|
+
log$14.warn("flushCardUpdate: card table limit exceeded (230099/11310), disabling CardKit streaming", { seq: this.cardKit.cardKitSequence });
|
|
8754
8668
|
this.cardKit.cardKitCardId = null;
|
|
8755
8669
|
return;
|
|
8756
8670
|
}
|
|
8757
8671
|
if (apiCode === 300317 && this.cardKit.cardKitCardId) {
|
|
8758
8672
|
const oldSeq = this.cardKit.cardKitSequence;
|
|
8759
8673
|
this.cardKit.cardKitSequence += 100;
|
|
8760
|
-
log$
|
|
8674
|
+
log$14.warn("flushCardUpdate: sequence conflict (300317), jumping sequence", {
|
|
8761
8675
|
oldSeq,
|
|
8762
8676
|
newSeq: this.cardKit.cardKitSequence
|
|
8763
8677
|
});
|
|
8764
8678
|
return;
|
|
8765
8679
|
}
|
|
8766
8680
|
const apiDetail = extractApiDetail(err);
|
|
8767
|
-
log$
|
|
8681
|
+
log$14.error("card stream update failed", {
|
|
8768
8682
|
apiCode,
|
|
8769
8683
|
seq: this.cardKit.cardKitSequence,
|
|
8770
8684
|
apiDetail
|
|
8771
8685
|
});
|
|
8772
8686
|
if (this.cardKit.cardKitCardId) {
|
|
8773
|
-
log$
|
|
8687
|
+
log$14.warn("disabling CardKit streaming, falling back to im.message.patch");
|
|
8774
8688
|
this.cardKit.cardKitCardId = null;
|
|
8775
8689
|
}
|
|
8776
8690
|
}
|
|
@@ -8795,7 +8709,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8795
8709
|
if (!this.cardKit.cardKitCardId || this.isTerminalPhase) return;
|
|
8796
8710
|
try {
|
|
8797
8711
|
const display = this.computeToolUseDisplay();
|
|
8798
|
-
log$
|
|
8712
|
+
log$14.info("updateToolUseStatus", {
|
|
8799
8713
|
hasDisplay: !!display,
|
|
8800
8714
|
stepCount: display?.stepCount,
|
|
8801
8715
|
steps: display?.steps?.map((s) => `${s.title}:${s.status}`).join(", ")
|
|
@@ -8817,7 +8731,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8817
8731
|
accountId: this.deps.accountId
|
|
8818
8732
|
});
|
|
8819
8733
|
} catch (err) {
|
|
8820
|
-
log$
|
|
8734
|
+
log$14.debug("updateToolUseStatus failed", { error: String(err) });
|
|
8821
8735
|
}
|
|
8822
8736
|
}
|
|
8823
8737
|
finalizeCard(source, reason) {
|
|
@@ -8829,7 +8743,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8829
8743
|
async closeStreamingAndUpdate(cardId, card, label) {
|
|
8830
8744
|
const seqBeforeClose = this.cardKit.cardKitSequence;
|
|
8831
8745
|
this.cardKit.cardKitSequence += 1;
|
|
8832
|
-
log$
|
|
8746
|
+
log$14.info(`${label}: closing streaming mode`, {
|
|
8833
8747
|
seqBefore: seqBeforeClose,
|
|
8834
8748
|
seqAfter: this.cardKit.cardKitSequence
|
|
8835
8749
|
});
|
|
@@ -8842,7 +8756,7 @@ var StreamingCardController = class StreamingCardController {
|
|
|
8842
8756
|
});
|
|
8843
8757
|
const seqBeforeUpdate = this.cardKit.cardKitSequence;
|
|
8844
8758
|
this.cardKit.cardKitSequence += 1;
|
|
8845
|
-
log$
|
|
8759
|
+
log$14.info(`${label}: updating card`, {
|
|
8846
8760
|
seqBefore: seqBeforeUpdate,
|
|
8847
8761
|
seqAfter: this.cardKit.cardKitSequence
|
|
8848
8762
|
});
|
|
@@ -8876,7 +8790,7 @@ function extractApiDetail(err) {
|
|
|
8876
8790
|
}
|
|
8877
8791
|
//#endregion
|
|
8878
8792
|
//#region src/messaging/format-for-cc.ts
|
|
8879
|
-
const log$
|
|
8793
|
+
const log$13 = larkLogger("messaging/format-for-cc");
|
|
8880
8794
|
function safeParseJSON(raw) {
|
|
8881
8795
|
try {
|
|
8882
8796
|
return JSON.parse(raw);
|
|
@@ -8932,7 +8846,7 @@ function extractPostText(rawContent) {
|
|
|
8932
8846
|
*/
|
|
8933
8847
|
function formatContentByType(ctx) {
|
|
8934
8848
|
const { contentType, content, resources } = ctx;
|
|
8935
|
-
log$
|
|
8849
|
+
log$13.debug("formatContentByType 开始格式化", {
|
|
8936
8850
|
contentType,
|
|
8937
8851
|
contentLength: content?.length ?? 0,
|
|
8938
8852
|
resourceCount: resources?.length ?? 0
|
|
@@ -8974,7 +8888,7 @@ function formatContentByType(ctx) {
|
|
|
8974
8888
|
case "todo":
|
|
8975
8889
|
case "vote": return content;
|
|
8976
8890
|
default:
|
|
8977
|
-
log$
|
|
8891
|
+
log$13.warn("遇到不支持的消息类型", { contentType });
|
|
8978
8892
|
return `[不支持的消息类型: ${contentType}]`;
|
|
8979
8893
|
}
|
|
8980
8894
|
}
|
|
@@ -9017,7 +8931,7 @@ function formatMessageTime(createTime) {
|
|
|
9017
8931
|
* @returns 格式化后的纯文本字符串
|
|
9018
8932
|
*/
|
|
9019
8933
|
function formatForCC(ctx, isGroup) {
|
|
9020
|
-
log$
|
|
8934
|
+
log$13.info("formatForCC 开始处理", {
|
|
9021
8935
|
messageId: ctx.messageId,
|
|
9022
8936
|
contentType: ctx.contentType,
|
|
9023
8937
|
chatType: ctx.chatType,
|
|
@@ -9033,7 +8947,7 @@ function formatForCC(ctx, isGroup) {
|
|
|
9033
8947
|
if (isGroup && ctx.senderName) parts.push(`[${ctx.senderName}]`);
|
|
9034
8948
|
if (ctx.threadId) parts.push("[话题回复]");
|
|
9035
8949
|
const result = (parts.length > 0 ? parts.join(" ") + " " : "") + formattedContent;
|
|
9036
|
-
log$
|
|
8950
|
+
log$13.info("formatForCC 完成", {
|
|
9037
8951
|
messageId: ctx.messageId,
|
|
9038
8952
|
resultLength: result.length,
|
|
9039
8953
|
hasTime: !!timeStr,
|
|
@@ -9043,7 +8957,7 @@ function formatForCC(ctx, isGroup) {
|
|
|
9043
8957
|
}
|
|
9044
8958
|
//#endregion
|
|
9045
8959
|
//#region src/messaging/inbound/dispatch-cc.ts
|
|
9046
|
-
const log$
|
|
8960
|
+
const log$12 = larkLogger("inbound/dispatch-cc");
|
|
9047
8961
|
async function dispatchToCC(params) {
|
|
9048
8962
|
const { ctx, account, sessionRouter, processManager } = params;
|
|
9049
8963
|
const chatId = ctx.chatId;
|
|
@@ -9053,7 +8967,7 @@ async function dispatchToCC(params) {
|
|
|
9053
8967
|
const ccModel = params.model;
|
|
9054
8968
|
const maxTurns = params.maxTurns;
|
|
9055
8969
|
const maxBudgetUsd = params.maxBudgetUsd;
|
|
9056
|
-
log$
|
|
8970
|
+
log$12.info("dispatchToCC 开始", {
|
|
9057
8971
|
msgId,
|
|
9058
8972
|
chatId,
|
|
9059
8973
|
chatType,
|
|
@@ -9066,12 +8980,12 @@ async function dispatchToCC(params) {
|
|
|
9066
8980
|
threadId,
|
|
9067
8981
|
userId: ctx.senderId
|
|
9068
8982
|
});
|
|
9069
|
-
log$
|
|
8983
|
+
log$12.info("会话路由解析完成", {
|
|
9070
8984
|
sessionId: route.sessionId,
|
|
9071
8985
|
cwd: route.cwd,
|
|
9072
8986
|
type: route.type
|
|
9073
8987
|
});
|
|
9074
|
-
if (params.userContext) log$
|
|
8988
|
+
if (params.userContext) log$12.info("用户上下文已注入", {
|
|
9075
8989
|
userId: params.userContext.userId,
|
|
9076
8990
|
isTenantIdentity: params.userContext.isTenantIdentity,
|
|
9077
8991
|
credentialDir: params.userContext.credentialDir
|
|
@@ -9108,7 +9022,7 @@ async function dispatchToCC(params) {
|
|
|
9108
9022
|
for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
9109
9023
|
buffer = Buffer.concat(chunks);
|
|
9110
9024
|
} else {
|
|
9111
|
-
log$
|
|
9025
|
+
log$12.warn("图片资源下载返回未知格式,跳过", {
|
|
9112
9026
|
fileKey: imgRes.fileKey,
|
|
9113
9027
|
responseType: typeof response
|
|
9114
9028
|
});
|
|
@@ -9116,7 +9030,7 @@ async function dispatchToCC(params) {
|
|
|
9116
9030
|
}
|
|
9117
9031
|
if (resp.headers?.["content-type"]) mediaType = resp.headers["content-type"];
|
|
9118
9032
|
} else {
|
|
9119
|
-
log$
|
|
9033
|
+
log$12.warn("图片资源下载返回空,跳过", { fileKey: imgRes.fileKey });
|
|
9120
9034
|
continue;
|
|
9121
9035
|
}
|
|
9122
9036
|
const imgFileName = `${imgRes.fileKey}.png`;
|
|
@@ -9126,13 +9040,13 @@ async function dispatchToCC(params) {
|
|
|
9126
9040
|
const { writeFile, mkdir } = await import("fs/promises");
|
|
9127
9041
|
await mkdir(filesDir, { recursive: true });
|
|
9128
9042
|
await writeFile(imgFilePath, buffer);
|
|
9129
|
-
log$
|
|
9043
|
+
log$12.info("图片已保存到工作目录", {
|
|
9130
9044
|
fileKey: imgRes.fileKey,
|
|
9131
9045
|
path: imgFilePath,
|
|
9132
9046
|
sizeBytes: buffer.length
|
|
9133
9047
|
});
|
|
9134
9048
|
} catch (saveErr) {
|
|
9135
|
-
log$
|
|
9049
|
+
log$12.warn("图片保存到工作目录失败", {
|
|
9136
9050
|
fileKey: imgRes.fileKey,
|
|
9137
9051
|
path: imgFilePath,
|
|
9138
9052
|
error: saveErr instanceof Error ? saveErr.message : String(saveErr)
|
|
@@ -9147,13 +9061,13 @@ async function dispatchToCC(params) {
|
|
|
9147
9061
|
data: base64Data
|
|
9148
9062
|
}
|
|
9149
9063
|
});
|
|
9150
|
-
log$
|
|
9064
|
+
log$12.info("图片资源下载成功", {
|
|
9151
9065
|
fileKey: imgRes.fileKey,
|
|
9152
9066
|
mediaType,
|
|
9153
9067
|
sizeBytes: buffer.length
|
|
9154
9068
|
});
|
|
9155
9069
|
} catch (err) {
|
|
9156
|
-
log$
|
|
9070
|
+
log$12.warn("图片资源下载失败,跳过", {
|
|
9157
9071
|
fileKey: imgRes.fileKey,
|
|
9158
9072
|
error: err instanceof Error ? err.message : String(err)
|
|
9159
9073
|
});
|
|
@@ -9163,7 +9077,7 @@ async function dispatchToCC(params) {
|
|
|
9163
9077
|
type: "text",
|
|
9164
9078
|
text: textPrompt
|
|
9165
9079
|
}, ...imageBlocks];
|
|
9166
|
-
log$
|
|
9080
|
+
log$12.info("已构建多模态 prompt", {
|
|
9167
9081
|
textLength: textPrompt.length,
|
|
9168
9082
|
imageCount: imageBlocks.length
|
|
9169
9083
|
});
|
|
@@ -9176,7 +9090,7 @@ async function dispatchToCC(params) {
|
|
|
9176
9090
|
await fsMkdir(filesDir, { recursive: true });
|
|
9177
9091
|
let updatedTextPrompt = typeof prompt === "string" ? prompt : prompt[0].text;
|
|
9178
9092
|
for (const res of attachmentResources) try {
|
|
9179
|
-
log$
|
|
9093
|
+
log$12.info("开始下载非图片附件", {
|
|
9180
9094
|
type: res.type,
|
|
9181
9095
|
fileKey: res.fileKey,
|
|
9182
9096
|
fileName: res.fileName,
|
|
@@ -9190,7 +9104,7 @@ async function dispatchToCC(params) {
|
|
|
9190
9104
|
},
|
|
9191
9105
|
params: { type: "file" }
|
|
9192
9106
|
});
|
|
9193
|
-
log$
|
|
9107
|
+
log$12.info("非图片附件 API 响应已收到", {
|
|
9194
9108
|
type: res.type,
|
|
9195
9109
|
fileKey: res.fileKey,
|
|
9196
9110
|
responseType: typeof response,
|
|
@@ -9209,7 +9123,7 @@ async function dispatchToCC(params) {
|
|
|
9209
9123
|
for await (const chunk of readable) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
9210
9124
|
buffer = Buffer.concat(chunks);
|
|
9211
9125
|
} else {
|
|
9212
|
-
log$
|
|
9126
|
+
log$12.warn("非图片附件下载返回未知格式,跳过", {
|
|
9213
9127
|
type: res.type,
|
|
9214
9128
|
fileKey: res.fileKey,
|
|
9215
9129
|
responseType: typeof response
|
|
@@ -9217,7 +9131,7 @@ async function dispatchToCC(params) {
|
|
|
9217
9131
|
continue;
|
|
9218
9132
|
}
|
|
9219
9133
|
} else {
|
|
9220
|
-
log$
|
|
9134
|
+
log$12.warn("非图片附件下载返回空,跳过", {
|
|
9221
9135
|
type: res.type,
|
|
9222
9136
|
fileKey: res.fileKey
|
|
9223
9137
|
});
|
|
@@ -9240,7 +9154,7 @@ async function dispatchToCC(params) {
|
|
|
9240
9154
|
}
|
|
9241
9155
|
const filePath = path.join(filesDir, savedFileName);
|
|
9242
9156
|
await fsWriteFile(filePath, buffer);
|
|
9243
|
-
log$
|
|
9157
|
+
log$12.info("非图片附件已保存到工作目录", {
|
|
9244
9158
|
type: res.type,
|
|
9245
9159
|
fileKey: res.fileKey,
|
|
9246
9160
|
savedFileName,
|
|
@@ -9274,7 +9188,7 @@ async function dispatchToCC(params) {
|
|
|
9274
9188
|
}
|
|
9275
9189
|
}
|
|
9276
9190
|
} catch (err) {
|
|
9277
|
-
log$
|
|
9191
|
+
log$12.warn("非图片附件下载失败,保留原始占位符", {
|
|
9278
9192
|
type: res.type,
|
|
9279
9193
|
fileKey: res.fileKey,
|
|
9280
9194
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -9285,12 +9199,12 @@ async function dispatchToCC(params) {
|
|
|
9285
9199
|
const textBlock = prompt.find((b) => b.type === "text");
|
|
9286
9200
|
if (textBlock) textBlock.text = updatedTextPrompt;
|
|
9287
9201
|
}
|
|
9288
|
-
log$
|
|
9202
|
+
log$12.info("非图片附件处理完成", {
|
|
9289
9203
|
totalAttachments: attachmentResources.length,
|
|
9290
9204
|
promptLength: typeof prompt === "string" ? prompt.length : prompt.find((b) => b.type === "text")?.text?.length
|
|
9291
9205
|
});
|
|
9292
9206
|
}
|
|
9293
|
-
log$
|
|
9207
|
+
log$12.info("消息格式化完成", {
|
|
9294
9208
|
promptLength: typeof prompt === "string" ? prompt.length : prompt.length,
|
|
9295
9209
|
isMultimodal: Array.isArray(prompt),
|
|
9296
9210
|
isGroup
|
|
@@ -9322,50 +9236,50 @@ async function dispatchToCC(params) {
|
|
|
9322
9236
|
};
|
|
9323
9237
|
const cardController = new StreamingCardController(cardDeps);
|
|
9324
9238
|
registerActiveCard(route.sessionId, cardController);
|
|
9325
|
-
log$
|
|
9239
|
+
log$12.info("StreamingCardController 已创建", {
|
|
9326
9240
|
sessionId: route.sessionId,
|
|
9327
9241
|
chatId,
|
|
9328
9242
|
replyToMessageId: cardDeps.replyToMessageId
|
|
9329
9243
|
});
|
|
9330
9244
|
cardController.ensureCardCreated().catch((err) => {
|
|
9331
|
-
log$
|
|
9245
|
+
log$12.warn("提前创建卡片失败(streaming 回调会重试)", { error: String(err) });
|
|
9332
9246
|
});
|
|
9333
9247
|
const bridge = new CCStreamBridge(cardController, {
|
|
9334
9248
|
autoCompleteOnTurnEnd: true,
|
|
9335
9249
|
sessionKey: route.sessionId
|
|
9336
9250
|
});
|
|
9337
|
-
log$
|
|
9251
|
+
log$12.info("CCStreamBridge 已创建", { sessionId: route.sessionId });
|
|
9338
9252
|
const callbacks = {
|
|
9339
9253
|
onTextDelta: (text) => {
|
|
9340
|
-
log$
|
|
9254
|
+
log$12.debug("CC onTextDelta", {
|
|
9341
9255
|
sessionId: route.sessionId,
|
|
9342
9256
|
deltaLen: text.length
|
|
9343
9257
|
});
|
|
9344
9258
|
bridge.onTextDelta(text);
|
|
9345
9259
|
},
|
|
9346
9260
|
onThinkingDelta: (text) => {
|
|
9347
|
-
log$
|
|
9261
|
+
log$12.debug("CC onThinkingDelta", {
|
|
9348
9262
|
sessionId: route.sessionId,
|
|
9349
9263
|
deltaLen: text.length
|
|
9350
9264
|
});
|
|
9351
9265
|
bridge.onThinkingDelta(text);
|
|
9352
9266
|
},
|
|
9353
9267
|
onToolUseStart: (toolName, toolInput) => {
|
|
9354
|
-
log$
|
|
9268
|
+
log$12.info("CC onToolUseStart", {
|
|
9355
9269
|
sessionId: route.sessionId,
|
|
9356
9270
|
toolName
|
|
9357
9271
|
});
|
|
9358
9272
|
bridge.onToolUseStart(toolName, toolInput);
|
|
9359
9273
|
},
|
|
9360
9274
|
onToolResult: (toolUseId) => {
|
|
9361
|
-
log$
|
|
9275
|
+
log$12.info("CC onToolResult", {
|
|
9362
9276
|
sessionId: route.sessionId,
|
|
9363
9277
|
toolUseId
|
|
9364
9278
|
});
|
|
9365
9279
|
bridge.onToolResult(toolUseId);
|
|
9366
9280
|
},
|
|
9367
9281
|
onToolProgress: (toolName, elapsedSeconds) => {
|
|
9368
|
-
log$
|
|
9282
|
+
log$12.debug("CC onToolProgress", {
|
|
9369
9283
|
sessionId: route.sessionId,
|
|
9370
9284
|
toolName,
|
|
9371
9285
|
elapsedSeconds
|
|
@@ -9373,14 +9287,14 @@ async function dispatchToCC(params) {
|
|
|
9373
9287
|
bridge.onToolProgress(toolName, elapsedSeconds);
|
|
9374
9288
|
},
|
|
9375
9289
|
onTurnEnd: (stopReason) => {
|
|
9376
|
-
log$
|
|
9290
|
+
log$12.info("CC onTurnEnd", {
|
|
9377
9291
|
sessionId: route.sessionId,
|
|
9378
9292
|
stopReason
|
|
9379
9293
|
});
|
|
9380
9294
|
bridge.onTurnEnd(stopReason);
|
|
9381
9295
|
},
|
|
9382
9296
|
onResult: (result) => {
|
|
9383
|
-
log$
|
|
9297
|
+
log$12.info("CC onResult", {
|
|
9384
9298
|
sessionId: route.sessionId,
|
|
9385
9299
|
subtype: result.subtype,
|
|
9386
9300
|
isError: result.isError,
|
|
@@ -9391,14 +9305,14 @@ async function dispatchToCC(params) {
|
|
|
9391
9305
|
bridge.onResult(result);
|
|
9392
9306
|
},
|
|
9393
9307
|
onError: (error) => {
|
|
9394
|
-
log$
|
|
9308
|
+
log$12.error("CC 进程错误", {
|
|
9395
9309
|
sessionId: route.sessionId,
|
|
9396
9310
|
error: error.message
|
|
9397
9311
|
});
|
|
9398
9312
|
cardController.onError(error, { kind: "cc-process" });
|
|
9399
9313
|
}
|
|
9400
9314
|
};
|
|
9401
|
-
log$
|
|
9315
|
+
log$12.info("开始执行 CC 进程", {
|
|
9402
9316
|
sessionId: route.sessionId,
|
|
9403
9317
|
cwd: route.cwd,
|
|
9404
9318
|
promptLength: prompt.length,
|
|
@@ -9415,10 +9329,10 @@ async function dispatchToCC(params) {
|
|
|
9415
9329
|
maxBudgetUsd,
|
|
9416
9330
|
userContext: params.userContext
|
|
9417
9331
|
}, callbacks);
|
|
9418
|
-
log$
|
|
9332
|
+
log$12.info("CC 进程 executePrompt 调用完成", { sessionId: route.sessionId });
|
|
9419
9333
|
} catch (err) {
|
|
9420
9334
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
9421
|
-
log$
|
|
9335
|
+
log$12.error("CC 进程 executePrompt 调用异常", {
|
|
9422
9336
|
sessionId: route.sessionId,
|
|
9423
9337
|
error: errorMessage
|
|
9424
9338
|
});
|
|
@@ -9440,7 +9354,7 @@ async function dispatchToCC(params) {
|
|
|
9440
9354
|
*/
|
|
9441
9355
|
async function dispatchTeammateEval(params) {
|
|
9442
9356
|
const { chatId, prompt, account, sessionRouter, processManager } = params;
|
|
9443
|
-
log$
|
|
9357
|
+
log$12.info("dispatchTeammateEval 开始", {
|
|
9444
9358
|
chatId,
|
|
9445
9359
|
promptLength: prompt.length
|
|
9446
9360
|
});
|
|
@@ -9479,7 +9393,7 @@ async function dispatchTeammateEval(params) {
|
|
|
9479
9393
|
const confirmReply = () => {
|
|
9480
9394
|
if (confirmed) return;
|
|
9481
9395
|
confirmed = true;
|
|
9482
|
-
log$
|
|
9396
|
+
log$12.info("teammate 确认回复,创建流式卡片", {
|
|
9483
9397
|
chatId,
|
|
9484
9398
|
thinkingLen: thinkingBuffer.length,
|
|
9485
9399
|
textLen: fullTextBuffer.length
|
|
@@ -9573,7 +9487,7 @@ async function dispatchTeammateEval(params) {
|
|
|
9573
9487
|
onTurnEnd: (stopReason) => {
|
|
9574
9488
|
finalStopReason = stopReason;
|
|
9575
9489
|
if (stopReason === "tool_use") {
|
|
9576
|
-
log$
|
|
9490
|
+
log$12.debug("teammate turnEnd: tool_use, 跳过最终判断", { chatId });
|
|
9577
9491
|
if (confirmed && bridge) bridge.onTurnEnd(stopReason);
|
|
9578
9492
|
return;
|
|
9579
9493
|
}
|
|
@@ -9581,7 +9495,7 @@ async function dispatchTeammateEval(params) {
|
|
|
9581
9495
|
const trimmed = fullTextBuffer.trim();
|
|
9582
9496
|
if (trimmed === NO_REPLY_TOKEN || trimmed === CC_INTERNAL_PLACEHOLDER || trimmed.endsWith(NO_REPLY_TOKEN) || trimmed === "") {
|
|
9583
9497
|
silenced = true;
|
|
9584
|
-
log$
|
|
9498
|
+
log$12.info("teammate turnEnd 最终判断: 静默", {
|
|
9585
9499
|
chatId,
|
|
9586
9500
|
trimmed: trimmed.slice(0, 60)
|
|
9587
9501
|
});
|
|
@@ -9596,7 +9510,7 @@ async function dispatchTeammateEval(params) {
|
|
|
9596
9510
|
resolve();
|
|
9597
9511
|
},
|
|
9598
9512
|
onError: (error) => {
|
|
9599
|
-
log$
|
|
9513
|
+
log$12.error("teammate 评估 CC 进程错误", {
|
|
9600
9514
|
chatId,
|
|
9601
9515
|
error: error.message
|
|
9602
9516
|
});
|
|
@@ -9609,14 +9523,14 @@ async function dispatchTeammateEval(params) {
|
|
|
9609
9523
|
prompt,
|
|
9610
9524
|
model: params.model
|
|
9611
9525
|
}, callbacks).catch((err) => {
|
|
9612
|
-
log$
|
|
9526
|
+
log$12.error("teammate executePrompt 异常", {
|
|
9613
9527
|
chatId,
|
|
9614
9528
|
error: err instanceof Error ? err.message : String(err)
|
|
9615
9529
|
});
|
|
9616
9530
|
resolve();
|
|
9617
9531
|
});
|
|
9618
9532
|
});
|
|
9619
|
-
log$
|
|
9533
|
+
log$12.info("dispatchTeammateEval 完成", {
|
|
9620
9534
|
chatId,
|
|
9621
9535
|
confirmed,
|
|
9622
9536
|
silenced,
|
|
@@ -9627,7 +9541,7 @@ async function dispatchTeammateEval(params) {
|
|
|
9627
9541
|
return { replied: confirmed };
|
|
9628
9542
|
} catch (err) {
|
|
9629
9543
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
9630
|
-
log$
|
|
9544
|
+
log$12.error("dispatchTeammateEval 异常", {
|
|
9631
9545
|
chatId,
|
|
9632
9546
|
error: errorMessage
|
|
9633
9547
|
});
|
|
@@ -11139,7 +11053,7 @@ const convertLocation = (raw) => {
|
|
|
11139
11053
|
* injected via callbacks in `ConvertContext`. Callers are responsible
|
|
11140
11054
|
* for creating the appropriate callbacks (UAT / TAT / event push).
|
|
11141
11055
|
*/
|
|
11142
|
-
const log$
|
|
11056
|
+
const log$11 = larkLogger("converters/merge-forward");
|
|
11143
11057
|
/**
|
|
11144
11058
|
* Recursively expand a merge_forward message.
|
|
11145
11059
|
*
|
|
@@ -11167,7 +11081,7 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
|
|
|
11167
11081
|
try {
|
|
11168
11082
|
items = await fetchSubMessages(messageId);
|
|
11169
11083
|
} catch (error) {
|
|
11170
|
-
log$
|
|
11084
|
+
log$11.error("fetch sub-messages failed", {
|
|
11171
11085
|
messageId,
|
|
11172
11086
|
error: error instanceof Error ? error.message : String(error)
|
|
11173
11087
|
});
|
|
@@ -11179,7 +11093,7 @@ async function expand(accountId, messageId, resolveUserName, batchResolveNames,
|
|
|
11179
11093
|
if (senderIds.length > 0 && batchResolveNames) try {
|
|
11180
11094
|
await batchResolveNames(senderIds);
|
|
11181
11095
|
} catch (err) {
|
|
11182
|
-
log$
|
|
11096
|
+
log$11.debug("batchResolveNames failed (best-effort)", { error: err instanceof Error ? err.message : String(err) });
|
|
11183
11097
|
}
|
|
11184
11098
|
return formatSubTree(messageId, childrenMap, accountId, resolveUserName, convertContent);
|
|
11185
11099
|
}
|
|
@@ -11259,7 +11173,7 @@ async function formatSubTree(parentId, childrenMap, accountId, resolveUserName,
|
|
|
11259
11173
|
const indented = indentLines(content, " ");
|
|
11260
11174
|
parts.push(`[${timestamp}] ${displayName}:\n${indented}`);
|
|
11261
11175
|
} catch (err) {
|
|
11262
|
-
log$
|
|
11176
|
+
log$11.warn("failed to convert sub-message", {
|
|
11263
11177
|
messageId: item.message_id,
|
|
11264
11178
|
msgType: item.msg_type ?? "unknown",
|
|
11265
11179
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -11473,7 +11387,7 @@ async function convertMessageContent(raw, messageType, ctx) {
|
|
|
11473
11387
|
}
|
|
11474
11388
|
//#endregion
|
|
11475
11389
|
//#region src/messaging/inbound/parse-io.ts
|
|
11476
|
-
const log$
|
|
11390
|
+
const log$10 = larkLogger("inbound/parse-io");
|
|
11477
11391
|
/**
|
|
11478
11392
|
* 对 interactive 消息,通过 TAT 调用 API 获取完整 v2 卡片内容。
|
|
11479
11393
|
* 事件推送的 content 可能不包含 json_card,API 调用可返回完整的 raw_card_content。
|
|
@@ -11493,7 +11407,7 @@ async function fetchCardContent(messageId, larkClient) {
|
|
|
11493
11407
|
}
|
|
11494
11408
|
}))?.data?.items?.[0]?.body?.content ?? void 0;
|
|
11495
11409
|
} catch (err) {
|
|
11496
|
-
log$
|
|
11410
|
+
log$10.warn(`fetchCardContent failed for ${messageId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
11497
11411
|
return;
|
|
11498
11412
|
}
|
|
11499
11413
|
}
|
|
@@ -11527,11 +11441,11 @@ function createFetchSubMessages(larkClient) {
|
|
|
11527
11441
|
* the account and log function.
|
|
11528
11442
|
*/
|
|
11529
11443
|
function createParseResolveNames(account) {
|
|
11530
|
-
return createBatchResolveNames(account, (...args) => log$
|
|
11444
|
+
return createBatchResolveNames(account, (...args) => log$10.info(args.map(String).join(" ")));
|
|
11531
11445
|
}
|
|
11532
11446
|
//#endregion
|
|
11533
11447
|
//#region src/messaging/inbound/parse.ts
|
|
11534
|
-
const log$
|
|
11448
|
+
const log$9 = larkLogger("inbound/parse");
|
|
11535
11449
|
/**
|
|
11536
11450
|
* Parse a raw Feishu message event into a normalised MessageContext.
|
|
11537
11451
|
*
|
|
@@ -11581,7 +11495,7 @@ async function parseMessageEvent(event, botOpenId, expandCtx) {
|
|
|
11581
11495
|
const fullContent = await fetchCardContent(event.message.message_id, larkClient);
|
|
11582
11496
|
if (fullContent) {
|
|
11583
11497
|
effectiveContent = fullContent;
|
|
11584
|
-
log$
|
|
11498
|
+
log$9.info("replaced interactive content with full v2 card data");
|
|
11585
11499
|
}
|
|
11586
11500
|
}
|
|
11587
11501
|
const convertCtx = {
|
|
@@ -11701,6 +11615,123 @@ var MessageDedup = class {
|
|
|
11701
11615
|
}
|
|
11702
11616
|
};
|
|
11703
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
|
|
11704
11735
|
//#region src/user/types.ts
|
|
11705
11736
|
/** Tenant 级别的 UserContext(群聊默认身份) */
|
|
11706
11737
|
const TENANT_USER_ID = "tenant";
|
|
@@ -11826,9 +11857,7 @@ async function ensureUserDirectory(userCtx) {
|
|
|
11826
11857
|
larkLogger("user/oauth-proxy");
|
|
11827
11858
|
//#endregion
|
|
11828
11859
|
//#region src/gateway/plugins/loader.ts
|
|
11829
|
-
const PLUGIN_REGISTRY = {
|
|
11830
|
-
return (await import("./preview-proxy-KMPQK_j4.mjs")).createPreviewProxyPlugin;
|
|
11831
|
-
} };
|
|
11860
|
+
const PLUGIN_REGISTRY = {};
|
|
11832
11861
|
const log$4 = larkLogger("gateway/plugin-loader");
|
|
11833
11862
|
/** 已加载的插件实例列表 */
|
|
11834
11863
|
let loadedPlugins = [];
|
|
@@ -12276,7 +12305,8 @@ var TeammateBuffer = class {
|
|
|
12276
12305
|
this.evalCallback({
|
|
12277
12306
|
chatId,
|
|
12278
12307
|
prompt,
|
|
12279
|
-
messageCount: messages.length
|
|
12308
|
+
messageCount: messages.length,
|
|
12309
|
+
messages: [...messages]
|
|
12280
12310
|
}).catch((err) => {
|
|
12281
12311
|
log$1.error("teammate 评估回调异常", {
|
|
12282
12312
|
chatId,
|
|
@@ -12300,9 +12330,7 @@ var TeammateBuffer = class {
|
|
|
12300
12330
|
"",
|
|
12301
12331
|
"重要规则:",
|
|
12302
12332
|
"- 这是一次旁听评估,仅在本次评估中适用 NO_REPLY 规则。后续如果用户直接 @你 则必须正常回复。",
|
|
12303
|
-
"-
|
|
12304
|
-
"- 不要使用任何工具(Bash/Glob/Read 等)来尝试查找或分析图片文件。",
|
|
12305
|
-
"- 只基于文字内容判断是否参与。",
|
|
12333
|
+
"- 不要使用工具来查找或分析消息内容,直接基于消息内容判断是否参与。",
|
|
12306
12334
|
"",
|
|
12307
12335
|
"---"
|
|
12308
12336
|
].join("\n");
|
|
@@ -12814,6 +12842,8 @@ async function main() {
|
|
|
12814
12842
|
workspaceRoot,
|
|
12815
12843
|
externalBaseUrl: process.env.LARKPAL_EXTERNAL_URL
|
|
12816
12844
|
});
|
|
12845
|
+
const { discoverTools } = await import("./tool-registry-consumer-DrklfqGF.mjs").then((n) => n.n);
|
|
12846
|
+
await discoverTools(gatewayPort);
|
|
12817
12847
|
await gateway.start();
|
|
12818
12848
|
logger.info("网关 HTTP 服务启动完成", {
|
|
12819
12849
|
host: gatewayHost,
|
|
@@ -12855,13 +12885,29 @@ async function main() {
|
|
|
12855
12885
|
messageCount: payload.messageCount,
|
|
12856
12886
|
promptLength: payload.prompt.length
|
|
12857
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
|
+
}
|
|
12858
12904
|
const { status: queueStatus } = enqueueFeishuChatTask({
|
|
12859
12905
|
accountId: "default",
|
|
12860
12906
|
chatId: payload.chatId,
|
|
12861
12907
|
task: async () => {
|
|
12862
12908
|
const result = await dispatchTeammateEval({
|
|
12863
12909
|
chatId: payload.chatId,
|
|
12864
|
-
prompt:
|
|
12910
|
+
prompt: evalPrompt,
|
|
12865
12911
|
account,
|
|
12866
12912
|
sessionRouter,
|
|
12867
12913
|
processManager: runtimeAdapter,
|
|
@@ -12950,7 +12996,8 @@ async function main() {
|
|
|
12950
12996
|
teammateBuffer.push(parsed.chatId, {
|
|
12951
12997
|
formattedText,
|
|
12952
12998
|
bufferedAt: Date.now(),
|
|
12953
|
-
messageId: msgId
|
|
12999
|
+
messageId: msgId,
|
|
13000
|
+
resources: parsed.resources.length > 0 ? parsed.resources : void 0
|
|
12954
13001
|
});
|
|
12955
13002
|
logger.info("回复策略: teammate 消息已写入缓冲", {
|
|
12956
13003
|
msgId,
|