@vibe-lark/larkpal 0.1.22 → 0.1.24

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.
Files changed (2) hide show
  1. package/dist/main.mjs +88 -12
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -435,7 +435,10 @@ var CCStreamParser = class extends EventEmitter {
435
435
  this.handleResult(msg);
436
436
  break;
437
437
  case "system":
438
- log$28.info("收到 system 消息", { subtype: msg.subtype });
438
+ log$28.info("收到 system 消息", {
439
+ subtype: msg.subtype,
440
+ detail: JSON.stringify(msg).slice(0, 500)
441
+ });
439
442
  this.emit("system", msg);
440
443
  break;
441
444
  default:
@@ -957,10 +960,15 @@ var SessionProcessManager = class {
957
960
  */
958
961
  async executePrompt(config, callbacks) {
959
962
  const { sessionId } = config;
963
+ const t0 = Date.now();
960
964
  const prevLock = this.sessionLocks.get(sessionId);
961
965
  if (prevLock) {
962
966
  log$25.info("等待上一条消息处理完成", { sessionId });
963
967
  await prevLock;
968
+ log$25.info("[perf] 串行锁等待完成", {
969
+ sessionId,
970
+ waitMs: Date.now() - t0
971
+ });
964
972
  }
965
973
  const completionPromise = this.createMessageCompletionPromise(sessionId);
966
974
  this.sessionLocks.set(sessionId, completionPromise);
@@ -970,11 +978,16 @@ var SessionProcessManager = class {
970
978
  log$25.info("向常驻进程发送消息", {
971
979
  sessionId,
972
980
  promptLength: config.prompt.length,
973
- pid: existing.childProcess.pid
981
+ pid: existing.childProcess.pid,
982
+ prepareMs: Date.now() - t0
974
983
  });
975
984
  this.sendMessage(sessionId, config.prompt);
976
985
  this.resetIdleTimer(sessionId);
977
986
  await completionPromise;
987
+ log$25.info("[perf] executePrompt 完成(热进程)", {
988
+ sessionId,
989
+ totalMs: Date.now() - t0
990
+ });
978
991
  return;
979
992
  }
980
993
  if (existing && (existing.status === "stopped" || existing.status === "crashed")) {
@@ -990,8 +1003,17 @@ var SessionProcessManager = class {
990
1003
  await this.stopProcess(sessionId);
991
1004
  this.sessionLocks.set(sessionId, completionPromise);
992
1005
  }
1006
+ const tStart = Date.now();
993
1007
  await this.startPersistentProcess(config);
1008
+ log$25.info("[perf] 冷启动进程完成", {
1009
+ sessionId,
1010
+ coldStartMs: Date.now() - tStart
1011
+ });
994
1012
  await completionPromise;
1013
+ log$25.info("[perf] executePrompt 完成(冷启动)", {
1014
+ sessionId,
1015
+ totalMs: Date.now() - t0
1016
+ });
995
1017
  }
996
1018
  /**
997
1019
  * 启动常驻 CC 进程
@@ -1207,10 +1229,31 @@ var SessionProcessManager = class {
1207
1229
  */
1208
1230
  bindParserToCallbackProxy(parser, sessionId) {
1209
1231
  const getCallbacks = () => this.activeCallbacks.get(sessionId);
1232
+ let firstTokenLogged = false;
1233
+ let messageSentAt = Date.now();
1234
+ const proc = this.processes.get(sessionId);
1235
+ if (proc) proc._resetPerfTimer = () => {
1236
+ firstTokenLogged = false;
1237
+ messageSentAt = Date.now();
1238
+ };
1210
1239
  parser.on("textDelta", (text) => {
1240
+ if (!firstTokenLogged) {
1241
+ firstTokenLogged = true;
1242
+ log$25.info("[perf] 首 token (text)", {
1243
+ sessionId,
1244
+ ttftMs: Date.now() - messageSentAt
1245
+ });
1246
+ }
1211
1247
  getCallbacks()?.onTextDelta?.(text);
1212
1248
  });
1213
1249
  parser.on("thinkingDelta", (text) => {
1250
+ if (!firstTokenLogged) {
1251
+ firstTokenLogged = true;
1252
+ log$25.info("[perf] 首 token (thinking)", {
1253
+ sessionId,
1254
+ ttftMs: Date.now() - messageSentAt
1255
+ });
1256
+ }
1214
1257
  getCallbacks()?.onThinkingDelta?.(text);
1215
1258
  });
1216
1259
  parser.on("toolUseStart", (toolName, toolInput) => {
@@ -1223,9 +1266,18 @@ var SessionProcessManager = class {
1223
1266
  getCallbacks()?.onToolProgress?.(toolName, elapsedSeconds);
1224
1267
  });
1225
1268
  parser.on("turnEnd", (stopReason) => {
1269
+ log$25.info("[perf] turnEnd", {
1270
+ sessionId,
1271
+ stopReason,
1272
+ sinceMessageMs: Date.now() - messageSentAt
1273
+ });
1226
1274
  getCallbacks()?.onTurnEnd?.(stopReason);
1227
1275
  });
1228
1276
  parser.on("result", (result) => {
1277
+ log$25.info("[perf] result 事件", {
1278
+ sessionId,
1279
+ sinceMessageMs: Date.now() - messageSentAt
1280
+ });
1229
1281
  getCallbacks()?.onResult?.(result);
1230
1282
  this.resolveMessageCompletion(sessionId);
1231
1283
  });
@@ -1249,6 +1301,7 @@ var SessionProcessManager = class {
1249
1301
  log$25.error("无法发送消息:进程不存在或 stdin 不可用", { sessionId });
1250
1302
  return;
1251
1303
  }
1304
+ proc._resetPerfTimer?.();
1252
1305
  const userMessageId = v4();
1253
1306
  this.lastUserMessageIds.set(sessionId, userMessageId);
1254
1307
  const payload = JSON.stringify({
@@ -4565,6 +4618,7 @@ function sortTraceValue(value) {
4565
4618
  //#endregion
4566
4619
  //#region src/card/cc-stream-bridge.ts
4567
4620
  const log$18 = larkLogger("card/cc-stream-bridge");
4621
+ const CC_INTERNAL_PLACEHOLDER = "No response requested.";
4568
4622
  /**
4569
4623
  * CCStreamBridge — 将 CC 流事件桥接到 StreamingCardController
4570
4624
  *
@@ -4683,6 +4737,15 @@ var CCStreamBridge = class {
4683
4737
  accumulatedThinkingTextLen: this.accumulatedThinkingText.length
4684
4738
  });
4685
4739
  if (this.options.autoCompleteOnTurnEnd && stopReason === "end_turn") {
4740
+ const trimmedText = this.accumulatedText.trim();
4741
+ if (trimmedText === CC_INTERNAL_PLACEHOLDER || trimmedText === "") {
4742
+ log$18.info("检测到 CC 内部占位消息,静默丢弃", {
4743
+ text: trimmedText.slice(0, 50),
4744
+ sessionKey: this.options.sessionKey
4745
+ });
4746
+ this.controller.abortCard();
4747
+ return;
4748
+ }
4686
4749
  this.controller.markFullyComplete();
4687
4750
  this.controller.onIdle();
4688
4751
  }
@@ -8711,7 +8774,8 @@ async function dispatchToCC(params) {
8711
8774
  const isGroup = chatType === "group";
8712
8775
  const replyInThread = !!threadId;
8713
8776
  const replyToMessageId = threadId ? void 0 : ctx.messageId;
8714
- const textPrompt = formatForCC(ctx, isGroup);
8777
+ let textPrompt = formatForCC(ctx, isGroup);
8778
+ if (isGroup) textPrompt = `[直接@你的消息,请正常回复] ${textPrompt}`;
8715
8779
  const imageResources = ctx.resources.filter((r) => r.type === "image");
8716
8780
  let prompt = textPrompt;
8717
8781
  if (imageResources.length > 0) {
@@ -8957,6 +9021,9 @@ async function dispatchToCC(params) {
8957
9021
  chatId,
8958
9022
  replyToMessageId: cardDeps.replyToMessageId
8959
9023
  });
9024
+ cardController.ensureCardCreated().catch((err) => {
9025
+ log$11.warn("提前创建卡片失败(streaming 回调会重试)", { error: String(err) });
9026
+ });
8960
9027
  const bridge = new CCStreamBridge(cardController, {
8961
9028
  autoCompleteOnTurnEnd: true,
8962
9029
  sessionKey: route.sessionId
@@ -9083,7 +9150,8 @@ async function dispatchTeammateEval(params) {
9083
9150
  const teammateSessionKey = `teammate_${route.sessionId}`;
9084
9151
  startToolUseTraceRun(teammateSessionKey);
9085
9152
  const NO_REPLY_TOKEN = SILENT_REPLY_TOKEN;
9086
- const PREFIX_LEN = NO_REPLY_TOKEN.length;
9153
+ const CC_INTERNAL_PLACEHOLDER = "No response requested.";
9154
+ const PREFIX_LEN = 22;
9087
9155
  /** 缓冲的 thinking 内容 */
9088
9156
  let thinkingBuffer = "";
9089
9157
  /** text 前缀缓冲(用于判断是否为 NO_REPLY) */
@@ -9156,15 +9224,22 @@ async function dispatchTeammateEval(params) {
9156
9224
  }
9157
9225
  textPrefixBuffer += text;
9158
9226
  if (textPrefixBuffer.length >= PREFIX_LEN) {
9159
- if (textPrefixBuffer.trim() === NO_REPLY_TOKEN) {
9227
+ const trimmed = textPrefixBuffer.trim();
9228
+ if (trimmed === NO_REPLY_TOKEN || trimmed === CC_INTERNAL_PLACEHOLDER) {
9160
9229
  silenced = true;
9161
- log$11.info("teammate 前缀检测: NO_REPLY", { chatId });
9230
+ log$11.info("teammate 前缀检测: 静默", {
9231
+ chatId,
9232
+ trimmed: trimmed.slice(0, 30)
9233
+ });
9162
9234
  return;
9163
9235
  }
9164
9236
  confirmReply();
9165
9237
  return;
9166
9238
  }
9167
- if (!NO_REPLY_TOKEN.startsWith(textPrefixBuffer.trim())) confirmReply();
9239
+ const currentTrimmed = textPrefixBuffer.trim();
9240
+ const couldBeNoReply = NO_REPLY_TOKEN.startsWith(currentTrimmed);
9241
+ const couldBePlaceholder = CC_INTERNAL_PLACEHOLDER.startsWith(currentTrimmed);
9242
+ if (!couldBeNoReply && !couldBePlaceholder) confirmReply();
9168
9243
  };
9169
9244
  try {
9170
9245
  await new Promise((resolve) => {
@@ -9211,11 +9286,11 @@ async function dispatchTeammateEval(params) {
9211
9286
  }
9212
9287
  if (!confirmed && !silenced) {
9213
9288
  const trimmed = textPrefixBuffer.trim();
9214
- if (trimmed === NO_REPLY_TOKEN || trimmed === "") {
9289
+ if (trimmed === NO_REPLY_TOKEN || trimmed === CC_INTERNAL_PLACEHOLDER || trimmed === "") {
9215
9290
  silenced = true;
9216
- log$11.info("teammate turnEnd 最终判断: NO_REPLY", {
9291
+ log$11.info("teammate turnEnd 最终判断: 静默", {
9217
9292
  chatId,
9218
- trimmed
9293
+ trimmed: trimmed.slice(0, 30)
9219
9294
  });
9220
9295
  } else {
9221
9296
  confirmReply();
@@ -11926,9 +12001,10 @@ var TeammateBuffer = class {
11926
12001
  */
11927
12002
  buildEvalPrompt(messages) {
11928
12003
  const header = [
11929
- "[系统提示] 以下是群聊中最近的对话,你作为团队成员正在旁听。",
11930
- "请判断是否需要参与讨论。如果不需要,只输出 NO_REPLY。",
12004
+ "[旁听评估任务] 以下是群聊中最近的对话,你作为团队成员正在旁听。",
12005
+ "请判断是否需要主动参与讨论。如果不需要,只输出 NO_REPLY。",
11931
12006
  "如果需要参与,直接输出你的回复内容。",
12007
+ "注意:这是一次旁听评估,仅在本次评估中适用 NO_REPLY 规则。后续如果用户直接 @你 则必须正常回复。",
11932
12008
  "",
11933
12009
  "---"
11934
12010
  ].join("\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-lark/larkpal",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "LarkPal - Lark/Feishu bot service",
5
5
  "type": "module",
6
6
  "main": "./dist/main.mjs",