@vibe-lark/larkpal 0.1.59 → 0.1.60
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 +146 -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.adapterBusy,
|
|
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,71 @@ 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
|
+
recordRunEvent(run, `run-${status}`);
|
|
3581
|
+
broadcastRunStatus(run);
|
|
3582
|
+
await appendRunStatusMessage(run, messageStore);
|
|
3583
|
+
return true;
|
|
3584
|
+
}
|
|
3585
|
+
async function reconcileRunWithAdapter(run, messageStore, processManager) {
|
|
3586
|
+
if (run.status !== "running") return;
|
|
3587
|
+
const processInfo = processManager.getProcessInfo(run.sessionId);
|
|
3588
|
+
const adapterBusy = processManager.isSessionBusy?.(run.sessionId) ?? processInfo?.status === "running";
|
|
3589
|
+
run.adapterBusy = adapterBusy;
|
|
3590
|
+
const idleMs = Date.now() - run.lastEventAt;
|
|
3591
|
+
let staleReason;
|
|
3592
|
+
if (!adapterBusy) staleReason = processInfo ? `runtime process is ${processInfo.status}` : "runtime adapter reports session is not busy";
|
|
3593
|
+
else if (idleMs >= getStaleRunIdleMs()) staleReason = `run idle for ${Math.floor(idleMs / 1e3)}s`;
|
|
3594
|
+
if (!staleReason) return;
|
|
3595
|
+
const error = `Chat run became stale: ${staleReason}`;
|
|
3596
|
+
log$29.warn("[stream] activeRun 与 runtime 状态不一致,标记为 stale", {
|
|
3597
|
+
sessionId: run.sessionId,
|
|
3598
|
+
runId: run.runId,
|
|
3599
|
+
staleReason,
|
|
3600
|
+
idleMs,
|
|
3601
|
+
adapterBusy,
|
|
3602
|
+
processStatus: processInfo?.status
|
|
3603
|
+
});
|
|
3604
|
+
if (await markRunTerminal(run, messageStore, "failed", {
|
|
3605
|
+
finalStatus: "stale",
|
|
3606
|
+
error,
|
|
3607
|
+
staleReason
|
|
3608
|
+
})) {
|
|
3609
|
+
broadcastRunEvent(run, "error", {
|
|
3610
|
+
runId: run.runId,
|
|
3611
|
+
message: error,
|
|
3612
|
+
staleReason
|
|
3613
|
+
});
|
|
3614
|
+
endRunClients(run);
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3525
3617
|
function buildRuntimeEventMessageMetadata(event, runtimeConfig) {
|
|
3526
3618
|
return {
|
|
3527
3619
|
runtimeEventType: event.type,
|
|
@@ -3663,37 +3755,10 @@ function createChatRouter(config) {
|
|
|
3663
3755
|
text: "",
|
|
3664
3756
|
clients: /* @__PURE__ */ new Set(),
|
|
3665
3757
|
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;
|
|
3758
|
+
persistQueue: Promise.resolve(),
|
|
3759
|
+
seenRuntimeEventIds: /* @__PURE__ */ new Set(),
|
|
3760
|
+
lastEventAt: Date.now(),
|
|
3761
|
+
lastEventType: "run-started"
|
|
3697
3762
|
};
|
|
3698
3763
|
try {
|
|
3699
3764
|
await messageStore.appendMessage({
|
|
@@ -3722,7 +3787,7 @@ function createChatRouter(config) {
|
|
|
3722
3787
|
return;
|
|
3723
3788
|
}
|
|
3724
3789
|
chatRunsBySession.set(sessionId, run);
|
|
3725
|
-
appendRunStatusMessage();
|
|
3790
|
+
appendRunStatusMessage(run, messageStore);
|
|
3726
3791
|
prepareSSE(res);
|
|
3727
3792
|
addRunClient(req, res, run);
|
|
3728
3793
|
sendSSE(res, "session", { sessionId });
|
|
@@ -3736,6 +3801,7 @@ function createChatRouter(config) {
|
|
|
3736
3801
|
}, 15e3);
|
|
3737
3802
|
const handleBlockedEvent = (event) => {
|
|
3738
3803
|
const sanitized = sanitizeBlockedEvent(event);
|
|
3804
|
+
recordRunEvent(run, "blocked", { toolName: sanitized.name });
|
|
3739
3805
|
log$29.warn("[stream] Agent blocked event", { ...sanitized });
|
|
3740
3806
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, sanitized.type, {
|
|
3741
3807
|
toolName: sanitized.name,
|
|
@@ -3766,7 +3832,21 @@ function createChatRouter(config) {
|
|
|
3766
3832
|
broadcastRunEvent(run, "blocked", { event: sanitized });
|
|
3767
3833
|
};
|
|
3768
3834
|
const handleRuntimeEvent = (event) => {
|
|
3835
|
+
const dedupeKey = getRuntimeEventDedupeKey(event);
|
|
3836
|
+
if (dedupeKey) {
|
|
3837
|
+
if (run.seenRuntimeEventIds.has(dedupeKey)) {
|
|
3838
|
+
log$29.debug("[stream] 跳过重复 runtime event", {
|
|
3839
|
+
sessionId,
|
|
3840
|
+
runId: run.runId,
|
|
3841
|
+
eventId: dedupeKey,
|
|
3842
|
+
eventType: event.type
|
|
3843
|
+
});
|
|
3844
|
+
return;
|
|
3845
|
+
}
|
|
3846
|
+
run.seenRuntimeEventIds.add(dedupeKey);
|
|
3847
|
+
}
|
|
3769
3848
|
const eventSummary = summarizeForAudit(event);
|
|
3849
|
+
recordRunEvent(run, event.type, { toolName: event.capabilityId });
|
|
3770
3850
|
const runtimeFinalText = extractRuntimeEventFinalText(event);
|
|
3771
3851
|
if (runtimeFinalText && !run.text.trim()) run.text = runtimeFinalText;
|
|
3772
3852
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "runtime_event", {
|
|
@@ -3793,13 +3873,16 @@ function createChatRouter(config) {
|
|
|
3793
3873
|
};
|
|
3794
3874
|
const callbacks = {
|
|
3795
3875
|
onTextDelta: (text) => {
|
|
3876
|
+
recordRunEvent(run, "text-delta");
|
|
3796
3877
|
run.text += text;
|
|
3797
3878
|
broadcastRunEvent(run, "text-delta", { text });
|
|
3798
3879
|
},
|
|
3799
3880
|
onThinkingDelta: (text) => {
|
|
3881
|
+
recordRunEvent(run, "thinking-delta");
|
|
3800
3882
|
broadcastRunEvent(run, "thinking-delta", { text });
|
|
3801
3883
|
},
|
|
3802
3884
|
onToolUseStart: (toolName, toolInput) => {
|
|
3885
|
+
recordRunEvent(run, "tool-use-start", { toolName });
|
|
3803
3886
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_started", {
|
|
3804
3887
|
toolName,
|
|
3805
3888
|
inputSummary: summarizeForAudit(toolInput)
|
|
@@ -3827,6 +3910,7 @@ function createChatRouter(config) {
|
|
|
3827
3910
|
});
|
|
3828
3911
|
},
|
|
3829
3912
|
onToolResult: (toolUseId, result) => {
|
|
3913
|
+
recordRunEvent(run, "tool-result");
|
|
3830
3914
|
const resultSummary = summarizeForAudit(result);
|
|
3831
3915
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_finished", {
|
|
3832
3916
|
toolCallId: toolUseId,
|
|
@@ -3857,19 +3941,22 @@ function createChatRouter(config) {
|
|
|
3857
3941
|
});
|
|
3858
3942
|
},
|
|
3859
3943
|
onToolProgress: (toolName, elapsedSeconds) => {
|
|
3944
|
+
recordRunEvent(run, "tool-progress", { toolName });
|
|
3860
3945
|
broadcastRunEvent(run, "tool-progress", {
|
|
3861
3946
|
toolName,
|
|
3862
3947
|
elapsedSeconds
|
|
3863
3948
|
});
|
|
3864
3949
|
},
|
|
3865
3950
|
onTurnEnd: (stopReason) => {
|
|
3951
|
+
recordRunEvent(run, "turn-end");
|
|
3866
3952
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "turn_finished", { metadata: { stopReason } }));
|
|
3867
3953
|
broadcastRunEvent(run, "turn-end", { stopReason });
|
|
3868
3954
|
},
|
|
3869
3955
|
onResult: async (result) => {
|
|
3956
|
+
recordRunEvent(run, "result");
|
|
3870
3957
|
const finalText = run.text.trim() ? run.text : result.result || "";
|
|
3871
3958
|
run.text = finalText;
|
|
3872
|
-
await markRunTerminal(result.isError ? "failed" : "completed", { finalStatus: result.subtype });
|
|
3959
|
+
await markRunTerminal(run, messageStore, result.isError ? "failed" : "completed", { finalStatus: result.subtype });
|
|
3873
3960
|
await queueRunMessage(run, messageStore, {
|
|
3874
3961
|
sessionId,
|
|
3875
3962
|
role: "assistant",
|
|
@@ -3920,6 +4007,7 @@ function createChatRouter(config) {
|
|
|
3920
4007
|
}));
|
|
3921
4008
|
},
|
|
3922
4009
|
onError: async (error) => {
|
|
4010
|
+
recordRunEvent(run, "error");
|
|
3923
4011
|
log$29.error("[stream] Runtime 执行出错", {
|
|
3924
4012
|
sessionId,
|
|
3925
4013
|
runId: run.runId,
|
|
@@ -3949,7 +4037,7 @@ function createChatRouter(config) {
|
|
|
3949
4037
|
requestId
|
|
3950
4038
|
}
|
|
3951
4039
|
}));
|
|
3952
|
-
await markRunTerminal("failed", {
|
|
4040
|
+
await markRunTerminal(run, messageStore, "failed", {
|
|
3953
4041
|
finalStatus: "error",
|
|
3954
4042
|
error: error.message
|
|
3955
4043
|
});
|
|
@@ -3968,6 +4056,7 @@ function createChatRouter(config) {
|
|
|
3968
4056
|
});
|
|
3969
4057
|
},
|
|
3970
4058
|
onVerifierResult: (result) => {
|
|
4059
|
+
recordRunEvent(run, "verifier-result");
|
|
3971
4060
|
const resultSummary = summarizeForAudit(result);
|
|
3972
4061
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "verifier_result", {
|
|
3973
4062
|
resultSummary,
|
|
@@ -4019,7 +4108,7 @@ function createChatRouter(config) {
|
|
|
4019
4108
|
sessionId,
|
|
4020
4109
|
runId: run.runId
|
|
4021
4110
|
});
|
|
4022
|
-
await markRunTerminal("failed", {
|
|
4111
|
+
await markRunTerminal(run, messageStore, "failed", {
|
|
4023
4112
|
finalStatus: "error",
|
|
4024
4113
|
error: errorMessage
|
|
4025
4114
|
});
|
|
@@ -4065,6 +4154,7 @@ function createChatRouter(config) {
|
|
|
4065
4154
|
tenantKey: chatUser.tenantKey,
|
|
4066
4155
|
openId: chatUser.openId
|
|
4067
4156
|
}) ? run : void 0;
|
|
4157
|
+
if (visibleRun) await reconcileRunWithAdapter(visibleRun, messageStore, processManager);
|
|
4068
4158
|
res.json({
|
|
4069
4159
|
messages: result.messages,
|
|
4070
4160
|
nextCursor: result.nextCursor,
|
|
@@ -4100,6 +4190,7 @@ function createChatRouter(config) {
|
|
|
4100
4190
|
});
|
|
4101
4191
|
return;
|
|
4102
4192
|
}
|
|
4193
|
+
await reconcileRunWithAdapter(run, messageStore, processManager);
|
|
4103
4194
|
const serialized = serializeChatRun(run);
|
|
4104
4195
|
res.json({
|
|
4105
4196
|
sessionId,
|
|
@@ -6569,6 +6660,7 @@ const PERSONA_FILE = join(homedir(), ".claude", "CLAUDE.md");
|
|
|
6569
6660
|
function createStatusRouter(config) {
|
|
6570
6661
|
const router = express.Router();
|
|
6571
6662
|
const sessionsDir = getWorkspaceChatsRoot(config?.workspaceRoot ?? resolveWorkspaceRoot("/workspace"));
|
|
6663
|
+
const processManager = config?.processManager;
|
|
6572
6664
|
router.get("/sessions", async (req, res) => {
|
|
6573
6665
|
logger$2.info("list sessions request", {
|
|
6574
6666
|
method: req.method,
|
|
@@ -6669,14 +6761,18 @@ function createStatusRouter(config) {
|
|
|
6669
6761
|
try {
|
|
6670
6762
|
const uptimeSeconds = process.uptime();
|
|
6671
6763
|
const memory = process.memoryUsage();
|
|
6764
|
+
const processes = processManager?.getAllProcessInfo().map((processInfo) => {
|
|
6765
|
+
return serializeRuntimeProcess(processInfo, processManager);
|
|
6766
|
+
}) ?? [];
|
|
6672
6767
|
const statusData = {
|
|
6673
6768
|
uptime: uptimeSeconds,
|
|
6674
|
-
processes
|
|
6769
|
+
processes,
|
|
6675
6770
|
memory
|
|
6676
6771
|
};
|
|
6677
6772
|
logger$2.info("get status response", {
|
|
6678
6773
|
uptime: uptimeSeconds,
|
|
6679
|
-
memoryRss: memory.rss
|
|
6774
|
+
memoryRss: memory.rss,
|
|
6775
|
+
processCount: processes.length
|
|
6680
6776
|
});
|
|
6681
6777
|
res.json(statusData);
|
|
6682
6778
|
} catch (err) {
|
|
@@ -6720,6 +6816,14 @@ function createStatusRouter(config) {
|
|
|
6720
6816
|
});
|
|
6721
6817
|
return router;
|
|
6722
6818
|
}
|
|
6819
|
+
function serializeRuntimeProcess(processInfo, processManager) {
|
|
6820
|
+
return {
|
|
6821
|
+
...processInfo,
|
|
6822
|
+
startedAt: processInfo.startedAt.toISOString(),
|
|
6823
|
+
lastActiveAt: processInfo.lastActiveAt.toISOString(),
|
|
6824
|
+
busy: processManager.isSessionBusy?.(processInfo.sessionId) ?? processInfo.status === "running"
|
|
6825
|
+
};
|
|
6826
|
+
}
|
|
6723
6827
|
//#endregion
|
|
6724
6828
|
//#region src/gateway/server.ts
|
|
6725
6829
|
const logger$1 = larkLogger("gateway");
|
|
@@ -6783,7 +6887,10 @@ function registerRoutes(app, processManager, scheduledTaskManager, appCredential
|
|
|
6783
6887
|
app.put("/api/scheduled-tasks/:id", notImplemented);
|
|
6784
6888
|
app.delete("/api/scheduled-tasks/:id", notImplemented);
|
|
6785
6889
|
}
|
|
6786
|
-
app.use("/api", createStatusRouter({
|
|
6890
|
+
app.use("/api", createStatusRouter({
|
|
6891
|
+
workspaceRoot,
|
|
6892
|
+
processManager
|
|
6893
|
+
}));
|
|
6787
6894
|
app.use("/hooks", createHooksRouter());
|
|
6788
6895
|
app.use("/api/mcp", createMcpCallbackRouter());
|
|
6789
6896
|
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.60",
|
|
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",
|