@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 +0 -0
- package/dist/main.mjs +154 -1
- package/package.json +14 -15
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.
|
|
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
|
+
}
|