@vibe-lark/larkpal 0.1.59 → 0.1.61

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 +147 -39
  2. package/package.json +2 -2
package/dist/main.mjs CHANGED
@@ -3421,6 +3421,7 @@ function summarizeString(value) {
3421
3421
  */
3422
3422
  const log$29 = larkLogger("chat/stream");
3423
3423
  const chatRunsBySession = /* @__PURE__ */ new Map();
3424
+ const DEFAULT_STALE_RUN_IDLE_MS = 1800 * 1e3;
3424
3425
  /** 统一写 SSE 格式数据到响应流 */
3425
3426
  function sendSSE(res, event, data) {
3426
3427
  const payload = typeof data === "string" ? data : JSON.stringify(data);
@@ -3444,6 +3445,7 @@ function getActiveChatRun(sessionId) {
3444
3445
  return run?.status === "running" ? run : void 0;
3445
3446
  }
3446
3447
  function serializeChatRun(run) {
3448
+ const idleSeconds = Math.max(0, Math.floor((Date.now() - run.lastEventAt) / 1e3));
3447
3449
  return {
3448
3450
  runId: run.runId,
3449
3451
  sessionId: run.sessionId,
@@ -3456,9 +3458,23 @@ function serializeChatRun(run) {
3456
3458
  finalStatus: run.finalStatus,
3457
3459
  error: run.error,
3458
3460
  textLength: run.text.length,
3459
- finalText: run.status === "running" ? void 0 : run.text || void 0
3461
+ finalText: run.status === "running" ? void 0 : run.text || void 0,
3462
+ lastEventAt: run.lastEventAt,
3463
+ lastEventType: run.lastEventType,
3464
+ lastToolName: run.lastToolName,
3465
+ idleSeconds,
3466
+ adapterBusy: run.status === "running" ? run.adapterBusy : false,
3467
+ staleReason: run.staleReason
3460
3468
  };
3461
3469
  }
3470
+ function getRuntimeEventDedupeKey(event) {
3471
+ const record = event;
3472
+ const eventId = record.eventId;
3473
+ if (typeof eventId === "string" && eventId.length > 0) return eventId;
3474
+ const runId = typeof record.runId === "string" ? record.runId : void 0;
3475
+ const seq = typeof record.seq === "number" || typeof record.seq === "string" ? String(record.seq) : void 0;
3476
+ return runId && seq ? `${runId}:${seq}` : void 0;
3477
+ }
3462
3478
  function isRunVisibleToUser(run, params) {
3463
3479
  return run.tenantKey === params.tenantKey && run.openId === params.openId;
3464
3480
  }
@@ -3475,6 +3491,17 @@ function addRunClient(req, res, run) {
3475
3491
  function sendRunStatus(res, run) {
3476
3492
  sendSSE(res, "run-status", serializeChatRun(run));
3477
3493
  }
3494
+ function getStaleRunIdleMs() {
3495
+ const raw = process.env.LARKPAL_CHAT_RUN_STALE_IDLE_MS;
3496
+ if (!raw) return DEFAULT_STALE_RUN_IDLE_MS;
3497
+ const parsed = Number.parseInt(raw, 10);
3498
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_STALE_RUN_IDLE_MS;
3499
+ }
3500
+ function recordRunEvent(run, eventType, params) {
3501
+ run.lastEventAt = Date.now();
3502
+ run.lastEventType = eventType;
3503
+ if (params?.toolName) run.lastToolName = params.toolName;
3504
+ }
3478
3505
  function extractRuntimeEventFinalText(event) {
3479
3506
  const direct = event.finalText;
3480
3507
  if (typeof direct === "string" && direct.trim()) return direct;
@@ -3522,6 +3549,72 @@ function queueRunMessage(run, messageStore, message, label) {
3522
3549
  });
3523
3550
  return run.persistQueue;
3524
3551
  }
3552
+ function appendRunStatusMessage(run, messageStore) {
3553
+ return queueRunMessage(run, messageStore, {
3554
+ sessionId: run.sessionId,
3555
+ role: "tool",
3556
+ content: JSON.stringify({
3557
+ runtimeEventType: "run-status",
3558
+ run: serializeChatRun(run)
3559
+ }),
3560
+ channel: "web",
3561
+ metadata: {
3562
+ runId: run.runId,
3563
+ requestId: run.requestId,
3564
+ traceId: run.traceId,
3565
+ scenarioId: run.scenarioId,
3566
+ runtimeEventType: "run-status",
3567
+ finalStatus: run.finalStatus,
3568
+ errorMessage: run.error,
3569
+ staleReason: run.staleReason
3570
+ }
3571
+ }, "[stream] 保存 run status 消息失败");
3572
+ }
3573
+ async function markRunTerminal(run, messageStore, status, params) {
3574
+ if (run.status !== "running") return false;
3575
+ run.status = status;
3576
+ run.completedAt = Date.now();
3577
+ run.finalStatus = params.finalStatus;
3578
+ run.error = params.error;
3579
+ run.staleReason = params.staleReason;
3580
+ run.adapterBusy = false;
3581
+ recordRunEvent(run, `run-${status}`);
3582
+ broadcastRunStatus(run);
3583
+ await appendRunStatusMessage(run, messageStore);
3584
+ return true;
3585
+ }
3586
+ async function reconcileRunWithAdapter(run, messageStore, processManager) {
3587
+ if (run.status !== "running") return;
3588
+ const processInfo = processManager.getProcessInfo(run.sessionId);
3589
+ const adapterBusy = processManager.isSessionBusy?.(run.sessionId) ?? processInfo?.status === "running";
3590
+ run.adapterBusy = adapterBusy;
3591
+ const idleMs = Date.now() - run.lastEventAt;
3592
+ let staleReason;
3593
+ if (!adapterBusy) staleReason = processInfo ? `runtime process is ${processInfo.status}` : "runtime adapter reports session is not busy";
3594
+ else if (idleMs >= getStaleRunIdleMs()) staleReason = `run idle for ${Math.floor(idleMs / 1e3)}s`;
3595
+ if (!staleReason) return;
3596
+ const error = `Chat run became stale: ${staleReason}`;
3597
+ log$29.warn("[stream] activeRun 与 runtime 状态不一致,标记为 stale", {
3598
+ sessionId: run.sessionId,
3599
+ runId: run.runId,
3600
+ staleReason,
3601
+ idleMs,
3602
+ adapterBusy,
3603
+ processStatus: processInfo?.status
3604
+ });
3605
+ if (await markRunTerminal(run, messageStore, "failed", {
3606
+ finalStatus: "stale",
3607
+ error,
3608
+ staleReason
3609
+ })) {
3610
+ broadcastRunEvent(run, "error", {
3611
+ runId: run.runId,
3612
+ message: error,
3613
+ staleReason
3614
+ });
3615
+ endRunClients(run);
3616
+ }
3617
+ }
3525
3618
  function buildRuntimeEventMessageMetadata(event, runtimeConfig) {
3526
3619
  return {
3527
3620
  runtimeEventType: event.type,
@@ -3663,37 +3756,10 @@ function createChatRouter(config) {
3663
3756
  text: "",
3664
3757
  clients: /* @__PURE__ */ new Set(),
3665
3758
  runtimeConfig,
3666
- persistQueue: Promise.resolve()
3667
- };
3668
- const appendRunStatusMessage = () => {
3669
- return queueRunMessage(run, messageStore, {
3670
- sessionId,
3671
- role: "tool",
3672
- content: JSON.stringify({
3673
- runtimeEventType: "run-status",
3674
- run: serializeChatRun(run)
3675
- }),
3676
- channel: "web",
3677
- metadata: {
3678
- runId: run.runId,
3679
- requestId,
3680
- traceId,
3681
- scenarioId,
3682
- runtimeEventType: "run-status",
3683
- finalStatus: run.finalStatus,
3684
- errorMessage: run.error
3685
- }
3686
- }, "[stream] 保存 run status 消息失败");
3687
- };
3688
- const markRunTerminal = async (status, params) => {
3689
- if (run.status !== "running") return false;
3690
- run.status = status;
3691
- run.completedAt = Date.now();
3692
- run.finalStatus = params.finalStatus;
3693
- run.error = params.error;
3694
- broadcastRunStatus(run);
3695
- await appendRunStatusMessage();
3696
- return true;
3759
+ persistQueue: Promise.resolve(),
3760
+ seenRuntimeEventIds: /* @__PURE__ */ new Set(),
3761
+ lastEventAt: Date.now(),
3762
+ lastEventType: "run-started"
3697
3763
  };
3698
3764
  try {
3699
3765
  await messageStore.appendMessage({
@@ -3722,7 +3788,7 @@ function createChatRouter(config) {
3722
3788
  return;
3723
3789
  }
3724
3790
  chatRunsBySession.set(sessionId, run);
3725
- appendRunStatusMessage();
3791
+ appendRunStatusMessage(run, messageStore);
3726
3792
  prepareSSE(res);
3727
3793
  addRunClient(req, res, run);
3728
3794
  sendSSE(res, "session", { sessionId });
@@ -3736,6 +3802,7 @@ function createChatRouter(config) {
3736
3802
  }, 15e3);
3737
3803
  const handleBlockedEvent = (event) => {
3738
3804
  const sanitized = sanitizeBlockedEvent(event);
3805
+ recordRunEvent(run, "blocked", { toolName: sanitized.name });
3739
3806
  log$29.warn("[stream] Agent blocked event", { ...sanitized });
3740
3807
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, sanitized.type, {
3741
3808
  toolName: sanitized.name,
@@ -3766,7 +3833,21 @@ function createChatRouter(config) {
3766
3833
  broadcastRunEvent(run, "blocked", { event: sanitized });
3767
3834
  };
3768
3835
  const handleRuntimeEvent = (event) => {
3836
+ const dedupeKey = getRuntimeEventDedupeKey(event);
3837
+ if (dedupeKey) {
3838
+ if (run.seenRuntimeEventIds.has(dedupeKey)) {
3839
+ log$29.debug("[stream] 跳过重复 runtime event", {
3840
+ sessionId,
3841
+ runId: run.runId,
3842
+ eventId: dedupeKey,
3843
+ eventType: event.type
3844
+ });
3845
+ return;
3846
+ }
3847
+ run.seenRuntimeEventIds.add(dedupeKey);
3848
+ }
3769
3849
  const eventSummary = summarizeForAudit(event);
3850
+ recordRunEvent(run, event.type, { toolName: event.capabilityId });
3770
3851
  const runtimeFinalText = extractRuntimeEventFinalText(event);
3771
3852
  if (runtimeFinalText && !run.text.trim()) run.text = runtimeFinalText;
3772
3853
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "runtime_event", {
@@ -3793,13 +3874,16 @@ function createChatRouter(config) {
3793
3874
  };
3794
3875
  const callbacks = {
3795
3876
  onTextDelta: (text) => {
3877
+ recordRunEvent(run, "text-delta");
3796
3878
  run.text += text;
3797
3879
  broadcastRunEvent(run, "text-delta", { text });
3798
3880
  },
3799
3881
  onThinkingDelta: (text) => {
3882
+ recordRunEvent(run, "thinking-delta");
3800
3883
  broadcastRunEvent(run, "thinking-delta", { text });
3801
3884
  },
3802
3885
  onToolUseStart: (toolName, toolInput) => {
3886
+ recordRunEvent(run, "tool-use-start", { toolName });
3803
3887
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_started", {
3804
3888
  toolName,
3805
3889
  inputSummary: summarizeForAudit(toolInput)
@@ -3827,6 +3911,7 @@ function createChatRouter(config) {
3827
3911
  });
3828
3912
  },
3829
3913
  onToolResult: (toolUseId, result) => {
3914
+ recordRunEvent(run, "tool-result");
3830
3915
  const resultSummary = summarizeForAudit(result);
3831
3916
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_finished", {
3832
3917
  toolCallId: toolUseId,
@@ -3857,19 +3942,22 @@ function createChatRouter(config) {
3857
3942
  });
3858
3943
  },
3859
3944
  onToolProgress: (toolName, elapsedSeconds) => {
3945
+ recordRunEvent(run, "tool-progress", { toolName });
3860
3946
  broadcastRunEvent(run, "tool-progress", {
3861
3947
  toolName,
3862
3948
  elapsedSeconds
3863
3949
  });
3864
3950
  },
3865
3951
  onTurnEnd: (stopReason) => {
3952
+ recordRunEvent(run, "turn-end");
3866
3953
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "turn_finished", { metadata: { stopReason } }));
3867
3954
  broadcastRunEvent(run, "turn-end", { stopReason });
3868
3955
  },
3869
3956
  onResult: async (result) => {
3957
+ recordRunEvent(run, "result");
3870
3958
  const finalText = run.text.trim() ? run.text : result.result || "";
3871
3959
  run.text = finalText;
3872
- await markRunTerminal(result.isError ? "failed" : "completed", { finalStatus: result.subtype });
3960
+ await markRunTerminal(run, messageStore, result.isError ? "failed" : "completed", { finalStatus: result.subtype });
3873
3961
  await queueRunMessage(run, messageStore, {
3874
3962
  sessionId,
3875
3963
  role: "assistant",
@@ -3920,6 +4008,7 @@ function createChatRouter(config) {
3920
4008
  }));
3921
4009
  },
3922
4010
  onError: async (error) => {
4011
+ recordRunEvent(run, "error");
3923
4012
  log$29.error("[stream] Runtime 执行出错", {
3924
4013
  sessionId,
3925
4014
  runId: run.runId,
@@ -3949,7 +4038,7 @@ function createChatRouter(config) {
3949
4038
  requestId
3950
4039
  }
3951
4040
  }));
3952
- await markRunTerminal("failed", {
4041
+ await markRunTerminal(run, messageStore, "failed", {
3953
4042
  finalStatus: "error",
3954
4043
  error: error.message
3955
4044
  });
@@ -3968,6 +4057,7 @@ function createChatRouter(config) {
3968
4057
  });
3969
4058
  },
3970
4059
  onVerifierResult: (result) => {
4060
+ recordRunEvent(run, "verifier-result");
3971
4061
  const resultSummary = summarizeForAudit(result);
3972
4062
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "verifier_result", {
3973
4063
  resultSummary,
@@ -4019,7 +4109,7 @@ function createChatRouter(config) {
4019
4109
  sessionId,
4020
4110
  runId: run.runId
4021
4111
  });
4022
- await markRunTerminal("failed", {
4112
+ await markRunTerminal(run, messageStore, "failed", {
4023
4113
  finalStatus: "error",
4024
4114
  error: errorMessage
4025
4115
  });
@@ -4065,6 +4155,7 @@ function createChatRouter(config) {
4065
4155
  tenantKey: chatUser.tenantKey,
4066
4156
  openId: chatUser.openId
4067
4157
  }) ? run : void 0;
4158
+ if (visibleRun) await reconcileRunWithAdapter(visibleRun, messageStore, processManager);
4068
4159
  res.json({
4069
4160
  messages: result.messages,
4070
4161
  nextCursor: result.nextCursor,
@@ -4100,6 +4191,7 @@ function createChatRouter(config) {
4100
4191
  });
4101
4192
  return;
4102
4193
  }
4194
+ await reconcileRunWithAdapter(run, messageStore, processManager);
4103
4195
  const serialized = serializeChatRun(run);
4104
4196
  res.json({
4105
4197
  sessionId,
@@ -6569,6 +6661,7 @@ const PERSONA_FILE = join(homedir(), ".claude", "CLAUDE.md");
6569
6661
  function createStatusRouter(config) {
6570
6662
  const router = express.Router();
6571
6663
  const sessionsDir = getWorkspaceChatsRoot(config?.workspaceRoot ?? resolveWorkspaceRoot("/workspace"));
6664
+ const processManager = config?.processManager;
6572
6665
  router.get("/sessions", async (req, res) => {
6573
6666
  logger$2.info("list sessions request", {
6574
6667
  method: req.method,
@@ -6669,14 +6762,18 @@ function createStatusRouter(config) {
6669
6762
  try {
6670
6763
  const uptimeSeconds = process.uptime();
6671
6764
  const memory = process.memoryUsage();
6765
+ const processes = processManager?.getAllProcessInfo().map((processInfo) => {
6766
+ return serializeRuntimeProcess(processInfo, processManager);
6767
+ }) ?? [];
6672
6768
  const statusData = {
6673
6769
  uptime: uptimeSeconds,
6674
- processes: [],
6770
+ processes,
6675
6771
  memory
6676
6772
  };
6677
6773
  logger$2.info("get status response", {
6678
6774
  uptime: uptimeSeconds,
6679
- memoryRss: memory.rss
6775
+ memoryRss: memory.rss,
6776
+ processCount: processes.length
6680
6777
  });
6681
6778
  res.json(statusData);
6682
6779
  } catch (err) {
@@ -6720,6 +6817,14 @@ function createStatusRouter(config) {
6720
6817
  });
6721
6818
  return router;
6722
6819
  }
6820
+ function serializeRuntimeProcess(processInfo, processManager) {
6821
+ return {
6822
+ ...processInfo,
6823
+ startedAt: processInfo.startedAt.toISOString(),
6824
+ lastActiveAt: processInfo.lastActiveAt.toISOString(),
6825
+ busy: processManager.isSessionBusy?.(processInfo.sessionId) ?? processInfo.status === "running"
6826
+ };
6827
+ }
6723
6828
  //#endregion
6724
6829
  //#region src/gateway/server.ts
6725
6830
  const logger$1 = larkLogger("gateway");
@@ -6783,7 +6888,10 @@ function registerRoutes(app, processManager, scheduledTaskManager, appCredential
6783
6888
  app.put("/api/scheduled-tasks/:id", notImplemented);
6784
6889
  app.delete("/api/scheduled-tasks/:id", notImplemented);
6785
6890
  }
6786
- app.use("/api", createStatusRouter({ workspaceRoot }));
6891
+ app.use("/api", createStatusRouter({
6892
+ workspaceRoot,
6893
+ processManager
6894
+ }));
6787
6895
  app.use("/hooks", createHooksRouter());
6788
6896
  app.use("/api/mcp", createMcpCallbackRouter());
6789
6897
  app.use(createChatAuthRouter());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-lark/larkpal",
3
- "version": "0.1.59",
3
+ "version": "0.1.61",
4
4
  "description": "LarkPal - Lark/Feishu bot service",
5
5
  "type": "module",
6
6
  "main": "./dist/main.mjs",
@@ -50,7 +50,7 @@
50
50
  "zod": "^4.3.6"
51
51
  },
52
52
  "optionalDependencies": {
53
- "@vibe-lark/larkpal-agent": "^0.2.8"
53
+ "@vibe-lark/larkpal-agent": "^0.2.12"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@eslint/js": "^10.0.1",