@vibe-lark/larkpal 0.1.68 → 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 {
@@ -3944,6 +3949,34 @@ async function cleanupTerminalRunProcess(run, processManager, reason) {
3944
3949
  });
3945
3950
  }
3946
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
+ }
3947
3980
  async function reconcileRunWithAdapter(run, messageStore, processManager) {
3948
3981
  if (run.status !== "running") return;
3949
3982
  const processInfo = processManager.getProcessInfo(run.sessionId);
@@ -4028,6 +4061,8 @@ function buildRuntimeEventMessageMetadata(event, runtimeConfig) {
4028
4061
  *
4029
4062
  * 路由:
4030
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
4031
4066
  * GET /api/chat/history — 分页获取会话历史
4032
4067
  * GET /api/chat/sessions — 列出当前用户的所有会话
4033
4068
  * POST /api/chat/sessions — 创建新会话
@@ -4188,6 +4223,7 @@ function createChatRouter(config) {
4188
4223
  }, 15e3);
4189
4224
  const handleBlockedEvent = (event) => {
4190
4225
  const sanitized = sanitizeBlockedEvent(event);
4226
+ if (run.status !== "running") return;
4191
4227
  recordRunEvent(run, "blocked", { toolName: sanitized.name });
4192
4228
  log$30.warn("[stream] Agent blocked event", { ...sanitized });
4193
4229
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, sanitized.type, {
@@ -4219,6 +4255,7 @@ function createChatRouter(config) {
4219
4255
  broadcastRunEvent(run, "blocked", { event: sanitized });
4220
4256
  };
4221
4257
  const handleRuntimeEvent = (event) => {
4258
+ if (run.status !== "running") return;
4222
4259
  const dedupeKey = getRuntimeEventDedupeKey(event);
4223
4260
  if (dedupeKey) {
4224
4261
  if (run.seenRuntimeEventIds.has(dedupeKey)) {
@@ -4260,15 +4297,18 @@ function createChatRouter(config) {
4260
4297
  };
4261
4298
  const callbacks = {
4262
4299
  onTextDelta: (text) => {
4300
+ if (run.status !== "running") return;
4263
4301
  recordRunEvent(run, "text-delta");
4264
4302
  run.text += text;
4265
4303
  broadcastRunEvent(run, "text-delta", { text });
4266
4304
  },
4267
4305
  onThinkingDelta: (text) => {
4306
+ if (run.status !== "running") return;
4268
4307
  recordRunEvent(run, "thinking-delta");
4269
4308
  broadcastRunEvent(run, "thinking-delta", { text });
4270
4309
  },
4271
4310
  onToolUseStart: (toolName, toolInput) => {
4311
+ if (run.status !== "running") return;
4272
4312
  recordRunEvent(run, "tool-use-start", { toolName });
4273
4313
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_started", {
4274
4314
  toolName,
@@ -4297,6 +4337,7 @@ function createChatRouter(config) {
4297
4337
  });
4298
4338
  },
4299
4339
  onToolResult: (toolUseId, result) => {
4340
+ if (run.status !== "running") return;
4300
4341
  recordRunEvent(run, "tool-result");
4301
4342
  const resultSummary = summarizeForAudit(result);
4302
4343
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_finished", {
@@ -4328,6 +4369,7 @@ function createChatRouter(config) {
4328
4369
  });
4329
4370
  },
4330
4371
  onToolProgress: (toolName, elapsedSeconds) => {
4372
+ if (run.status !== "running") return;
4331
4373
  recordRunEvent(run, "tool-progress", { toolName });
4332
4374
  broadcastRunEvent(run, "tool-progress", {
4333
4375
  toolName,
@@ -4335,11 +4377,13 @@ function createChatRouter(config) {
4335
4377
  });
4336
4378
  },
4337
4379
  onTurnEnd: (stopReason) => {
4380
+ if (run.status !== "running") return;
4338
4381
  recordRunEvent(run, "turn-end");
4339
4382
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "turn_finished", { metadata: { stopReason } }));
4340
4383
  broadcastRunEvent(run, "turn-end", { stopReason });
4341
4384
  },
4342
4385
  onResult: async (result) => {
4386
+ if (run.status !== "running") return;
4343
4387
  recordRunEvent(run, "result");
4344
4388
  const finalText = run.text.trim() ? run.text : result.result || "";
4345
4389
  run.text = finalText;
@@ -4395,6 +4439,7 @@ function createChatRouter(config) {
4395
4439
  }));
4396
4440
  },
4397
4441
  onError: async (error) => {
4442
+ if (run.status !== "running") return;
4398
4443
  recordRunEvent(run, "error");
4399
4444
  log$30.error("[stream] Runtime 执行出错", {
4400
4445
  sessionId,
@@ -4445,6 +4490,7 @@ function createChatRouter(config) {
4445
4490
  });
4446
4491
  },
4447
4492
  onVerifierResult: (result) => {
4493
+ if (run.status !== "running") return;
4448
4494
  recordRunEvent(run, "verifier-result");
4449
4495
  const resultSummary = summarizeForAudit(result);
4450
4496
  if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "verifier_result", {
@@ -4519,6 +4565,84 @@ function createChatRouter(config) {
4519
4565
  if (run.status === "running") await callbacks.onError?.(err instanceof Error ? err : new Error(errorMessage));
4520
4566
  }
4521
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
+ });
4522
4646
  router.get("/api/chat/history", chatAuthMiddleware, async (req, res) => {
4523
4647
  const chatUser = res.locals.chatUser;
4524
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.68",
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
+ }