@vibe-lark/larkpal 0.1.67 → 0.1.69

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/bin/larkpal.js CHANGED
File without changes
package/dist/main.mjs CHANGED
@@ -3724,6 +3724,8 @@ function summarizeString(value) {
3724
3724
  *
3725
3725
  * 路由:
3726
3726
  * POST /api/chat/stream — SSE 流式对话
3727
+ * POST /api/chat/runs/:runId/cancel — 取消运行中的 Web Chat run
3728
+ * POST /api/chat/sessions/:id/cancel-active-run — 取消会话中的 active run
3727
3729
  * GET /api/chat/history — 分页获取会话历史
3728
3730
  * GET /api/chat/sessions — 列出当前用户的所有会话
3729
3731
  * POST /api/chat/sessions — 创建新会话
@@ -3754,6 +3756,9 @@ function getActiveChatRun(sessionId) {
3754
3756
  const run = chatRunsBySession.get(sessionId);
3755
3757
  return run?.status === "running" ? run : void 0;
3756
3758
  }
3759
+ function findChatRunByRunId(runId) {
3760
+ for (const run of chatRunsBySession.values()) if (run.runId === runId) return run;
3761
+ }
3757
3762
  function serializeChatRun(run) {
3758
3763
  const idleSeconds = Math.max(0, Math.floor((Date.now() - run.lastEventAt) / 1e3));
3759
3764
  return {
@@ -3918,6 +3923,60 @@ async function markRunTerminal(run, messageStore, status, params) {
3918
3923
  await appendRunStatusMessage(run, messageStore);
3919
3924
  return true;
3920
3925
  }
3926
+ async function cleanupTerminalRunProcess(run, processManager, reason) {
3927
+ if (run.status === "running") return;
3928
+ const processInfo = processManager.getProcessInfo(run.sessionId);
3929
+ const adapterBusy = processManager.isSessionBusy?.(run.sessionId) ?? processInfo?.status === "running";
3930
+ run.adapterBusy = adapterBusy;
3931
+ run.processStatus = processInfo?.status;
3932
+ if (adapterBusy || processInfo?.status !== "running" && processInfo?.status !== "starting") return;
3933
+ try {
3934
+ await processManager.stopProcess(run.sessionId);
3935
+ run.processStatus = processManager.getProcessInfo(run.sessionId)?.status ?? "stopped";
3936
+ log$30.info("[stream] terminal run 已清理 runtime process", {
3937
+ sessionId: run.sessionId,
3938
+ runId: run.runId,
3939
+ reason,
3940
+ processStatus: processInfo.status
3941
+ });
3942
+ } catch (err) {
3943
+ log$30.error("[stream] terminal run 清理 runtime process 失败", {
3944
+ sessionId: run.sessionId,
3945
+ runId: run.runId,
3946
+ reason,
3947
+ processStatus: processInfo.status,
3948
+ error: err instanceof Error ? err.message : String(err)
3949
+ });
3950
+ }
3951
+ }
3952
+ async function cancelChatRun(run, messageStore, processManager) {
3953
+ if (run.status !== "running") return {
3954
+ cancelled: false,
3955
+ run: serializeChatRun(run)
3956
+ };
3957
+ const processInfo = processManager.getProcessInfo(run.sessionId);
3958
+ run.adapterBusy = processManager.isSessionBusy?.(run.sessionId) ?? processInfo?.status === "running";
3959
+ run.processStatus = processInfo?.status;
3960
+ await processManager.stopProcess(run.sessionId);
3961
+ const stoppedInfo = processManager.getProcessInfo(run.sessionId);
3962
+ run.adapterBusy = processManager.isSessionBusy?.(run.sessionId) ?? false;
3963
+ run.processStatus = stoppedInfo?.status ?? "stopped";
3964
+ const marked = await markRunTerminal(run, messageStore, "cancelled", {
3965
+ finalStatus: "user_cancelled",
3966
+ error: "Chat run cancelled by user"
3967
+ });
3968
+ if (marked) {
3969
+ broadcastRunEvent(run, "cancelled", {
3970
+ runId: run.runId,
3971
+ sessionId: run.sessionId
3972
+ });
3973
+ endRunClients(run);
3974
+ }
3975
+ return {
3976
+ cancelled: marked,
3977
+ run: serializeChatRun(run)
3978
+ };
3979
+ }
3921
3980
  async function reconcileRunWithAdapter(run, messageStore, processManager) {
3922
3981
  if (run.status !== "running") return;
3923
3982
  const processInfo = processManager.getProcessInfo(run.sessionId);
@@ -4002,6 +4061,8 @@ function buildRuntimeEventMessageMetadata(event, runtimeConfig) {
4002
4061
  *
4003
4062
  * 路由:
4004
4063
  * POST /api/chat/stream — SSE 流式对话
4064
+ * POST /api/chat/runs/:runId/cancel — 取消运行中的 Web Chat run
4065
+ * POST /api/chat/sessions/:id/cancel-active-run — 取消会话中的 active run
4005
4066
  * GET /api/chat/history — 分页获取会话历史
4006
4067
  * GET /api/chat/sessions — 列出当前用户的所有会话
4007
4068
  * POST /api/chat/sessions — 创建新会话
@@ -4162,6 +4223,7 @@ function createChatRouter(config) {
4162
4223
  }, 15e3);
4163
4224
  const handleBlockedEvent = (event) => {
4164
4225
  const sanitized = sanitizeBlockedEvent(event);
4226
+ if (run.status !== "running") return;
4165
4227
  recordRunEvent(run, "blocked", { toolName: sanitized.name });
4166
4228
  log$30.warn("[stream] Agent blocked event", { ...sanitized });
4167
4229
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, sanitized.type, {
@@ -4193,6 +4255,7 @@ function createChatRouter(config) {
4193
4255
  broadcastRunEvent(run, "blocked", { event: sanitized });
4194
4256
  };
4195
4257
  const handleRuntimeEvent = (event) => {
4258
+ if (run.status !== "running") return;
4196
4259
  const dedupeKey = getRuntimeEventDedupeKey(event);
4197
4260
  if (dedupeKey) {
4198
4261
  if (run.seenRuntimeEventIds.has(dedupeKey)) {
@@ -4234,15 +4297,18 @@ function createChatRouter(config) {
4234
4297
  };
4235
4298
  const callbacks = {
4236
4299
  onTextDelta: (text) => {
4300
+ if (run.status !== "running") return;
4237
4301
  recordRunEvent(run, "text-delta");
4238
4302
  run.text += text;
4239
4303
  broadcastRunEvent(run, "text-delta", { text });
4240
4304
  },
4241
4305
  onThinkingDelta: (text) => {
4306
+ if (run.status !== "running") return;
4242
4307
  recordRunEvent(run, "thinking-delta");
4243
4308
  broadcastRunEvent(run, "thinking-delta", { text });
4244
4309
  },
4245
4310
  onToolUseStart: (toolName, toolInput) => {
4311
+ if (run.status !== "running") return;
4246
4312
  recordRunEvent(run, "tool-use-start", { toolName });
4247
4313
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_started", {
4248
4314
  toolName,
@@ -4271,6 +4337,7 @@ function createChatRouter(config) {
4271
4337
  });
4272
4338
  },
4273
4339
  onToolResult: (toolUseId, result) => {
4340
+ if (run.status !== "running") return;
4274
4341
  recordRunEvent(run, "tool-result");
4275
4342
  const resultSummary = summarizeForAudit(result);
4276
4343
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_finished", {
@@ -4302,6 +4369,7 @@ function createChatRouter(config) {
4302
4369
  });
4303
4370
  },
4304
4371
  onToolProgress: (toolName, elapsedSeconds) => {
4372
+ if (run.status !== "running") return;
4305
4373
  recordRunEvent(run, "tool-progress", { toolName });
4306
4374
  broadcastRunEvent(run, "tool-progress", {
4307
4375
  toolName,
@@ -4309,15 +4377,18 @@ function createChatRouter(config) {
4309
4377
  });
4310
4378
  },
4311
4379
  onTurnEnd: (stopReason) => {
4380
+ if (run.status !== "running") return;
4312
4381
  recordRunEvent(run, "turn-end");
4313
4382
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "turn_finished", { metadata: { stopReason } }));
4314
4383
  broadcastRunEvent(run, "turn-end", { stopReason });
4315
4384
  },
4316
4385
  onResult: async (result) => {
4386
+ if (run.status !== "running") return;
4317
4387
  recordRunEvent(run, "result");
4318
4388
  const finalText = run.text.trim() ? run.text : result.result || "";
4319
4389
  run.text = finalText;
4320
4390
  await markRunTerminal(run, messageStore, result.isError ? "failed" : "completed", { finalStatus: result.subtype });
4391
+ await cleanupTerminalRunProcess(run, processManager, "result");
4321
4392
  await queueRunMessage(run, messageStore, {
4322
4393
  sessionId,
4323
4394
  role: "assistant",
@@ -4368,6 +4439,7 @@ function createChatRouter(config) {
4368
4439
  }));
4369
4440
  },
4370
4441
  onError: async (error) => {
4442
+ if (run.status !== "running") return;
4371
4443
  recordRunEvent(run, "error");
4372
4444
  log$30.error("[stream] Runtime 执行出错", {
4373
4445
  sessionId,
@@ -4402,6 +4474,7 @@ function createChatRouter(config) {
4402
4474
  finalStatus: "error",
4403
4475
  error: error.message
4404
4476
  });
4477
+ await cleanupTerminalRunProcess(run, processManager, "error");
4405
4478
  broadcastRunEvent(run, "error", {
4406
4479
  runId: run.runId,
4407
4480
  message: error.message
@@ -4417,6 +4490,7 @@ function createChatRouter(config) {
4417
4490
  });
4418
4491
  },
4419
4492
  onVerifierResult: (result) => {
4493
+ if (run.status !== "running") return;
4420
4494
  recordRunEvent(run, "verifier-result");
4421
4495
  const resultSummary = summarizeForAudit(result);
4422
4496
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "verifier_result", {
@@ -4473,13 +4547,14 @@ function createChatRouter(config) {
4473
4547
  finalStatus: "error",
4474
4548
  error: errorMessage
4475
4549
  });
4550
+ await cleanupTerminalRunProcess(run, processManager, "missing-result");
4476
4551
  broadcastRunEvent(run, "error", {
4477
4552
  runId: run.runId,
4478
4553
  message: errorMessage
4479
4554
  });
4480
4555
  clearInterval(heartbeatTimer);
4481
4556
  endRunClients(run);
4482
- }
4557
+ } else await cleanupTerminalRunProcess(run, processManager, "execute-returned");
4483
4558
  } catch (err) {
4484
4559
  const errorMessage = err instanceof Error ? err.message : String(err);
4485
4560
  log$30.error("[stream] executePrompt 异常", {
@@ -4490,6 +4565,84 @@ function createChatRouter(config) {
4490
4565
  if (run.status === "running") await callbacks.onError?.(err instanceof Error ? err : new Error(errorMessage));
4491
4566
  }
4492
4567
  });
4568
+ router.post("/api/chat/runs/:runId/cancel", chatAuthMiddleware, async (req, res) => {
4569
+ const chatUser = res.locals.chatUser;
4570
+ const runId = String(req.params.runId);
4571
+ const run = findChatRunByRunId(runId);
4572
+ if (!run) {
4573
+ res.json({
4574
+ success: true,
4575
+ cancelled: false,
4576
+ run: null
4577
+ });
4578
+ return;
4579
+ }
4580
+ if (!isRunVisibleToUser(run, {
4581
+ tenantKey: chatUser.tenantKey,
4582
+ openId: chatUser.openId
4583
+ })) {
4584
+ res.status(403).json({ error: "Forbidden" });
4585
+ return;
4586
+ }
4587
+ try {
4588
+ const result = await cancelChatRun(run, messageStore, processManager);
4589
+ res.json({
4590
+ success: true,
4591
+ ...result
4592
+ });
4593
+ } catch (err) {
4594
+ const errorMessage = err instanceof Error ? err.message : String(err);
4595
+ log$30.error("[runs] 取消 run 失败", {
4596
+ runId,
4597
+ sessionId: run.sessionId,
4598
+ error: errorMessage
4599
+ });
4600
+ res.status(500).json({
4601
+ error: "Failed to cancel chat run",
4602
+ run: serializeChatRun(run)
4603
+ });
4604
+ }
4605
+ });
4606
+ router.post("/api/chat/sessions/:id/cancel-active-run", chatAuthMiddleware, async (req, res) => {
4607
+ const chatUser = res.locals.chatUser;
4608
+ const sessionId = String(req.params.id);
4609
+ const run = chatRunsBySession.get(sessionId);
4610
+ if (!run) {
4611
+ res.json({
4612
+ success: true,
4613
+ cancelled: false,
4614
+ sessionId,
4615
+ run: null
4616
+ });
4617
+ return;
4618
+ }
4619
+ if (!isRunVisibleToUser(run, {
4620
+ tenantKey: chatUser.tenantKey,
4621
+ openId: chatUser.openId
4622
+ })) {
4623
+ res.status(403).json({ error: "Forbidden" });
4624
+ return;
4625
+ }
4626
+ try {
4627
+ const result = await cancelChatRun(run, messageStore, processManager);
4628
+ res.json({
4629
+ success: true,
4630
+ sessionId,
4631
+ ...result
4632
+ });
4633
+ } catch (err) {
4634
+ const errorMessage = err instanceof Error ? err.message : String(err);
4635
+ log$30.error("[sessions] 取消 active run 失败", {
4636
+ sessionId,
4637
+ runId: run.runId,
4638
+ error: errorMessage
4639
+ });
4640
+ res.status(500).json({
4641
+ error: "Failed to cancel active chat run",
4642
+ run: serializeChatRun(run)
4643
+ });
4644
+ }
4645
+ });
4493
4646
  router.get("/api/chat/history", chatAuthMiddleware, async (req, res) => {
4494
4647
  const chatUser = res.locals.chatUser;
4495
4648
  const sessionId = req.query.session_id;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-lark/larkpal",
3
- "version": "0.1.67",
3
+ "version": "0.1.69",
4
4
  "description": "LarkPal - Lark/Feishu bot service",
5
5
  "type": "module",
6
6
  "main": "./dist/main.mjs",
@@ -21,22 +21,9 @@
21
21
  "registry": "https://registry.npmjs.org",
22
22
  "access": "public"
23
23
  },
24
- "packageManager": "pnpm@10.32.1",
25
24
  "engines": {
26
25
  "node": ">=22"
27
26
  },
28
- "scripts": {
29
- "start": "node --env-file=.env bin/larkpal.js",
30
- "build": "tsdown",
31
- "test": "vitest run",
32
- "test:watch": "vitest",
33
- "lint": "eslint src/",
34
- "lint:fix": "eslint src/ --fix",
35
- "sit:larkpal-agent": "node scripts/sit/larkpal-agent-0.1.9-sit.mjs",
36
- "typecheck": "tsc --noEmit",
37
- "format": "prettier --write src/**/*.ts",
38
- "format:check": "prettier --check src/**/*.ts"
39
- },
40
27
  "dependencies": {
41
28
  "@larksuiteoapi/node-sdk": "^1.60.0",
42
29
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -69,5 +56,17 @@
69
56
  "typescript": "^5.9.3",
70
57
  "typescript-eslint": "^8.32.1",
71
58
  "vitest": "^4.1.1"
59
+ },
60
+ "scripts": {
61
+ "start": "node --env-file=.env bin/larkpal.js",
62
+ "build": "tsdown",
63
+ "test": "vitest run",
64
+ "test:watch": "vitest",
65
+ "lint": "eslint src/",
66
+ "lint:fix": "eslint src/ --fix",
67
+ "sit:larkpal-agent": "node scripts/sit/larkpal-agent-0.1.9-sit.mjs",
68
+ "typecheck": "tsc --noEmit",
69
+ "format": "prettier --write src/**/*.ts",
70
+ "format:check": "prettier --check src/**/*.ts"
72
71
  }
73
- }
72
+ }