@vibe-lark/larkpal 0.1.56 → 0.1.57

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 +367 -118
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -3637,18 +3637,93 @@ function summarizeString(value) {
3637
3637
  * DELETE /api/chat/sessions/:id — 删除会话
3638
3638
  */
3639
3639
  const log$28 = larkLogger("chat/stream");
3640
- const activeStreamSessions = /* @__PURE__ */ new Set();
3640
+ const chatRunsBySession = /* @__PURE__ */ new Map();
3641
3641
  /** 统一写 SSE 格式数据到响应流 */
3642
3642
  function sendSSE(res, event, data) {
3643
3643
  const payload = typeof data === "string" ? data : JSON.stringify(data);
3644
3644
  res.write(`event: ${event}\ndata: ${payload}\n\n`);
3645
3645
  }
3646
3646
  function isChatSessionRunning(sessionId, processManager) {
3647
- return activeStreamSessions.has(sessionId) || (processManager.isSessionBusy?.(sessionId) ?? false);
3647
+ return getActiveChatRun(sessionId) != null || (processManager.isSessionBusy?.(sessionId) ?? false);
3648
3648
  }
3649
3649
  function logChatAudit(event) {
3650
3650
  log$28.info("[stream] Agent audit event", { ...event });
3651
3651
  }
3652
+ function prepareSSE(res) {
3653
+ res.setHeader("Content-Type", "text/event-stream");
3654
+ res.setHeader("Cache-Control", "no-cache");
3655
+ res.setHeader("Connection", "keep-alive");
3656
+ res.setHeader("X-Accel-Buffering", "no");
3657
+ res.flushHeaders();
3658
+ }
3659
+ function getActiveChatRun(sessionId) {
3660
+ const run = chatRunsBySession.get(sessionId);
3661
+ return run?.status === "running" ? run : void 0;
3662
+ }
3663
+ function serializeChatRun(run) {
3664
+ return {
3665
+ runId: run.runId,
3666
+ sessionId: run.sessionId,
3667
+ requestId: run.requestId,
3668
+ traceId: run.traceId,
3669
+ scenarioId: run.scenarioId,
3670
+ status: run.status,
3671
+ startedAt: run.startedAt,
3672
+ completedAt: run.completedAt,
3673
+ finalStatus: run.finalStatus,
3674
+ error: run.error,
3675
+ textLength: run.text.length
3676
+ };
3677
+ }
3678
+ function isRunVisibleToUser(run, params) {
3679
+ return run.tenantKey === params.tenantKey && run.openId === params.openId;
3680
+ }
3681
+ function addRunClient(req, res, run) {
3682
+ run.clients.add(res);
3683
+ req.on("close", () => {
3684
+ run.clients.delete(res);
3685
+ log$28.info("[stream] 客户端断开连接", {
3686
+ sessionId: run.sessionId,
3687
+ runId: run.runId
3688
+ });
3689
+ });
3690
+ }
3691
+ function sendRunStatus(res, run) {
3692
+ sendSSE(res, "run-status", serializeChatRun(run));
3693
+ }
3694
+ function broadcastRunEvent(run, event, data) {
3695
+ for (const client of Array.from(run.clients)) try {
3696
+ sendSSE(client, event, data);
3697
+ } catch (err) {
3698
+ run.clients.delete(client);
3699
+ log$28.warn("[stream] SSE 写入失败,移除客户端", {
3700
+ sessionId: run.sessionId,
3701
+ runId: run.runId,
3702
+ error: err instanceof Error ? err.message : String(err)
3703
+ });
3704
+ }
3705
+ }
3706
+ function broadcastRunStatus(run) {
3707
+ broadcastRunEvent(run, "run-status", serializeChatRun(run));
3708
+ }
3709
+ function endRunClients(run) {
3710
+ for (const client of Array.from(run.clients)) try {
3711
+ client.end();
3712
+ } catch {}
3713
+ run.clients.clear();
3714
+ }
3715
+ function queueRunMessage(run, messageStore, message, label) {
3716
+ run.persistQueue = run.persistQueue.catch(() => void 0).then(async () => {
3717
+ await messageStore.appendMessage(message);
3718
+ }).catch((err) => {
3719
+ log$28.error(label, {
3720
+ sessionId: run.sessionId,
3721
+ runId: run.runId,
3722
+ error: err instanceof Error ? err.message : String(err)
3723
+ });
3724
+ });
3725
+ return run.persistQueue;
3726
+ }
3652
3727
  function buildRuntimeEventMessageMetadata(event, runtimeConfig) {
3653
3728
  return {
3654
3729
  runtimeEventType: event.type,
@@ -3686,22 +3761,42 @@ function createChatRouter(config) {
3686
3761
  router.post("/api/chat/stream", chatAuthMiddleware, async (req, res) => {
3687
3762
  const { userId, openId, tenantKey } = res.locals.chatUser;
3688
3763
  const body = req.body;
3689
- if (!body.prompt || typeof body.prompt !== "string") {
3690
- res.status(400).json({ error: "prompt is required and must be a string" });
3691
- return;
3692
- }
3693
3764
  if (!tenantKey || !userId || !openId) {
3694
3765
  res.status(401).json({ error: "Missing required chat identity: tenantKey, userId, openId" });
3695
3766
  return;
3696
3767
  }
3697
3768
  const requestId = getGatewayRequestId(req.headers);
3698
3769
  const traceId = getGatewayTraceId(req.headers, requestId);
3770
+ let sessionId = body.session_id;
3771
+ const activeRun = sessionId ? getActiveChatRun(sessionId) : void 0;
3772
+ if (activeRun) {
3773
+ if (!isRunVisibleToUser(activeRun, {
3774
+ tenantKey,
3775
+ openId
3776
+ })) {
3777
+ res.status(403).json({ error: "Forbidden" });
3778
+ return;
3779
+ }
3780
+ prepareSSE(res);
3781
+ addRunClient(req, res, activeRun);
3782
+ sendSSE(res, "session", { sessionId });
3783
+ sendRunStatus(res, activeRun);
3784
+ log$28.info("[stream] 已重新连接到运行中的 run", {
3785
+ sessionId,
3786
+ runId: activeRun.runId
3787
+ });
3788
+ return;
3789
+ }
3790
+ if (!body.prompt || typeof body.prompt !== "string") {
3791
+ res.status(400).json({ error: "prompt is required and must be a string" });
3792
+ return;
3793
+ }
3699
3794
  const scenarioId = getScenarioId(body.options?.scenarioId, body.options?.metadata);
3700
3795
  const attachments = getRunInputValue(body.attachments, body.options?.attachments, body.options?.metadata?.attachments);
3701
3796
  const sourceArtifacts = getRunInputValue(body.sourceArtifacts, body.source_artifacts, body.options?.sourceArtifacts, body.options?.source_artifacts, body.options?.metadata?.sourceArtifacts, body.options?.metadata?.source_artifacts);
3702
3797
  log$28.info("[stream] 收到流式对话请求", {
3703
3798
  userId,
3704
- sessionId: body.session_id,
3799
+ sessionId,
3705
3800
  requestId,
3706
3801
  traceId,
3707
3802
  scenarioId,
@@ -3709,7 +3804,6 @@ function createChatRouter(config) {
3709
3804
  hasSourceArtifacts: sourceArtifacts !== void 0,
3710
3805
  promptLength: body.prompt.length
3711
3806
  });
3712
- let sessionId = body.session_id;
3713
3807
  if (!sessionId) {
3714
3808
  sessionId = (await messageStore.createSession({
3715
3809
  id: `client_${openId}_${Date.now().toString(36)}`,
@@ -3723,22 +3817,85 @@ function createChatRouter(config) {
3723
3817
  });
3724
3818
  }
3725
3819
  if (isChatSessionRunning(sessionId, processManager)) {
3726
- log$28.warn("[stream] session 正在运行,拒绝并发请求", {
3820
+ log$28.warn("[stream] session 正在被非 Web Chat run 占用", {
3727
3821
  tenantKey,
3728
3822
  userId,
3729
3823
  sessionId
3730
3824
  });
3731
- res.setHeader("Content-Type", "text/event-stream");
3732
- res.setHeader("Cache-Control", "no-cache");
3733
- res.setHeader("Connection", "keep-alive");
3734
- res.setHeader("X-Accel-Buffering", "no");
3735
- res.flushHeaders();
3825
+ prepareSSE(res);
3736
3826
  sendSSE(res, "session", { sessionId });
3737
3827
  sendSSE(res, "error", { message: `Session ${sessionId} is already running` });
3738
3828
  res.end();
3739
3829
  return;
3740
3830
  }
3741
- activeStreamSessions.add(sessionId);
3831
+ const runtimeConfig = buildAgentRuntimeConfig({
3832
+ requestId,
3833
+ traceId,
3834
+ sessionId,
3835
+ conversationId: sessionId,
3836
+ entrypoint: "chat",
3837
+ scenarioId,
3838
+ prompt: body.prompt,
3839
+ maxTurns: body.options?.maxTurns,
3840
+ maxBudgetUsd: body.options?.maxBudgetUsd,
3841
+ model: process.env.CLAUDE_MODEL || void 0,
3842
+ attachments,
3843
+ sourceArtifacts,
3844
+ policy: body.options?.policy,
3845
+ metadata: body.options?.metadata,
3846
+ identity: {
3847
+ tenantKey,
3848
+ userId,
3849
+ openId,
3850
+ userName: userId
3851
+ }
3852
+ });
3853
+ const run = {
3854
+ runId: requestId,
3855
+ sessionId,
3856
+ requestId,
3857
+ traceId,
3858
+ scenarioId,
3859
+ tenantKey,
3860
+ userId,
3861
+ openId,
3862
+ status: "running",
3863
+ startedAt: Date.now(),
3864
+ text: "",
3865
+ clients: /* @__PURE__ */ new Set(),
3866
+ runtimeConfig,
3867
+ persistQueue: Promise.resolve()
3868
+ };
3869
+ const appendRunStatusMessage = () => {
3870
+ return queueRunMessage(run, messageStore, {
3871
+ sessionId,
3872
+ role: "tool",
3873
+ content: JSON.stringify({
3874
+ runtimeEventType: "run-status",
3875
+ run: serializeChatRun(run)
3876
+ }),
3877
+ channel: "web",
3878
+ metadata: {
3879
+ runId: run.runId,
3880
+ requestId,
3881
+ traceId,
3882
+ scenarioId,
3883
+ runtimeEventType: "run-status",
3884
+ finalStatus: run.finalStatus,
3885
+ errorMessage: run.error
3886
+ }
3887
+ }, "[stream] 保存 run status 消息失败");
3888
+ };
3889
+ const markRunTerminal = async (status, params) => {
3890
+ if (run.status !== "running") return false;
3891
+ run.status = status;
3892
+ run.completedAt = Date.now();
3893
+ run.finalStatus = params.finalStatus;
3894
+ run.error = params.error;
3895
+ broadcastRunStatus(run);
3896
+ await appendRunStatusMessage();
3897
+ return true;
3898
+ };
3742
3899
  try {
3743
3900
  await messageStore.appendMessage({
3744
3901
  sessionId,
@@ -3746,6 +3903,7 @@ function createChatRouter(config) {
3746
3903
  content: body.prompt,
3747
3904
  channel: "web",
3748
3905
  metadata: {
3906
+ runId: run.runId,
3749
3907
  requestId,
3750
3908
  traceId,
3751
3909
  scenarioId,
@@ -3756,7 +3914,6 @@ function createChatRouter(config) {
3756
3914
  }
3757
3915
  });
3758
3916
  } catch (err) {
3759
- activeStreamSessions.delete(sessionId);
3760
3917
  const errorMessage = err instanceof Error ? err.message : String(err);
3761
3918
  log$28.error("[stream] 保存用户消息失败", {
3762
3919
  sessionId,
@@ -3765,34 +3922,12 @@ function createChatRouter(config) {
3765
3922
  res.status(500).json({ error: "Failed to append user message" });
3766
3923
  return;
3767
3924
  }
3768
- res.setHeader("Content-Type", "text/event-stream");
3769
- res.setHeader("Cache-Control", "no-cache");
3770
- res.setHeader("Connection", "keep-alive");
3771
- res.setHeader("X-Accel-Buffering", "no");
3772
- res.flushHeaders();
3925
+ chatRunsBySession.set(sessionId, run);
3926
+ appendRunStatusMessage();
3927
+ prepareSSE(res);
3928
+ addRunClient(req, res, run);
3773
3929
  sendSSE(res, "session", { sessionId });
3774
- const runtimeConfig = buildAgentRuntimeConfig({
3775
- requestId,
3776
- traceId,
3777
- sessionId,
3778
- conversationId: sessionId,
3779
- entrypoint: "chat",
3780
- scenarioId,
3781
- prompt: body.prompt,
3782
- maxTurns: body.options?.maxTurns,
3783
- maxBudgetUsd: body.options?.maxBudgetUsd,
3784
- model: process.env.CLAUDE_MODEL || void 0,
3785
- attachments,
3786
- sourceArtifacts,
3787
- policy: body.options?.policy,
3788
- metadata: body.options?.metadata,
3789
- identity: {
3790
- tenantKey,
3791
- userId,
3792
- openId,
3793
- userName: userId
3794
- }
3795
- });
3930
+ sendRunStatus(res, run);
3796
3931
  const handleBlockedEvent = (event) => {
3797
3932
  const sanitized = sanitizeBlockedEvent(event);
3798
3933
  log$28.warn("[stream] Agent blocked event", { ...sanitized });
@@ -3802,24 +3937,35 @@ function createChatRouter(config) {
3802
3937
  errorCode: sanitized.reasonCode,
3803
3938
  errorMessage: sanitized.reason,
3804
3939
  metadata: {
3940
+ runId: run.runId,
3805
3941
  traceId,
3806
3942
  reasonCode: sanitized.reasonCode
3807
3943
  }
3808
3944
  }));
3945
+ queueRunMessage(run, messageStore, {
3946
+ sessionId,
3947
+ role: "tool",
3948
+ content: JSON.stringify({ blocked: sanitized }),
3949
+ channel: "web",
3950
+ metadata: {
3951
+ runId: run.runId,
3952
+ requestId,
3953
+ traceId,
3954
+ scenarioId,
3955
+ runtimeEventType: "blocked",
3956
+ toolName: sanitized.name,
3957
+ reasonCode: sanitized.reasonCode
3958
+ }
3959
+ }, "[stream] 保存 blocked 消息失败");
3960
+ broadcastRunEvent(run, "blocked", { event: sanitized });
3809
3961
  };
3810
- let accumText = "";
3811
- let clientDisconnected = false;
3812
- req.on("close", () => {
3813
- clientDisconnected = true;
3814
- log$28.info("[stream] 客户端断开连接", { sessionId });
3815
- });
3816
3962
  const handleRuntimeEvent = (event) => {
3817
3963
  const eventSummary = summarizeForAudit(event);
3818
3964
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "runtime_event", {
3819
3965
  resultSummary: eventSummary,
3820
3966
  metadata: buildRuntimeEventAuditMetadata(event)
3821
3967
  }));
3822
- messageStore.appendMessage({
3968
+ queueRunMessage(run, messageStore, {
3823
3969
  sessionId,
3824
3970
  role: "tool",
3825
3971
  content: JSON.stringify({
@@ -3827,47 +3973,47 @@ function createChatRouter(config) {
3827
3973
  event: eventSummary
3828
3974
  }),
3829
3975
  channel: "web",
3830
- metadata: buildRuntimeEventMessageMetadata(event, runtimeConfig)
3831
- }).catch((err) => {
3832
- log$28.error("[stream] 保存 runtime event 消息失败", {
3833
- sessionId,
3834
- error: err
3835
- });
3836
- });
3837
- if (!clientDisconnected) sendSSE(res, "runtime-event", {
3976
+ metadata: {
3977
+ ...buildRuntimeEventMessageMetadata(event, runtimeConfig),
3978
+ runId: run.runId
3979
+ }
3980
+ }, "[stream] 保存 runtime event 消息失败");
3981
+ broadcastRunEvent(run, "runtime-event", {
3838
3982
  type: event.type,
3839
3983
  event: eventSummary
3840
3984
  });
3841
3985
  };
3842
3986
  const callbacks = {
3843
3987
  onTextDelta: (text) => {
3844
- accumText += text;
3845
- if (!clientDisconnected) sendSSE(res, "text-delta", { text });
3988
+ run.text += text;
3989
+ broadcastRunEvent(run, "text-delta", { text });
3846
3990
  },
3847
3991
  onThinkingDelta: (text) => {
3848
- if (!clientDisconnected) sendSSE(res, "thinking-delta", { text });
3992
+ broadcastRunEvent(run, "thinking-delta", { text });
3849
3993
  },
3850
3994
  onToolUseStart: (toolName, toolInput) => {
3851
3995
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_started", {
3852
3996
  toolName,
3853
3997
  inputSummary: summarizeForAudit(toolInput)
3854
3998
  }));
3855
- messageStore.appendMessage({
3999
+ queueRunMessage(run, messageStore, {
3856
4000
  sessionId,
3857
4001
  role: "tool",
3858
4002
  content: JSON.stringify({
3859
4003
  toolName,
3860
- input: toolInput
4004
+ input: summarizeForAudit(toolInput)
3861
4005
  }),
3862
4006
  channel: "web",
3863
- metadata: { toolName }
3864
- }).catch((err) => {
3865
- log$28.error("[stream] 保存 tool 消息失败", {
3866
- sessionId,
3867
- error: err
3868
- });
3869
- });
3870
- if (!clientDisconnected) sendSSE(res, "tool-use-start", {
4007
+ metadata: {
4008
+ runId: run.runId,
4009
+ requestId,
4010
+ traceId,
4011
+ scenarioId,
4012
+ runtimeEventType: "tool-use-start",
4013
+ toolName
4014
+ }
4015
+ }, "[stream] 保存 tool 消息失败");
4016
+ broadcastRunEvent(run, "tool-use-start", {
3871
4017
  toolName,
3872
4018
  input: toolInput
3873
4019
  });
@@ -3879,62 +4025,80 @@ function createChatRouter(config) {
3879
4025
  resultSummary,
3880
4026
  metadata: buildToolResultAuditMetadata(result)
3881
4027
  }));
3882
- if (!clientDisconnected) sendSSE(res, "tool-result", {
4028
+ queueRunMessage(run, messageStore, {
4029
+ sessionId,
4030
+ role: "tool",
4031
+ content: JSON.stringify({
4032
+ toolUseId,
4033
+ result: resultSummary
4034
+ }),
4035
+ channel: "web",
4036
+ metadata: {
4037
+ runId: run.runId,
4038
+ requestId,
4039
+ traceId,
4040
+ scenarioId,
4041
+ runtimeEventType: "tool-result",
4042
+ toolCallId: toolUseId,
4043
+ toolResult: resultSummary
4044
+ }
4045
+ }, "[stream] 保存 tool result 消息失败");
4046
+ broadcastRunEvent(run, "tool-result", {
3883
4047
  toolUseId,
3884
4048
  result: resultSummary
3885
4049
  });
3886
4050
  },
3887
4051
  onToolProgress: (toolName, elapsedSeconds) => {
3888
- if (!clientDisconnected) sendSSE(res, "tool-progress", {
4052
+ broadcastRunEvent(run, "tool-progress", {
3889
4053
  toolName,
3890
4054
  elapsedSeconds
3891
4055
  });
3892
4056
  },
3893
4057
  onTurnEnd: (stopReason) => {
3894
4058
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "turn_finished", { metadata: { stopReason } }));
3895
- if (!clientDisconnected) sendSSE(res, "turn-end", { stopReason });
4059
+ broadcastRunEvent(run, "turn-end", { stopReason });
3896
4060
  },
3897
4061
  onResult: async (result) => {
3898
- try {
3899
- await messageStore.appendMessage({
3900
- sessionId,
3901
- role: "assistant",
3902
- content: accumText,
3903
- channel: "web",
3904
- metadata: {
3905
- triggeredBy: "web",
3906
- durationMs: result.durationMs,
3907
- numTurns: result.numTurns,
3908
- traceId,
3909
- requestId
3910
- }
3911
- });
3912
- } catch (err) {
3913
- log$28.error("[stream] 保存 assistant 消息失败", {
3914
- sessionId,
3915
- error: err
3916
- });
3917
- }
3918
- if (!clientDisconnected) {
3919
- sendSSE(res, "done", {
3920
- sessionId,
3921
- result: result.subtype,
3922
- text: accumText,
4062
+ const finalText = run.text || result.result || "";
4063
+ await queueRunMessage(run, messageStore, {
4064
+ sessionId,
4065
+ role: "assistant",
4066
+ content: finalText,
4067
+ channel: "web",
4068
+ metadata: {
4069
+ runId: run.runId,
4070
+ triggeredBy: "web",
3923
4071
  durationMs: result.durationMs,
3924
- totalCostUsd: result.totalCostUsd,
3925
- numTurns: result.numTurns
3926
- });
3927
- res.end();
3928
- }
4072
+ numTurns: result.numTurns,
4073
+ traceId,
4074
+ requestId,
4075
+ scenarioId,
4076
+ finalStatus: result.subtype,
4077
+ isError: result.isError
4078
+ }
4079
+ }, "[stream] 保存 assistant 消息失败");
4080
+ await markRunTerminal(result.isError ? "failed" : "completed", { finalStatus: result.subtype });
4081
+ broadcastRunEvent(run, "done", {
4082
+ sessionId,
4083
+ runId: run.runId,
4084
+ result: result.subtype,
4085
+ text: finalText,
4086
+ durationMs: result.durationMs,
4087
+ totalCostUsd: result.totalCostUsd,
4088
+ numTurns: result.numTurns
4089
+ });
4090
+ endRunClients(run);
3929
4091
  log$28.info("[stream] 对话完成", {
3930
4092
  sessionId,
4093
+ runId: run.runId,
3931
4094
  result: result.subtype,
3932
- textLength: accumText.length,
4095
+ textLength: finalText.length,
3933
4096
  durationMs: result.durationMs
3934
4097
  });
3935
4098
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "run_finished", {
3936
4099
  durationMs: result.durationMs,
3937
4100
  metadata: {
4101
+ runId: run.runId,
3938
4102
  subtype: result.subtype,
3939
4103
  totalCostUsd: result.totalCostUsd,
3940
4104
  numTurns: result.numTurns,
@@ -3945,22 +4109,45 @@ function createChatRouter(config) {
3945
4109
  }
3946
4110
  }));
3947
4111
  },
3948
- onError: (error) => {
4112
+ onError: async (error) => {
3949
4113
  log$28.error("[stream] Runtime 执行出错", {
3950
4114
  sessionId,
4115
+ runId: run.runId,
3951
4116
  error: error.message
3952
4117
  });
4118
+ await queueRunMessage(run, messageStore, {
4119
+ sessionId,
4120
+ role: "assistant",
4121
+ content: error.message,
4122
+ channel: "web",
4123
+ metadata: {
4124
+ runId: run.runId,
4125
+ triggeredBy: "web",
4126
+ traceId,
4127
+ requestId,
4128
+ scenarioId,
4129
+ finalStatus: "error",
4130
+ isError: true,
4131
+ errorMessage: error.message
4132
+ }
4133
+ }, "[stream] 保存 error 消息失败");
3953
4134
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "error", {
3954
4135
  errorMessage: error.message,
3955
4136
  metadata: {
4137
+ runId: run.runId,
3956
4138
  traceId,
3957
4139
  requestId
3958
4140
  }
3959
4141
  }));
3960
- if (!clientDisconnected) {
3961
- sendSSE(res, "error", { message: error.message });
3962
- res.end();
3963
- }
4142
+ await markRunTerminal("failed", {
4143
+ finalStatus: "error",
4144
+ error: error.message
4145
+ });
4146
+ broadcastRunEvent(run, "error", {
4147
+ runId: run.runId,
4148
+ message: error.message
4149
+ });
4150
+ endRunClients(run);
3964
4151
  },
3965
4152
  onAuditEvent: (event) => {
3966
4153
  logChatAudit({
@@ -3975,7 +4162,21 @@ function createChatRouter(config) {
3975
4162
  resultSummary,
3976
4163
  metadata: buildVerifierAuditMetadata(result)
3977
4164
  }));
3978
- if (!clientDisconnected) sendSSE(res, "verifier-result", { result: resultSummary });
4165
+ queueRunMessage(run, messageStore, {
4166
+ sessionId,
4167
+ role: "tool",
4168
+ content: JSON.stringify({ verifierResult: resultSummary }),
4169
+ channel: "web",
4170
+ metadata: {
4171
+ runId: run.runId,
4172
+ requestId,
4173
+ traceId,
4174
+ scenarioId,
4175
+ runtimeEventType: "verifier-result",
4176
+ verifierResult: resultSummary
4177
+ }
4178
+ }, "[stream] 保存 verifier result 消息失败");
4179
+ broadcastRunEvent(run, "verifier-result", { result: resultSummary });
3979
4180
  },
3980
4181
  onRuntimeEvent: handleRuntimeEvent,
3981
4182
  onScenarioEvent: handleRuntimeEvent,
@@ -3984,6 +4185,7 @@ function createChatRouter(config) {
3984
4185
  };
3985
4186
  try {
3986
4187
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "run_started", { metadata: {
4188
+ runId: run.runId,
3987
4189
  cwd: runtimeConfig.cwd,
3988
4190
  maxTurns: runtimeConfig.maxTurns,
3989
4191
  maxBudgetUsd: runtimeConfig.maxBudgetUsd,
@@ -3996,21 +4198,34 @@ function createChatRouter(config) {
3996
4198
  } }));
3997
4199
  log$28.info("[stream] 开始执行 prompt", {
3998
4200
  sessionId,
4201
+ runId: run.runId,
3999
4202
  cwd: runtimeConfig.cwd
4000
4203
  });
4001
4204
  await processManager.executePrompt(runtimeConfig, callbacks);
4205
+ if (run.status === "running") {
4206
+ const errorMessage = "Runtime completed without a final result event";
4207
+ log$28.error("[stream] executePrompt 未返回终态回调", {
4208
+ sessionId,
4209
+ runId: run.runId
4210
+ });
4211
+ await markRunTerminal("failed", {
4212
+ finalStatus: "error",
4213
+ error: errorMessage
4214
+ });
4215
+ broadcastRunEvent(run, "error", {
4216
+ runId: run.runId,
4217
+ message: errorMessage
4218
+ });
4219
+ endRunClients(run);
4220
+ }
4002
4221
  } catch (err) {
4003
4222
  const errorMessage = err instanceof Error ? err.message : String(err);
4004
4223
  log$28.error("[stream] executePrompt 异常", {
4005
4224
  sessionId,
4225
+ runId: run.runId,
4006
4226
  error: errorMessage
4007
4227
  });
4008
- if (!clientDisconnected) {
4009
- sendSSE(res, "error", { message: errorMessage });
4010
- res.end();
4011
- }
4012
- } finally {
4013
- activeStreamSessions.delete(sessionId);
4228
+ if (run.status === "running") await callbacks.onError?.(err instanceof Error ? err : new Error(errorMessage));
4014
4229
  }
4015
4230
  });
4016
4231
  router.get("/api/chat/history", chatAuthMiddleware, async (req, res) => {
@@ -4033,10 +4248,17 @@ function createChatRouter(config) {
4033
4248
  cursor,
4034
4249
  limit
4035
4250
  });
4251
+ const run = chatRunsBySession.get(sessionId);
4252
+ const visibleRun = run && isRunVisibleToUser(run, {
4253
+ tenantKey: chatUser.tenantKey,
4254
+ openId: chatUser.openId
4255
+ }) ? run : void 0;
4036
4256
  res.json({
4037
4257
  messages: result.messages,
4038
4258
  nextCursor: result.nextCursor,
4039
- sessionId
4259
+ sessionId,
4260
+ activeRun: visibleRun?.status === "running" ? serializeChatRun(visibleRun) : null,
4261
+ latestRun: visibleRun ? serializeChatRun(visibleRun) : null
4040
4262
  });
4041
4263
  } catch (err) {
4042
4264
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -4047,6 +4269,32 @@ function createChatRouter(config) {
4047
4269
  res.status(500).json({ error: "Failed to fetch history" });
4048
4270
  }
4049
4271
  });
4272
+ router.get("/api/chat/runs", chatAuthMiddleware, async (req, res) => {
4273
+ const chatUser = res.locals.chatUser;
4274
+ const sessionId = req.query.session_id;
4275
+ if (!sessionId) {
4276
+ res.status(400).json({ error: "session_id is required" });
4277
+ return;
4278
+ }
4279
+ const run = chatRunsBySession.get(sessionId);
4280
+ if (!run || !isRunVisibleToUser(run, {
4281
+ tenantKey: chatUser.tenantKey,
4282
+ openId: chatUser.openId
4283
+ })) {
4284
+ res.json({
4285
+ sessionId,
4286
+ run: null,
4287
+ activeRun: null
4288
+ });
4289
+ return;
4290
+ }
4291
+ const serialized = serializeChatRun(run);
4292
+ res.json({
4293
+ sessionId,
4294
+ run: serialized,
4295
+ activeRun: run.status === "running" ? serialized : null
4296
+ });
4297
+ });
4050
4298
  router.get("/api/chat/sessions", chatAuthMiddleware, async (_req, res) => {
4051
4299
  const chatUser = res.locals.chatUser;
4052
4300
  log$28.info("[sessions] 列出用户会话", { userId: chatUser.userId });
@@ -4096,6 +4344,7 @@ function createChatRouter(config) {
4096
4344
  });
4097
4345
  try {
4098
4346
  await messageStore.deleteSession(sessionId);
4347
+ chatRunsBySession.delete(sessionId);
4099
4348
  res.json({ success: true });
4100
4349
  } catch (err) {
4101
4350
  const errorMessage = err instanceof Error ? err.message : String(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-lark/larkpal",
3
- "version": "0.1.56",
3
+ "version": "0.1.57",
4
4
  "description": "LarkPal - Lark/Feishu bot service",
5
5
  "type": "module",
6
6
  "main": "./dist/main.mjs",