@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.
- package/dist/main.mjs +147 -39
- 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
|
-
|
|
3669
|
-
|
|
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({
|
|
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.
|
|
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.
|
|
53
|
+
"@vibe-lark/larkpal-agent": "^0.2.12"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@eslint/js": "^10.0.1",
|