@vibe-lark/larkpal 0.1.72 → 0.1.74
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 +589 -22
- package/package.json +1 -1
package/dist/main.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { n as getLarkRuntime, r as setLarkRuntime, t as larkLogger } from "./lar
|
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import * as path$2 from "node:path";
|
|
6
|
-
import path, { basename, dirname, extname, join, resolve } from "node:path";
|
|
6
|
+
import path, { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import "dotenv/config";
|
|
9
9
|
import { access, copyFile, lstat, mkdir, open, readFile, readdir, readlink, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
@@ -782,7 +782,10 @@ function getToolDisplayName(toolName) {
|
|
|
782
782
|
* - parseError(error, rawLine) — 解析错误(不中断流)
|
|
783
783
|
*/
|
|
784
784
|
const log$36 = larkLogger("cc-runtime/stream-parser");
|
|
785
|
+
const MAX_TOOL_RESULT_CONTENT_CHARS = 8e3;
|
|
785
786
|
var CCStreamParser = class extends EventEmitter {
|
|
787
|
+
toolNamesById = /* @__PURE__ */ new Map();
|
|
788
|
+
declaredTools = null;
|
|
786
789
|
/**
|
|
787
790
|
* 解析一行 NDJSON 文本
|
|
788
791
|
*
|
|
@@ -830,6 +833,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
830
833
|
subtype: msg.subtype,
|
|
831
834
|
detail: JSON.stringify(msg).slice(0, 500)
|
|
832
835
|
});
|
|
836
|
+
this.handleSystem(msg);
|
|
833
837
|
this.emit("system", msg);
|
|
834
838
|
break;
|
|
835
839
|
default:
|
|
@@ -858,6 +862,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
858
862
|
toolUseId: block?.id
|
|
859
863
|
});
|
|
860
864
|
if (block?.type === "tool_use" && block.name) {
|
|
865
|
+
this.recordToolUse(block.name, block.id);
|
|
861
866
|
log$36.info("工具调用开始", {
|
|
862
867
|
toolName: block.name,
|
|
863
868
|
displayName: getToolDisplayName(block.name),
|
|
@@ -905,6 +910,7 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
905
910
|
});
|
|
906
911
|
if (Array.isArray(content)) {
|
|
907
912
|
for (const block of content) if (block.type === "tool_use" && block.name) {
|
|
913
|
+
this.recordToolUse(block.name, block.id);
|
|
908
914
|
log$36.info("从 assistant 消息提取工具调用", {
|
|
909
915
|
toolName: block.name,
|
|
910
916
|
displayName: getToolDisplayName(block.name),
|
|
@@ -924,10 +930,46 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
924
930
|
const content = msg.message?.content;
|
|
925
931
|
if (!Array.isArray(content)) return;
|
|
926
932
|
for (const item of content) if (item.type === "tool_result" && item.tool_use_id) {
|
|
927
|
-
|
|
928
|
-
|
|
933
|
+
const result = this.buildToolResultSummary(item, msg.toolUseResult);
|
|
934
|
+
log$36.debug("工具结果返回", {
|
|
935
|
+
toolUseId: item.tool_use_id,
|
|
936
|
+
toolName: result.toolName,
|
|
937
|
+
isError: result.isError,
|
|
938
|
+
truncated: result.truncated
|
|
939
|
+
});
|
|
940
|
+
this.emit("toolResult", item.tool_use_id, result);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
handleSystem(msg) {
|
|
944
|
+
if (msg.subtype !== "init" || !Array.isArray(msg.tools)) return;
|
|
945
|
+
this.declaredTools = new Set(msg.tools);
|
|
946
|
+
}
|
|
947
|
+
recordToolUse(toolName, toolUseId) {
|
|
948
|
+
if (toolUseId) this.toolNamesById.set(toolUseId, toolName);
|
|
949
|
+
if (this.declaredTools && !this.declaredTools.has(toolName)) {
|
|
950
|
+
log$36.warn("模型请求了未声明工具", {
|
|
951
|
+
toolName,
|
|
952
|
+
toolUseId
|
|
953
|
+
});
|
|
954
|
+
this.emit("unknownToolUse", toolName, toolUseId);
|
|
929
955
|
}
|
|
930
956
|
}
|
|
957
|
+
buildToolResultSummary(item, toolUseResult) {
|
|
958
|
+
const serializedContent = serializeToolResultContent(item.content);
|
|
959
|
+
const truncatedContent = truncateToolResultContent(serializedContent);
|
|
960
|
+
const toolUseResultSummary = summarizeToolUseResult(toolUseResult);
|
|
961
|
+
return {
|
|
962
|
+
toolCallId: item.tool_use_id,
|
|
963
|
+
toolName: this.toolNamesById.get(item.tool_use_id),
|
|
964
|
+
rawType: "tool_result",
|
|
965
|
+
isError: item.is_error,
|
|
966
|
+
content: truncatedContent.content,
|
|
967
|
+
resultSummary: extractToolResultSummary(serializedContent, toolUseResultSummary),
|
|
968
|
+
truncated: truncatedContent.truncated || toolUseResultSummary.truncated,
|
|
969
|
+
originalSize: Math.max(serializedContent.length, toolUseResultSummary.originalSize),
|
|
970
|
+
toolUseResult: toolUseResultSummary.summary
|
|
971
|
+
};
|
|
972
|
+
}
|
|
931
973
|
/**
|
|
932
974
|
* 处理 tool_progress 消息 — 工具执行进度
|
|
933
975
|
*/
|
|
@@ -959,6 +1001,52 @@ var CCStreamParser = class extends EventEmitter {
|
|
|
959
1001
|
});
|
|
960
1002
|
}
|
|
961
1003
|
};
|
|
1004
|
+
function serializeToolResultContent(content) {
|
|
1005
|
+
if (typeof content === "string") return content;
|
|
1006
|
+
return safeJsonStringify(content);
|
|
1007
|
+
}
|
|
1008
|
+
function summarizeToolUseResult(value) {
|
|
1009
|
+
if (value === void 0) return {
|
|
1010
|
+
originalSize: 0,
|
|
1011
|
+
truncated: false
|
|
1012
|
+
};
|
|
1013
|
+
const serialized = typeof value === "string" ? value : safeJsonStringify(value);
|
|
1014
|
+
const truncated = truncateToolResultContent(serialized);
|
|
1015
|
+
return {
|
|
1016
|
+
summary: typeof value === "string" ? truncated.content : tryParseJson$1(truncated.content) ?? truncated.content,
|
|
1017
|
+
originalSize: serialized.length,
|
|
1018
|
+
truncated: truncated.truncated
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
function truncateToolResultContent(content) {
|
|
1022
|
+
if (content.length <= MAX_TOOL_RESULT_CONTENT_CHARS) return {
|
|
1023
|
+
content,
|
|
1024
|
+
truncated: false
|
|
1025
|
+
};
|
|
1026
|
+
return {
|
|
1027
|
+
content: content.slice(0, MAX_TOOL_RESULT_CONTENT_CHARS),
|
|
1028
|
+
truncated: true
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
function extractToolResultSummary(content, toolUseResult) {
|
|
1032
|
+
const text = (typeof toolUseResult.summary === "string" ? toolUseResult.summary : toolUseResult.summary === void 0 ? void 0 : safeJsonStringify(toolUseResult.summary)) || content;
|
|
1033
|
+
const summary = (text.match(/<tool_use_error>([\s\S]*?)<\/tool_use_error>/)?.[1] || text).trim();
|
|
1034
|
+
return summary ? truncateToolResultContent(summary).content : void 0;
|
|
1035
|
+
}
|
|
1036
|
+
function safeJsonStringify(value) {
|
|
1037
|
+
try {
|
|
1038
|
+
return JSON.stringify(value);
|
|
1039
|
+
} catch {
|
|
1040
|
+
return String(value);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
function tryParseJson$1(value) {
|
|
1044
|
+
try {
|
|
1045
|
+
return JSON.parse(value);
|
|
1046
|
+
} catch {
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
962
1050
|
//#endregion
|
|
963
1051
|
//#region src/cc-runtime/process-manager.ts
|
|
964
1052
|
/**
|
|
@@ -1416,8 +1504,11 @@ var SessionProcessManager = class {
|
|
|
1416
1504
|
parser.on("toolUseStart", (toolName, toolInput) => {
|
|
1417
1505
|
getCallbacks()?.onToolUseStart?.(toolName, toolInput);
|
|
1418
1506
|
});
|
|
1419
|
-
parser.on("toolResult", (toolUseId) => {
|
|
1420
|
-
getCallbacks()?.onToolResult?.(toolUseId);
|
|
1507
|
+
parser.on("toolResult", (toolUseId, result) => {
|
|
1508
|
+
getCallbacks()?.onToolResult?.(toolUseId, result);
|
|
1509
|
+
});
|
|
1510
|
+
parser.on("unknownToolUse", (toolName, toolUseId) => {
|
|
1511
|
+
getCallbacks()?.onUnknownToolUse?.(toolName, toolUseId);
|
|
1421
1512
|
});
|
|
1422
1513
|
parser.on("toolProgress", (toolName, elapsedSeconds) => {
|
|
1423
1514
|
getCallbacks()?.onToolProgress?.(toolName, elapsedSeconds);
|
|
@@ -3679,6 +3770,13 @@ function summarizeString(value) {
|
|
|
3679
3770
|
* DELETE /api/chat/sessions/:id — 删除会话
|
|
3680
3771
|
*/
|
|
3681
3772
|
const log$30 = larkLogger("chat/stream");
|
|
3773
|
+
var SessionFileUploadError = class extends Error {
|
|
3774
|
+
constructor(message, statusCode = 400) {
|
|
3775
|
+
super(message);
|
|
3776
|
+
this.statusCode = statusCode;
|
|
3777
|
+
this.name = "SessionFileUploadError";
|
|
3778
|
+
}
|
|
3779
|
+
};
|
|
3682
3780
|
const chatRunsBySession = /* @__PURE__ */ new Map();
|
|
3683
3781
|
const DEFAULT_STALE_RUN_IDLE_MS = 1800 * 1e3;
|
|
3684
3782
|
/** 统一写 SSE 格式数据到响应流 */
|
|
@@ -3725,6 +3823,9 @@ function serializeChatRun(run) {
|
|
|
3725
3823
|
lastEventAt: run.lastEventAt,
|
|
3726
3824
|
lastEventType: run.lastEventType,
|
|
3727
3825
|
lastToolName: run.lastToolName,
|
|
3826
|
+
lastToolError: run.lastToolError,
|
|
3827
|
+
unknownToolCallCount: run.unknownToolCallCount,
|
|
3828
|
+
lastUnknownToolName: run.lastUnknownToolName,
|
|
3728
3829
|
idleSeconds,
|
|
3729
3830
|
adapterBusy: run.status === "running" ? run.adapterBusy : false,
|
|
3730
3831
|
processStatus,
|
|
@@ -3771,6 +3872,31 @@ function getRuntimeEventDedupeKey(event) {
|
|
|
3771
3872
|
function isRunVisibleToUser(run, params) {
|
|
3772
3873
|
return run.tenantKey === params.tenantKey && run.openId === params.openId;
|
|
3773
3874
|
}
|
|
3875
|
+
function getSessionOwner(chatUser) {
|
|
3876
|
+
return {
|
|
3877
|
+
tenantKey: chatUser.tenantKey,
|
|
3878
|
+
openId: chatUser.openId
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
function isSessionOwnedBy$1(session, owner) {
|
|
3882
|
+
return session.tenantKey === owner.tenantKey && session.userId === owner.openId;
|
|
3883
|
+
}
|
|
3884
|
+
async function getOwnedSession(messageStore, sessionId, chatUser) {
|
|
3885
|
+
return await messageStore.getSessionForOwner(sessionId, getSessionOwner(chatUser));
|
|
3886
|
+
}
|
|
3887
|
+
function logSessionDecision(params) {
|
|
3888
|
+
log$30.info("[session-audit] chat session access decision", {
|
|
3889
|
+
action: params.action,
|
|
3890
|
+
decision: params.decision,
|
|
3891
|
+
tenantKey: params.chatUser.tenantKey,
|
|
3892
|
+
userId: params.chatUser.userId,
|
|
3893
|
+
openId: params.chatUser.openId,
|
|
3894
|
+
sessionId: params.sessionId,
|
|
3895
|
+
requestId: params.requestId,
|
|
3896
|
+
traceId: params.traceId,
|
|
3897
|
+
reason: params.reason
|
|
3898
|
+
});
|
|
3899
|
+
}
|
|
3774
3900
|
function addRunClient(req, res, run) {
|
|
3775
3901
|
run.clients.add(res);
|
|
3776
3902
|
req.on("close", () => {
|
|
@@ -4009,6 +4135,232 @@ function buildRuntimeEventMessageMetadata(event, runtimeConfig) {
|
|
|
4009
4135
|
finalStatus: event.finalStatus
|
|
4010
4136
|
};
|
|
4011
4137
|
}
|
|
4138
|
+
async function handleSessionFileUpload(req, res, messageStore) {
|
|
4139
|
+
const chatUser = res.locals.chatUser;
|
|
4140
|
+
const sessionId = String(req.params.id);
|
|
4141
|
+
const requestId = getGatewayRequestId(req.headers);
|
|
4142
|
+
const traceId = getGatewayTraceId(req.headers, requestId);
|
|
4143
|
+
if (!await getOwnedSession(messageStore, sessionId, chatUser)) {
|
|
4144
|
+
logSessionDecision({
|
|
4145
|
+
action: "attach",
|
|
4146
|
+
decision: "denied",
|
|
4147
|
+
chatUser,
|
|
4148
|
+
sessionId,
|
|
4149
|
+
requestId,
|
|
4150
|
+
traceId,
|
|
4151
|
+
reason: "session_not_owned"
|
|
4152
|
+
});
|
|
4153
|
+
res.status(404).json({ error: "Session not found" });
|
|
4154
|
+
return;
|
|
4155
|
+
}
|
|
4156
|
+
logSessionDecision({
|
|
4157
|
+
action: "attach",
|
|
4158
|
+
decision: "allowed",
|
|
4159
|
+
chatUser,
|
|
4160
|
+
sessionId,
|
|
4161
|
+
requestId,
|
|
4162
|
+
traceId
|
|
4163
|
+
});
|
|
4164
|
+
if (!req.headers["content-type"]?.includes("multipart/form-data")) {
|
|
4165
|
+
res.status(415).json({ error: "Content-Type must be multipart/form-data" });
|
|
4166
|
+
return;
|
|
4167
|
+
}
|
|
4168
|
+
try {
|
|
4169
|
+
const file = await parseSessionFileUpload(req, sessionId);
|
|
4170
|
+
await messageStore.appendMessage({
|
|
4171
|
+
sessionId,
|
|
4172
|
+
role: "tool",
|
|
4173
|
+
content: JSON.stringify({
|
|
4174
|
+
runtimeEventType: "session-file-attached",
|
|
4175
|
+
file
|
|
4176
|
+
}),
|
|
4177
|
+
channel: "web",
|
|
4178
|
+
metadata: {
|
|
4179
|
+
requestId,
|
|
4180
|
+
traceId,
|
|
4181
|
+
runtimeEventType: "session-file-attached",
|
|
4182
|
+
fileName: file.fileName,
|
|
4183
|
+
relativePath: file.relativePath,
|
|
4184
|
+
mimeType: file.mimeType,
|
|
4185
|
+
size: file.size,
|
|
4186
|
+
attachments: [file]
|
|
4187
|
+
}
|
|
4188
|
+
});
|
|
4189
|
+
res.status(201).json({
|
|
4190
|
+
file,
|
|
4191
|
+
files: [file]
|
|
4192
|
+
});
|
|
4193
|
+
} catch (err) {
|
|
4194
|
+
const statusCode = err instanceof SessionFileUploadError ? err.statusCode : 500;
|
|
4195
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4196
|
+
log$30.warn("[sessions] session file upload failed", {
|
|
4197
|
+
tenantKey: chatUser.tenantKey,
|
|
4198
|
+
userId: chatUser.userId,
|
|
4199
|
+
openId: chatUser.openId,
|
|
4200
|
+
sessionId,
|
|
4201
|
+
requestId,
|
|
4202
|
+
traceId,
|
|
4203
|
+
statusCode,
|
|
4204
|
+
message
|
|
4205
|
+
});
|
|
4206
|
+
res.status(statusCode).json({ error: message });
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
function parseSessionFileUpload(req, sessionId) {
|
|
4210
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
4211
|
+
const busboy = Busboy({
|
|
4212
|
+
headers: req.headers,
|
|
4213
|
+
limits: {
|
|
4214
|
+
fileSize: getSessionFileUploadMaxBytes(),
|
|
4215
|
+
files: 1
|
|
4216
|
+
}
|
|
4217
|
+
});
|
|
4218
|
+
let metadata;
|
|
4219
|
+
let uploadPromise;
|
|
4220
|
+
let fileSeen = false;
|
|
4221
|
+
let settled = false;
|
|
4222
|
+
const fail = (error) => {
|
|
4223
|
+
if (settled) return;
|
|
4224
|
+
settled = true;
|
|
4225
|
+
rejectPromise(error);
|
|
4226
|
+
};
|
|
4227
|
+
busboy.on("field", (name, value) => {
|
|
4228
|
+
if (name !== "metadata") return;
|
|
4229
|
+
if (!value.trim()) return;
|
|
4230
|
+
try {
|
|
4231
|
+
const parsed = JSON.parse(value);
|
|
4232
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
4233
|
+
metadata = parsed;
|
|
4234
|
+
return;
|
|
4235
|
+
}
|
|
4236
|
+
fail(new SessionFileUploadError("metadata must be a JSON object"));
|
|
4237
|
+
} catch (err) {
|
|
4238
|
+
fail(new SessionFileUploadError(`metadata is not valid JSON: ${err instanceof Error ? err.message : String(err)}`));
|
|
4239
|
+
}
|
|
4240
|
+
});
|
|
4241
|
+
busboy.on("file", (_name, file, info) => {
|
|
4242
|
+
if (fileSeen) {
|
|
4243
|
+
file.resume();
|
|
4244
|
+
fail(new SessionFileUploadError("Only one file is supported per upload"));
|
|
4245
|
+
return;
|
|
4246
|
+
}
|
|
4247
|
+
fileSeen = true;
|
|
4248
|
+
uploadPromise = writeSessionFile(sessionId, {
|
|
4249
|
+
file,
|
|
4250
|
+
originalName: info.filename || "upload.bin",
|
|
4251
|
+
mimeType: info.mimeType || "application/octet-stream",
|
|
4252
|
+
metadata
|
|
4253
|
+
});
|
|
4254
|
+
});
|
|
4255
|
+
busboy.on("filesLimit", () => fail(new SessionFileUploadError("Only one file is supported per upload")));
|
|
4256
|
+
busboy.on("error", fail);
|
|
4257
|
+
busboy.on("finish", () => {
|
|
4258
|
+
if (settled) return;
|
|
4259
|
+
if (!fileSeen || !uploadPromise) {
|
|
4260
|
+
fail(new SessionFileUploadError("file is required"));
|
|
4261
|
+
return;
|
|
4262
|
+
}
|
|
4263
|
+
uploadPromise.then((file) => {
|
|
4264
|
+
if (settled) return;
|
|
4265
|
+
settled = true;
|
|
4266
|
+
resolvePromise(file);
|
|
4267
|
+
}, fail);
|
|
4268
|
+
});
|
|
4269
|
+
req.pipe(busboy);
|
|
4270
|
+
});
|
|
4271
|
+
}
|
|
4272
|
+
function writeSessionFile(sessionId, params) {
|
|
4273
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
4274
|
+
const uploadsDir = resolve(resolveAgentCwd(sessionId), "uploads");
|
|
4275
|
+
const fileName = buildSafeUploadFileName(params.originalName);
|
|
4276
|
+
const destination = resolve(uploadsDir, fileName);
|
|
4277
|
+
const relativePath = `uploads/${fileName}`;
|
|
4278
|
+
const maxBytes = getSessionFileUploadMaxBytes();
|
|
4279
|
+
let size = 0;
|
|
4280
|
+
let settled = false;
|
|
4281
|
+
const fail = (error) => {
|
|
4282
|
+
if (settled) return;
|
|
4283
|
+
settled = true;
|
|
4284
|
+
rejectPromise(error);
|
|
4285
|
+
};
|
|
4286
|
+
if (!isPathInside(uploadsDir, destination)) {
|
|
4287
|
+
fail(new SessionFileUploadError("Invalid upload path"));
|
|
4288
|
+
params.file.resume();
|
|
4289
|
+
return;
|
|
4290
|
+
}
|
|
4291
|
+
mkdir(uploadsDir, { recursive: true }).then(() => {
|
|
4292
|
+
const output = createWriteStream(destination, { flags: "wx" });
|
|
4293
|
+
params.file.on("data", (chunk) => {
|
|
4294
|
+
size += chunk.length;
|
|
4295
|
+
if (size > maxBytes) {
|
|
4296
|
+
output.destroy();
|
|
4297
|
+
fail(new SessionFileUploadError(`File exceeds maximum upload size of ${maxBytes} bytes`, 413));
|
|
4298
|
+
}
|
|
4299
|
+
});
|
|
4300
|
+
params.file.on("limit", () => {
|
|
4301
|
+
output.destroy();
|
|
4302
|
+
fail(new SessionFileUploadError(`File exceeds maximum upload size of ${maxBytes} bytes`, 413));
|
|
4303
|
+
});
|
|
4304
|
+
params.file.on("error", fail);
|
|
4305
|
+
output.on("error", fail);
|
|
4306
|
+
output.on("finish", () => {
|
|
4307
|
+
if (settled) return;
|
|
4308
|
+
settled = true;
|
|
4309
|
+
resolvePromise({
|
|
4310
|
+
sessionId,
|
|
4311
|
+
kind: "session-file",
|
|
4312
|
+
fileName,
|
|
4313
|
+
relativePath,
|
|
4314
|
+
uri: `session-file://${relativePath}`,
|
|
4315
|
+
mimeType: params.mimeType,
|
|
4316
|
+
size,
|
|
4317
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4318
|
+
metadata: params.metadata
|
|
4319
|
+
});
|
|
4320
|
+
});
|
|
4321
|
+
params.file.pipe(output);
|
|
4322
|
+
}, fail);
|
|
4323
|
+
});
|
|
4324
|
+
}
|
|
4325
|
+
function getSessionFileUploadMaxBytes() {
|
|
4326
|
+
const raw = process.env.LARKPAL_SESSION_FILE_MAX_UPLOAD_BYTES || process.env.LARKPAL_ARTIFACT_MAX_UPLOAD_BYTES;
|
|
4327
|
+
if (!raw) return 50 * 1024 * 1024;
|
|
4328
|
+
const parsed = Number.parseInt(raw, 10);
|
|
4329
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 50 * 1024 * 1024;
|
|
4330
|
+
}
|
|
4331
|
+
function buildSafeUploadFileName(originalName) {
|
|
4332
|
+
const base = basename(originalName).replace(/[\0\r\n\\/]/g, "_");
|
|
4333
|
+
const extension = extname(base).replace(/[^A-Za-z0-9.]/g, "").slice(0, 32);
|
|
4334
|
+
const stem = basename(base, extname(base)).replace(/[^A-Za-z0-9._-]/g, "_").replace(/^\.+$/, "").slice(0, 80) || "upload";
|
|
4335
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}-${stem}${extension}`;
|
|
4336
|
+
}
|
|
4337
|
+
function isPathInside(parent, child) {
|
|
4338
|
+
const path = relative(parent, child);
|
|
4339
|
+
return path !== "" && !path.startsWith("..") && !isAbsolute(path);
|
|
4340
|
+
}
|
|
4341
|
+
function buildToolResultPayload(result, resultSummary) {
|
|
4342
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) return { result: resultSummary };
|
|
4343
|
+
const record = result;
|
|
4344
|
+
return {
|
|
4345
|
+
rawType: record.rawType,
|
|
4346
|
+
isError: record.isError,
|
|
4347
|
+
content: record.content,
|
|
4348
|
+
resultSummary: record.resultSummary ?? resultSummary,
|
|
4349
|
+
truncated: record.truncated,
|
|
4350
|
+
originalSize: record.originalSize,
|
|
4351
|
+
result: resultSummary
|
|
4352
|
+
};
|
|
4353
|
+
}
|
|
4354
|
+
function readStringField(value, key) {
|
|
4355
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4356
|
+
const field = value[key];
|
|
4357
|
+
return typeof field === "string" ? field : void 0;
|
|
4358
|
+
}
|
|
4359
|
+
function readBooleanField(value, key) {
|
|
4360
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
4361
|
+
const field = value[key];
|
|
4362
|
+
return typeof field === "boolean" ? field : void 0;
|
|
4363
|
+
}
|
|
4012
4364
|
/**
|
|
4013
4365
|
* 创建 Chat 流式对话 + 历史查询路由
|
|
4014
4366
|
*
|
|
@@ -4025,7 +4377,8 @@ function createChatRouter(config) {
|
|
|
4025
4377
|
const router = Router();
|
|
4026
4378
|
const { processManager, messageStore } = config;
|
|
4027
4379
|
router.post("/api/chat/stream", chatAuthMiddleware, async (req, res) => {
|
|
4028
|
-
const
|
|
4380
|
+
const chatUser = res.locals.chatUser;
|
|
4381
|
+
const { userId, openId, tenantKey } = chatUser;
|
|
4029
4382
|
const body = req.body;
|
|
4030
4383
|
if (!tenantKey || !userId || !openId) {
|
|
4031
4384
|
res.status(401).json({ error: "Missing required chat identity: tenantKey, userId, openId" });
|
|
@@ -4034,7 +4387,48 @@ function createChatRouter(config) {
|
|
|
4034
4387
|
const requestId = getGatewayRequestId(req.headers);
|
|
4035
4388
|
const traceId = getGatewayTraceId(req.headers, requestId);
|
|
4036
4389
|
let sessionId = body.session_id;
|
|
4390
|
+
let streamSession = null;
|
|
4037
4391
|
const activeRun = sessionId ? getActiveChatRun(sessionId) : void 0;
|
|
4392
|
+
if (sessionId) {
|
|
4393
|
+
streamSession = await messageStore.getSession(sessionId);
|
|
4394
|
+
if (streamSession && !isSessionOwnedBy$1(streamSession, getSessionOwner(chatUser))) {
|
|
4395
|
+
logSessionDecision({
|
|
4396
|
+
action: "stream",
|
|
4397
|
+
decision: "denied",
|
|
4398
|
+
chatUser,
|
|
4399
|
+
sessionId,
|
|
4400
|
+
requestId,
|
|
4401
|
+
traceId,
|
|
4402
|
+
reason: "session_not_owned"
|
|
4403
|
+
});
|
|
4404
|
+
res.status(404).json({ error: "Session not found" });
|
|
4405
|
+
return;
|
|
4406
|
+
}
|
|
4407
|
+
if (!streamSession && activeRun && !isRunVisibleToUser(activeRun, {
|
|
4408
|
+
tenantKey,
|
|
4409
|
+
openId
|
|
4410
|
+
})) {
|
|
4411
|
+
logSessionDecision({
|
|
4412
|
+
action: "stream",
|
|
4413
|
+
decision: "denied",
|
|
4414
|
+
chatUser,
|
|
4415
|
+
sessionId,
|
|
4416
|
+
requestId,
|
|
4417
|
+
traceId,
|
|
4418
|
+
reason: "active_run_not_owned"
|
|
4419
|
+
});
|
|
4420
|
+
res.status(404).json({ error: "Session not found" });
|
|
4421
|
+
return;
|
|
4422
|
+
}
|
|
4423
|
+
if (streamSession || activeRun) logSessionDecision({
|
|
4424
|
+
action: "stream",
|
|
4425
|
+
decision: "allowed",
|
|
4426
|
+
chatUser,
|
|
4427
|
+
sessionId,
|
|
4428
|
+
requestId,
|
|
4429
|
+
traceId
|
|
4430
|
+
});
|
|
4431
|
+
}
|
|
4038
4432
|
if (activeRun) {
|
|
4039
4433
|
if (!isRunVisibleToUser(activeRun, {
|
|
4040
4434
|
tenantKey,
|
|
@@ -4057,6 +4451,22 @@ function createChatRouter(config) {
|
|
|
4057
4451
|
res.status(400).json({ error: "prompt is required and must be a string" });
|
|
4058
4452
|
return;
|
|
4059
4453
|
}
|
|
4454
|
+
if (sessionId && !streamSession) {
|
|
4455
|
+
await messageStore.createSession({
|
|
4456
|
+
id: sessionId,
|
|
4457
|
+
userId: openId,
|
|
4458
|
+
tenantKey,
|
|
4459
|
+
channel: "web"
|
|
4460
|
+
});
|
|
4461
|
+
logSessionDecision({
|
|
4462
|
+
action: "stream",
|
|
4463
|
+
decision: "allowed",
|
|
4464
|
+
chatUser,
|
|
4465
|
+
sessionId,
|
|
4466
|
+
requestId,
|
|
4467
|
+
traceId
|
|
4468
|
+
});
|
|
4469
|
+
}
|
|
4060
4470
|
const scenarioId = getScenarioId(body.options?.scenarioId, body.options?.metadata);
|
|
4061
4471
|
const attachments = getRunInputValue(body.attachments, body.options?.attachments, body.options?.metadata?.attachments);
|
|
4062
4472
|
const sourceArtifacts = getRunInputValue(body.sourceArtifacts, body.source_artifacts, body.options?.sourceArtifacts, body.options?.source_artifacts, body.options?.metadata?.sourceArtifacts, body.options?.metadata?.source_artifacts);
|
|
@@ -4077,6 +4487,14 @@ function createChatRouter(config) {
|
|
|
4077
4487
|
tenantKey,
|
|
4078
4488
|
channel: "web"
|
|
4079
4489
|
})).id;
|
|
4490
|
+
logSessionDecision({
|
|
4491
|
+
action: "create",
|
|
4492
|
+
decision: "allowed",
|
|
4493
|
+
chatUser,
|
|
4494
|
+
sessionId,
|
|
4495
|
+
requestId,
|
|
4496
|
+
traceId
|
|
4497
|
+
});
|
|
4080
4498
|
log$30.info("[stream] 自动创建新会话", {
|
|
4081
4499
|
userId: openId,
|
|
4082
4500
|
sessionId
|
|
@@ -4293,8 +4711,12 @@ function createChatRouter(config) {
|
|
|
4293
4711
|
if (run.status !== "running") return;
|
|
4294
4712
|
recordRunEvent(run, "tool-result");
|
|
4295
4713
|
const resultSummary = summarizeForAudit(result);
|
|
4714
|
+
const toolName = readStringField(result, "toolName") || run.lastToolName;
|
|
4715
|
+
const isError = readBooleanField(result, "isError");
|
|
4716
|
+
if (isError) run.lastToolError = resultSummary;
|
|
4296
4717
|
if (runtimeConfig.requestContext) logChatAudit(buildAuditEvent(runtimeConfig.requestContext, "tool_call_finished", {
|
|
4297
4718
|
toolCallId: toolUseId,
|
|
4719
|
+
toolName,
|
|
4298
4720
|
resultSummary,
|
|
4299
4721
|
metadata: buildToolResultAuditMetadata(result)
|
|
4300
4722
|
}));
|
|
@@ -4303,7 +4725,8 @@ function createChatRouter(config) {
|
|
|
4303
4725
|
role: "tool",
|
|
4304
4726
|
content: JSON.stringify({
|
|
4305
4727
|
toolUseId,
|
|
4306
|
-
|
|
4728
|
+
toolName,
|
|
4729
|
+
...buildToolResultPayload(result, resultSummary)
|
|
4307
4730
|
}),
|
|
4308
4731
|
channel: "web",
|
|
4309
4732
|
metadata: {
|
|
@@ -4313,12 +4736,33 @@ function createChatRouter(config) {
|
|
|
4313
4736
|
scenarioId,
|
|
4314
4737
|
runtimeEventType: "tool-result",
|
|
4315
4738
|
toolCallId: toolUseId,
|
|
4316
|
-
|
|
4739
|
+
toolName,
|
|
4740
|
+
toolResult: resultSummary,
|
|
4741
|
+
isError
|
|
4317
4742
|
}
|
|
4318
4743
|
}, "[stream] 保存 tool result 消息失败");
|
|
4319
4744
|
broadcastRunEvent(run, "tool-result", {
|
|
4320
4745
|
toolUseId,
|
|
4321
|
-
|
|
4746
|
+
toolName,
|
|
4747
|
+
...buildToolResultPayload(result, resultSummary)
|
|
4748
|
+
});
|
|
4749
|
+
},
|
|
4750
|
+
onUnknownToolUse: (toolName, toolUseId) => {
|
|
4751
|
+
if (run.status !== "running") return;
|
|
4752
|
+
run.unknownToolCallCount = (run.unknownToolCallCount ?? 0) + 1;
|
|
4753
|
+
run.lastUnknownToolName = toolName;
|
|
4754
|
+
run.lastToolError = {
|
|
4755
|
+
isError: true,
|
|
4756
|
+
reasonCode: "UNKNOWN_TOOL_CALL",
|
|
4757
|
+
toolName,
|
|
4758
|
+
toolCallId: toolUseId
|
|
4759
|
+
};
|
|
4760
|
+
recordRunEvent(run, "unknown-tool-use", { toolName });
|
|
4761
|
+
broadcastRunEvent(run, "runtime-event", {
|
|
4762
|
+
type: "unknown-tool-use",
|
|
4763
|
+
toolName,
|
|
4764
|
+
toolUseId,
|
|
4765
|
+
unknownToolCallCount: run.unknownToolCallCount
|
|
4322
4766
|
});
|
|
4323
4767
|
},
|
|
4324
4768
|
onToolProgress: (toolName, elapsedSeconds) => {
|
|
@@ -4559,6 +5003,29 @@ function createChatRouter(config) {
|
|
|
4559
5003
|
router.post("/api/chat/sessions/:id/cancel-active-run", chatAuthMiddleware, async (req, res) => {
|
|
4560
5004
|
const chatUser = res.locals.chatUser;
|
|
4561
5005
|
const sessionId = String(req.params.id);
|
|
5006
|
+
const requestId = getGatewayRequestId(req.headers);
|
|
5007
|
+
const traceId = getGatewayTraceId(req.headers, requestId);
|
|
5008
|
+
if (!await getOwnedSession(messageStore, sessionId, chatUser)) {
|
|
5009
|
+
logSessionDecision({
|
|
5010
|
+
action: "cancel",
|
|
5011
|
+
decision: "denied",
|
|
5012
|
+
chatUser,
|
|
5013
|
+
sessionId,
|
|
5014
|
+
requestId,
|
|
5015
|
+
traceId,
|
|
5016
|
+
reason: "session_not_owned"
|
|
5017
|
+
});
|
|
5018
|
+
res.status(404).json({ error: "Session not found" });
|
|
5019
|
+
return;
|
|
5020
|
+
}
|
|
5021
|
+
logSessionDecision({
|
|
5022
|
+
action: "cancel",
|
|
5023
|
+
decision: "allowed",
|
|
5024
|
+
chatUser,
|
|
5025
|
+
sessionId,
|
|
5026
|
+
requestId,
|
|
5027
|
+
traceId
|
|
5028
|
+
});
|
|
4562
5029
|
const run = chatRunsBySession.get(sessionId);
|
|
4563
5030
|
if (!run) {
|
|
4564
5031
|
res.json({
|
|
@@ -4596,6 +5063,9 @@ function createChatRouter(config) {
|
|
|
4596
5063
|
});
|
|
4597
5064
|
}
|
|
4598
5065
|
});
|
|
5066
|
+
router.post("/api/chat/sessions/:id/files", chatAuthMiddleware, async (req, res) => {
|
|
5067
|
+
await handleSessionFileUpload(req, res, messageStore);
|
|
5068
|
+
});
|
|
4599
5069
|
router.get("/api/chat/history", chatAuthMiddleware, async (req, res) => {
|
|
4600
5070
|
const chatUser = res.locals.chatUser;
|
|
4601
5071
|
const sessionId = req.query.session_id;
|
|
@@ -4605,6 +5075,8 @@ function createChatRouter(config) {
|
|
|
4605
5075
|
}
|
|
4606
5076
|
const cursor = req.query.cursor;
|
|
4607
5077
|
const limit = parseInt(req.query.limit, 10) || 50;
|
|
5078
|
+
const requestId = getGatewayRequestId(req.headers);
|
|
5079
|
+
const traceId = getGatewayTraceId(req.headers, requestId);
|
|
4608
5080
|
log$30.info("[history] 查询会话历史", {
|
|
4609
5081
|
userId: chatUser.userId,
|
|
4610
5082
|
sessionId,
|
|
@@ -4612,6 +5084,27 @@ function createChatRouter(config) {
|
|
|
4612
5084
|
limit
|
|
4613
5085
|
});
|
|
4614
5086
|
try {
|
|
5087
|
+
if (!await getOwnedSession(messageStore, sessionId, chatUser)) {
|
|
5088
|
+
logSessionDecision({
|
|
5089
|
+
action: "history",
|
|
5090
|
+
decision: "denied",
|
|
5091
|
+
chatUser,
|
|
5092
|
+
sessionId,
|
|
5093
|
+
requestId,
|
|
5094
|
+
traceId,
|
|
5095
|
+
reason: "session_not_owned"
|
|
5096
|
+
});
|
|
5097
|
+
res.status(404).json({ error: "Session not found" });
|
|
5098
|
+
return;
|
|
5099
|
+
}
|
|
5100
|
+
logSessionDecision({
|
|
5101
|
+
action: "history",
|
|
5102
|
+
decision: "allowed",
|
|
5103
|
+
chatUser,
|
|
5104
|
+
sessionId,
|
|
5105
|
+
requestId,
|
|
5106
|
+
traceId
|
|
5107
|
+
});
|
|
4615
5108
|
const result = await messageStore.getMessages(sessionId, {
|
|
4616
5109
|
cursor,
|
|
4617
5110
|
limit
|
|
@@ -4647,6 +5140,29 @@ function createChatRouter(config) {
|
|
|
4647
5140
|
res.status(400).json({ error: "session_id is required" });
|
|
4648
5141
|
return;
|
|
4649
5142
|
}
|
|
5143
|
+
const requestId = getGatewayRequestId(req.headers);
|
|
5144
|
+
const traceId = getGatewayTraceId(req.headers, requestId);
|
|
5145
|
+
if (!await getOwnedSession(messageStore, sessionId, chatUser)) {
|
|
5146
|
+
logSessionDecision({
|
|
5147
|
+
action: "runs",
|
|
5148
|
+
decision: "denied",
|
|
5149
|
+
chatUser,
|
|
5150
|
+
sessionId,
|
|
5151
|
+
requestId,
|
|
5152
|
+
traceId,
|
|
5153
|
+
reason: "session_not_owned"
|
|
5154
|
+
});
|
|
5155
|
+
res.status(404).json({ error: "Session not found" });
|
|
5156
|
+
return;
|
|
5157
|
+
}
|
|
5158
|
+
logSessionDecision({
|
|
5159
|
+
action: "runs",
|
|
5160
|
+
decision: "allowed",
|
|
5161
|
+
chatUser,
|
|
5162
|
+
sessionId,
|
|
5163
|
+
requestId,
|
|
5164
|
+
traceId
|
|
5165
|
+
});
|
|
4650
5166
|
const run = chatRunsBySession.get(sessionId);
|
|
4651
5167
|
if (!run || !isRunVisibleToUser(run, {
|
|
4652
5168
|
tenantKey: chatUser.tenantKey,
|
|
@@ -4673,7 +5189,7 @@ function createChatRouter(config) {
|
|
|
4673
5189
|
const chatUser = res.locals.chatUser;
|
|
4674
5190
|
log$30.info("[sessions] 列出用户会话", { userId: chatUser.userId });
|
|
4675
5191
|
try {
|
|
4676
|
-
const sessions = await messageStore.listSessions(chatUser.openId);
|
|
5192
|
+
const sessions = await messageStore.listSessions(chatUser.openId, chatUser.tenantKey);
|
|
4677
5193
|
res.json({ sessions });
|
|
4678
5194
|
} catch (err) {
|
|
4679
5195
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -4717,7 +5233,29 @@ function createChatRouter(config) {
|
|
|
4717
5233
|
sessionId
|
|
4718
5234
|
});
|
|
4719
5235
|
try {
|
|
4720
|
-
|
|
5236
|
+
const requestId = getGatewayRequestId(req.headers);
|
|
5237
|
+
const traceId = getGatewayTraceId(req.headers, requestId);
|
|
5238
|
+
if (!await messageStore.deleteSessionForOwner(sessionId, getSessionOwner(chatUser))) {
|
|
5239
|
+
logSessionDecision({
|
|
5240
|
+
action: "delete",
|
|
5241
|
+
decision: "denied",
|
|
5242
|
+
chatUser,
|
|
5243
|
+
sessionId,
|
|
5244
|
+
requestId,
|
|
5245
|
+
traceId,
|
|
5246
|
+
reason: "session_not_owned"
|
|
5247
|
+
});
|
|
5248
|
+
res.status(404).json({ error: "Session not found" });
|
|
5249
|
+
return;
|
|
5250
|
+
}
|
|
5251
|
+
logSessionDecision({
|
|
5252
|
+
action: "delete",
|
|
5253
|
+
decision: "allowed",
|
|
5254
|
+
chatUser,
|
|
5255
|
+
sessionId,
|
|
5256
|
+
requestId,
|
|
5257
|
+
traceId
|
|
5258
|
+
});
|
|
4721
5259
|
chatRunsBySession.delete(sessionId);
|
|
4722
5260
|
res.json({ success: true });
|
|
4723
5261
|
} catch (err) {
|
|
@@ -7935,6 +8473,11 @@ var SessionMessageStore = class {
|
|
|
7935
8473
|
* 创建新会话
|
|
7936
8474
|
*/
|
|
7937
8475
|
async createSession(session) {
|
|
8476
|
+
const existing = await this.getSession(session.id);
|
|
8477
|
+
if (existing) {
|
|
8478
|
+
if (existing.userId === session.userId && existing.tenantKey === session.tenantKey) return existing;
|
|
8479
|
+
throw new Error(`Session ${session.id} already exists for a different owner`);
|
|
8480
|
+
}
|
|
7938
8481
|
const now = Date.now();
|
|
7939
8482
|
const fullSession = {
|
|
7940
8483
|
...session,
|
|
@@ -7946,9 +8489,9 @@ var SessionMessageStore = class {
|
|
|
7946
8489
|
sessionId: fullSession.id,
|
|
7947
8490
|
userId: fullSession.userId
|
|
7948
8491
|
});
|
|
7949
|
-
const sessions = await this.readUserSessions(fullSession.userId);
|
|
8492
|
+
const sessions = await this.readUserSessions(fullSession.userId, fullSession.tenantKey);
|
|
7950
8493
|
sessions.push(fullSession);
|
|
7951
|
-
await this.writeUserSessions(fullSession.userId, sessions);
|
|
8494
|
+
await this.writeUserSessions(fullSession.userId, sessions, fullSession.tenantKey);
|
|
7952
8495
|
return fullSession;
|
|
7953
8496
|
}
|
|
7954
8497
|
/**
|
|
@@ -7975,9 +8518,21 @@ var SessionMessageStore = class {
|
|
|
7975
8518
|
/**
|
|
7976
8519
|
* 列出指定用户的所有会话
|
|
7977
8520
|
*/
|
|
7978
|
-
async listSessions(userId) {
|
|
7979
|
-
log$24.debug("列出用户会话", {
|
|
7980
|
-
|
|
8521
|
+
async listSessions(userId, tenantKey) {
|
|
8522
|
+
log$24.debug("列出用户会话", {
|
|
8523
|
+
userId,
|
|
8524
|
+
tenantKey
|
|
8525
|
+
});
|
|
8526
|
+
const sessions = await this.readUserSessions(userId, tenantKey);
|
|
8527
|
+
if (!tenantKey) return sessions;
|
|
8528
|
+
const ownedLegacySessions = (await this.readUserSessions(userId)).filter((session) => session.userId === userId && session.tenantKey === tenantKey);
|
|
8529
|
+
const seen = new Set(sessions.map((session) => session.id));
|
|
8530
|
+
return [...sessions, ...ownedLegacySessions.filter((session) => !seen.has(session.id))];
|
|
8531
|
+
}
|
|
8532
|
+
async getSessionForOwner(sessionId, owner) {
|
|
8533
|
+
const session = await this.getSession(sessionId);
|
|
8534
|
+
if (!session) return null;
|
|
8535
|
+
return isSessionOwnedBy(session, owner) ? session : null;
|
|
7981
8536
|
}
|
|
7982
8537
|
/**
|
|
7983
8538
|
* 删除指定会话(同时删除消息文件)
|
|
@@ -8007,6 +8562,12 @@ var SessionMessageStore = class {
|
|
|
8007
8562
|
throw err;
|
|
8008
8563
|
}
|
|
8009
8564
|
}
|
|
8565
|
+
async deleteSessionForOwner(sessionId, owner) {
|
|
8566
|
+
const session = await this.getSession(sessionId);
|
|
8567
|
+
if (!session || !isSessionOwnedBy(session, owner)) return false;
|
|
8568
|
+
await this.deleteSession(sessionId);
|
|
8569
|
+
return true;
|
|
8570
|
+
}
|
|
8010
8571
|
/**
|
|
8011
8572
|
* 追加消息到会话
|
|
8012
8573
|
* 同时更新会话的 updatedAt 和 messageCount
|
|
@@ -8096,21 +8657,21 @@ var SessionMessageStore = class {
|
|
|
8096
8657
|
/**
|
|
8097
8658
|
* 获取用户会话索引文件路径
|
|
8098
8659
|
*/
|
|
8099
|
-
getUserSessionFilePath(userId) {
|
|
8100
|
-
return join(this.sessionsDir, `${userId}.json`);
|
|
8660
|
+
getUserSessionFilePath(userId, tenantKey) {
|
|
8661
|
+
return join(this.sessionsDir, `${buildOwnerKey(userId, tenantKey)}.json`);
|
|
8101
8662
|
}
|
|
8102
8663
|
/**
|
|
8103
8664
|
* 读取用户的会话列表
|
|
8104
8665
|
*/
|
|
8105
|
-
async readUserSessions(userId) {
|
|
8106
|
-
const filePath = this.getUserSessionFilePath(userId);
|
|
8666
|
+
async readUserSessions(userId, tenantKey) {
|
|
8667
|
+
const filePath = this.getUserSessionFilePath(userId, tenantKey);
|
|
8107
8668
|
return await this.readJsonFile(filePath) || [];
|
|
8108
8669
|
}
|
|
8109
8670
|
/**
|
|
8110
8671
|
* 写入用户的会话列表
|
|
8111
8672
|
*/
|
|
8112
|
-
async writeUserSessions(userId, sessions) {
|
|
8113
|
-
const filePath = this.getUserSessionFilePath(userId);
|
|
8673
|
+
async writeUserSessions(userId, sessions, tenantKey) {
|
|
8674
|
+
const filePath = this.getUserSessionFilePath(userId, tenantKey);
|
|
8114
8675
|
await this.writeJsonFile(filePath, sessions);
|
|
8115
8676
|
}
|
|
8116
8677
|
/**
|
|
@@ -8210,6 +8771,12 @@ var SessionMessageStore = class {
|
|
|
8210
8771
|
} catch {}
|
|
8211
8772
|
}
|
|
8212
8773
|
};
|
|
8774
|
+
function isSessionOwnedBy(session, owner) {
|
|
8775
|
+
return session.userId === owner.openId && session.tenantKey === owner.tenantKey;
|
|
8776
|
+
}
|
|
8777
|
+
function buildOwnerKey(userId, tenantKey) {
|
|
8778
|
+
return (tenantKey ? `${tenantKey}__${userId}` : userId).replace(/[^A-Za-z0-9_.:-]/g, "_");
|
|
8779
|
+
}
|
|
8213
8780
|
//#endregion
|
|
8214
8781
|
//#region src/messaging/inbound/user-name-cache-store.ts
|
|
8215
8782
|
/**
|