@vibe-lark/larkpal 0.1.72 → 0.1.73

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.
Files changed (2) hide show
  1. package/dist/main.mjs +439 -15
  2. 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";
@@ -3679,6 +3679,13 @@ function summarizeString(value) {
3679
3679
  * DELETE /api/chat/sessions/:id — 删除会话
3680
3680
  */
3681
3681
  const log$30 = larkLogger("chat/stream");
3682
+ var SessionFileUploadError = class extends Error {
3683
+ constructor(message, statusCode = 400) {
3684
+ super(message);
3685
+ this.statusCode = statusCode;
3686
+ this.name = "SessionFileUploadError";
3687
+ }
3688
+ };
3682
3689
  const chatRunsBySession = /* @__PURE__ */ new Map();
3683
3690
  const DEFAULT_STALE_RUN_IDLE_MS = 1800 * 1e3;
3684
3691
  /** 统一写 SSE 格式数据到响应流 */
@@ -3771,6 +3778,31 @@ function getRuntimeEventDedupeKey(event) {
3771
3778
  function isRunVisibleToUser(run, params) {
3772
3779
  return run.tenantKey === params.tenantKey && run.openId === params.openId;
3773
3780
  }
3781
+ function getSessionOwner(chatUser) {
3782
+ return {
3783
+ tenantKey: chatUser.tenantKey,
3784
+ openId: chatUser.openId
3785
+ };
3786
+ }
3787
+ function isSessionOwnedBy$1(session, owner) {
3788
+ return session.tenantKey === owner.tenantKey && session.userId === owner.openId;
3789
+ }
3790
+ async function getOwnedSession(messageStore, sessionId, chatUser) {
3791
+ return await messageStore.getSessionForOwner(sessionId, getSessionOwner(chatUser));
3792
+ }
3793
+ function logSessionDecision(params) {
3794
+ log$30.info("[session-audit] chat session access decision", {
3795
+ action: params.action,
3796
+ decision: params.decision,
3797
+ tenantKey: params.chatUser.tenantKey,
3798
+ userId: params.chatUser.userId,
3799
+ openId: params.chatUser.openId,
3800
+ sessionId: params.sessionId,
3801
+ requestId: params.requestId,
3802
+ traceId: params.traceId,
3803
+ reason: params.reason
3804
+ });
3805
+ }
3774
3806
  function addRunClient(req, res, run) {
3775
3807
  run.clients.add(res);
3776
3808
  req.on("close", () => {
@@ -4009,6 +4041,209 @@ function buildRuntimeEventMessageMetadata(event, runtimeConfig) {
4009
4041
  finalStatus: event.finalStatus
4010
4042
  };
4011
4043
  }
4044
+ async function handleSessionFileUpload(req, res, messageStore) {
4045
+ const chatUser = res.locals.chatUser;
4046
+ const sessionId = String(req.params.id);
4047
+ const requestId = getGatewayRequestId(req.headers);
4048
+ const traceId = getGatewayTraceId(req.headers, requestId);
4049
+ if (!await getOwnedSession(messageStore, sessionId, chatUser)) {
4050
+ logSessionDecision({
4051
+ action: "attach",
4052
+ decision: "denied",
4053
+ chatUser,
4054
+ sessionId,
4055
+ requestId,
4056
+ traceId,
4057
+ reason: "session_not_owned"
4058
+ });
4059
+ res.status(404).json({ error: "Session not found" });
4060
+ return;
4061
+ }
4062
+ logSessionDecision({
4063
+ action: "attach",
4064
+ decision: "allowed",
4065
+ chatUser,
4066
+ sessionId,
4067
+ requestId,
4068
+ traceId
4069
+ });
4070
+ if (!req.headers["content-type"]?.includes("multipart/form-data")) {
4071
+ res.status(415).json({ error: "Content-Type must be multipart/form-data" });
4072
+ return;
4073
+ }
4074
+ try {
4075
+ const file = await parseSessionFileUpload(req, sessionId);
4076
+ await messageStore.appendMessage({
4077
+ sessionId,
4078
+ role: "tool",
4079
+ content: JSON.stringify({
4080
+ runtimeEventType: "session-file-attached",
4081
+ file
4082
+ }),
4083
+ channel: "web",
4084
+ metadata: {
4085
+ requestId,
4086
+ traceId,
4087
+ runtimeEventType: "session-file-attached",
4088
+ fileName: file.fileName,
4089
+ relativePath: file.relativePath,
4090
+ mimeType: file.mimeType,
4091
+ size: file.size,
4092
+ attachments: [file]
4093
+ }
4094
+ });
4095
+ res.status(201).json({
4096
+ file,
4097
+ files: [file]
4098
+ });
4099
+ } catch (err) {
4100
+ const statusCode = err instanceof SessionFileUploadError ? err.statusCode : 500;
4101
+ const message = err instanceof Error ? err.message : String(err);
4102
+ log$30.warn("[sessions] session file upload failed", {
4103
+ tenantKey: chatUser.tenantKey,
4104
+ userId: chatUser.userId,
4105
+ openId: chatUser.openId,
4106
+ sessionId,
4107
+ requestId,
4108
+ traceId,
4109
+ statusCode,
4110
+ message
4111
+ });
4112
+ res.status(statusCode).json({ error: message });
4113
+ }
4114
+ }
4115
+ function parseSessionFileUpload(req, sessionId) {
4116
+ return new Promise((resolvePromise, rejectPromise) => {
4117
+ const busboy = Busboy({
4118
+ headers: req.headers,
4119
+ limits: {
4120
+ fileSize: getSessionFileUploadMaxBytes(),
4121
+ files: 1
4122
+ }
4123
+ });
4124
+ let metadata;
4125
+ let uploadPromise;
4126
+ let fileSeen = false;
4127
+ let settled = false;
4128
+ const fail = (error) => {
4129
+ if (settled) return;
4130
+ settled = true;
4131
+ rejectPromise(error);
4132
+ };
4133
+ busboy.on("field", (name, value) => {
4134
+ if (name !== "metadata") return;
4135
+ if (!value.trim()) return;
4136
+ try {
4137
+ const parsed = JSON.parse(value);
4138
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4139
+ metadata = parsed;
4140
+ return;
4141
+ }
4142
+ fail(new SessionFileUploadError("metadata must be a JSON object"));
4143
+ } catch (err) {
4144
+ fail(new SessionFileUploadError(`metadata is not valid JSON: ${err instanceof Error ? err.message : String(err)}`));
4145
+ }
4146
+ });
4147
+ busboy.on("file", (_name, file, info) => {
4148
+ if (fileSeen) {
4149
+ file.resume();
4150
+ fail(new SessionFileUploadError("Only one file is supported per upload"));
4151
+ return;
4152
+ }
4153
+ fileSeen = true;
4154
+ uploadPromise = writeSessionFile(sessionId, {
4155
+ file,
4156
+ originalName: info.filename || "upload.bin",
4157
+ mimeType: info.mimeType || "application/octet-stream",
4158
+ metadata
4159
+ });
4160
+ });
4161
+ busboy.on("filesLimit", () => fail(new SessionFileUploadError("Only one file is supported per upload")));
4162
+ busboy.on("error", fail);
4163
+ busboy.on("finish", () => {
4164
+ if (settled) return;
4165
+ if (!fileSeen || !uploadPromise) {
4166
+ fail(new SessionFileUploadError("file is required"));
4167
+ return;
4168
+ }
4169
+ uploadPromise.then((file) => {
4170
+ if (settled) return;
4171
+ settled = true;
4172
+ resolvePromise(file);
4173
+ }, fail);
4174
+ });
4175
+ req.pipe(busboy);
4176
+ });
4177
+ }
4178
+ function writeSessionFile(sessionId, params) {
4179
+ return new Promise((resolvePromise, rejectPromise) => {
4180
+ const uploadsDir = resolve(resolveAgentCwd(sessionId), "uploads");
4181
+ const fileName = buildSafeUploadFileName(params.originalName);
4182
+ const destination = resolve(uploadsDir, fileName);
4183
+ const relativePath = `uploads/${fileName}`;
4184
+ const maxBytes = getSessionFileUploadMaxBytes();
4185
+ let size = 0;
4186
+ let settled = false;
4187
+ const fail = (error) => {
4188
+ if (settled) return;
4189
+ settled = true;
4190
+ rejectPromise(error);
4191
+ };
4192
+ if (!isPathInside(uploadsDir, destination)) {
4193
+ fail(new SessionFileUploadError("Invalid upload path"));
4194
+ params.file.resume();
4195
+ return;
4196
+ }
4197
+ mkdir(uploadsDir, { recursive: true }).then(() => {
4198
+ const output = createWriteStream(destination, { flags: "wx" });
4199
+ params.file.on("data", (chunk) => {
4200
+ size += chunk.length;
4201
+ if (size > maxBytes) {
4202
+ output.destroy();
4203
+ fail(new SessionFileUploadError(`File exceeds maximum upload size of ${maxBytes} bytes`, 413));
4204
+ }
4205
+ });
4206
+ params.file.on("limit", () => {
4207
+ output.destroy();
4208
+ fail(new SessionFileUploadError(`File exceeds maximum upload size of ${maxBytes} bytes`, 413));
4209
+ });
4210
+ params.file.on("error", fail);
4211
+ output.on("error", fail);
4212
+ output.on("finish", () => {
4213
+ if (settled) return;
4214
+ settled = true;
4215
+ resolvePromise({
4216
+ sessionId,
4217
+ kind: "session-file",
4218
+ fileName,
4219
+ relativePath,
4220
+ uri: `session-file://${relativePath}`,
4221
+ mimeType: params.mimeType,
4222
+ size,
4223
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4224
+ metadata: params.metadata
4225
+ });
4226
+ });
4227
+ params.file.pipe(output);
4228
+ }, fail);
4229
+ });
4230
+ }
4231
+ function getSessionFileUploadMaxBytes() {
4232
+ const raw = process.env.LARKPAL_SESSION_FILE_MAX_UPLOAD_BYTES || process.env.LARKPAL_ARTIFACT_MAX_UPLOAD_BYTES;
4233
+ if (!raw) return 50 * 1024 * 1024;
4234
+ const parsed = Number.parseInt(raw, 10);
4235
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 50 * 1024 * 1024;
4236
+ }
4237
+ function buildSafeUploadFileName(originalName) {
4238
+ const base = basename(originalName).replace(/[\0\r\n\\/]/g, "_");
4239
+ const extension = extname(base).replace(/[^A-Za-z0-9.]/g, "").slice(0, 32);
4240
+ const stem = basename(base, extname(base)).replace(/[^A-Za-z0-9._-]/g, "_").replace(/^\.+$/, "").slice(0, 80) || "upload";
4241
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}-${stem}${extension}`;
4242
+ }
4243
+ function isPathInside(parent, child) {
4244
+ const path = relative(parent, child);
4245
+ return path !== "" && !path.startsWith("..") && !isAbsolute(path);
4246
+ }
4012
4247
  /**
4013
4248
  * 创建 Chat 流式对话 + 历史查询路由
4014
4249
  *
@@ -4025,7 +4260,8 @@ function createChatRouter(config) {
4025
4260
  const router = Router();
4026
4261
  const { processManager, messageStore } = config;
4027
4262
  router.post("/api/chat/stream", chatAuthMiddleware, async (req, res) => {
4028
- const { userId, openId, tenantKey } = res.locals.chatUser;
4263
+ const chatUser = res.locals.chatUser;
4264
+ const { userId, openId, tenantKey } = chatUser;
4029
4265
  const body = req.body;
4030
4266
  if (!tenantKey || !userId || !openId) {
4031
4267
  res.status(401).json({ error: "Missing required chat identity: tenantKey, userId, openId" });
@@ -4034,7 +4270,48 @@ function createChatRouter(config) {
4034
4270
  const requestId = getGatewayRequestId(req.headers);
4035
4271
  const traceId = getGatewayTraceId(req.headers, requestId);
4036
4272
  let sessionId = body.session_id;
4273
+ let streamSession = null;
4037
4274
  const activeRun = sessionId ? getActiveChatRun(sessionId) : void 0;
4275
+ if (sessionId) {
4276
+ streamSession = await messageStore.getSession(sessionId);
4277
+ if (streamSession && !isSessionOwnedBy$1(streamSession, getSessionOwner(chatUser))) {
4278
+ logSessionDecision({
4279
+ action: "stream",
4280
+ decision: "denied",
4281
+ chatUser,
4282
+ sessionId,
4283
+ requestId,
4284
+ traceId,
4285
+ reason: "session_not_owned"
4286
+ });
4287
+ res.status(404).json({ error: "Session not found" });
4288
+ return;
4289
+ }
4290
+ if (!streamSession && activeRun && !isRunVisibleToUser(activeRun, {
4291
+ tenantKey,
4292
+ openId
4293
+ })) {
4294
+ logSessionDecision({
4295
+ action: "stream",
4296
+ decision: "denied",
4297
+ chatUser,
4298
+ sessionId,
4299
+ requestId,
4300
+ traceId,
4301
+ reason: "active_run_not_owned"
4302
+ });
4303
+ res.status(404).json({ error: "Session not found" });
4304
+ return;
4305
+ }
4306
+ if (streamSession || activeRun) logSessionDecision({
4307
+ action: "stream",
4308
+ decision: "allowed",
4309
+ chatUser,
4310
+ sessionId,
4311
+ requestId,
4312
+ traceId
4313
+ });
4314
+ }
4038
4315
  if (activeRun) {
4039
4316
  if (!isRunVisibleToUser(activeRun, {
4040
4317
  tenantKey,
@@ -4057,6 +4334,22 @@ function createChatRouter(config) {
4057
4334
  res.status(400).json({ error: "prompt is required and must be a string" });
4058
4335
  return;
4059
4336
  }
4337
+ if (sessionId && !streamSession) {
4338
+ await messageStore.createSession({
4339
+ id: sessionId,
4340
+ userId: openId,
4341
+ tenantKey,
4342
+ channel: "web"
4343
+ });
4344
+ logSessionDecision({
4345
+ action: "stream",
4346
+ decision: "allowed",
4347
+ chatUser,
4348
+ sessionId,
4349
+ requestId,
4350
+ traceId
4351
+ });
4352
+ }
4060
4353
  const scenarioId = getScenarioId(body.options?.scenarioId, body.options?.metadata);
4061
4354
  const attachments = getRunInputValue(body.attachments, body.options?.attachments, body.options?.metadata?.attachments);
4062
4355
  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 +4370,14 @@ function createChatRouter(config) {
4077
4370
  tenantKey,
4078
4371
  channel: "web"
4079
4372
  })).id;
4373
+ logSessionDecision({
4374
+ action: "create",
4375
+ decision: "allowed",
4376
+ chatUser,
4377
+ sessionId,
4378
+ requestId,
4379
+ traceId
4380
+ });
4080
4381
  log$30.info("[stream] 自动创建新会话", {
4081
4382
  userId: openId,
4082
4383
  sessionId
@@ -4559,6 +4860,29 @@ function createChatRouter(config) {
4559
4860
  router.post("/api/chat/sessions/:id/cancel-active-run", chatAuthMiddleware, async (req, res) => {
4560
4861
  const chatUser = res.locals.chatUser;
4561
4862
  const sessionId = String(req.params.id);
4863
+ const requestId = getGatewayRequestId(req.headers);
4864
+ const traceId = getGatewayTraceId(req.headers, requestId);
4865
+ if (!await getOwnedSession(messageStore, sessionId, chatUser)) {
4866
+ logSessionDecision({
4867
+ action: "cancel",
4868
+ decision: "denied",
4869
+ chatUser,
4870
+ sessionId,
4871
+ requestId,
4872
+ traceId,
4873
+ reason: "session_not_owned"
4874
+ });
4875
+ res.status(404).json({ error: "Session not found" });
4876
+ return;
4877
+ }
4878
+ logSessionDecision({
4879
+ action: "cancel",
4880
+ decision: "allowed",
4881
+ chatUser,
4882
+ sessionId,
4883
+ requestId,
4884
+ traceId
4885
+ });
4562
4886
  const run = chatRunsBySession.get(sessionId);
4563
4887
  if (!run) {
4564
4888
  res.json({
@@ -4596,6 +4920,9 @@ function createChatRouter(config) {
4596
4920
  });
4597
4921
  }
4598
4922
  });
4923
+ router.post("/api/chat/sessions/:id/files", chatAuthMiddleware, async (req, res) => {
4924
+ await handleSessionFileUpload(req, res, messageStore);
4925
+ });
4599
4926
  router.get("/api/chat/history", chatAuthMiddleware, async (req, res) => {
4600
4927
  const chatUser = res.locals.chatUser;
4601
4928
  const sessionId = req.query.session_id;
@@ -4605,6 +4932,8 @@ function createChatRouter(config) {
4605
4932
  }
4606
4933
  const cursor = req.query.cursor;
4607
4934
  const limit = parseInt(req.query.limit, 10) || 50;
4935
+ const requestId = getGatewayRequestId(req.headers);
4936
+ const traceId = getGatewayTraceId(req.headers, requestId);
4608
4937
  log$30.info("[history] 查询会话历史", {
4609
4938
  userId: chatUser.userId,
4610
4939
  sessionId,
@@ -4612,6 +4941,27 @@ function createChatRouter(config) {
4612
4941
  limit
4613
4942
  });
4614
4943
  try {
4944
+ if (!await getOwnedSession(messageStore, sessionId, chatUser)) {
4945
+ logSessionDecision({
4946
+ action: "history",
4947
+ decision: "denied",
4948
+ chatUser,
4949
+ sessionId,
4950
+ requestId,
4951
+ traceId,
4952
+ reason: "session_not_owned"
4953
+ });
4954
+ res.status(404).json({ error: "Session not found" });
4955
+ return;
4956
+ }
4957
+ logSessionDecision({
4958
+ action: "history",
4959
+ decision: "allowed",
4960
+ chatUser,
4961
+ sessionId,
4962
+ requestId,
4963
+ traceId
4964
+ });
4615
4965
  const result = await messageStore.getMessages(sessionId, {
4616
4966
  cursor,
4617
4967
  limit
@@ -4647,6 +4997,29 @@ function createChatRouter(config) {
4647
4997
  res.status(400).json({ error: "session_id is required" });
4648
4998
  return;
4649
4999
  }
5000
+ const requestId = getGatewayRequestId(req.headers);
5001
+ const traceId = getGatewayTraceId(req.headers, requestId);
5002
+ if (!await getOwnedSession(messageStore, sessionId, chatUser)) {
5003
+ logSessionDecision({
5004
+ action: "runs",
5005
+ decision: "denied",
5006
+ chatUser,
5007
+ sessionId,
5008
+ requestId,
5009
+ traceId,
5010
+ reason: "session_not_owned"
5011
+ });
5012
+ res.status(404).json({ error: "Session not found" });
5013
+ return;
5014
+ }
5015
+ logSessionDecision({
5016
+ action: "runs",
5017
+ decision: "allowed",
5018
+ chatUser,
5019
+ sessionId,
5020
+ requestId,
5021
+ traceId
5022
+ });
4650
5023
  const run = chatRunsBySession.get(sessionId);
4651
5024
  if (!run || !isRunVisibleToUser(run, {
4652
5025
  tenantKey: chatUser.tenantKey,
@@ -4673,7 +5046,7 @@ function createChatRouter(config) {
4673
5046
  const chatUser = res.locals.chatUser;
4674
5047
  log$30.info("[sessions] 列出用户会话", { userId: chatUser.userId });
4675
5048
  try {
4676
- const sessions = await messageStore.listSessions(chatUser.openId);
5049
+ const sessions = await messageStore.listSessions(chatUser.openId, chatUser.tenantKey);
4677
5050
  res.json({ sessions });
4678
5051
  } catch (err) {
4679
5052
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -4717,7 +5090,29 @@ function createChatRouter(config) {
4717
5090
  sessionId
4718
5091
  });
4719
5092
  try {
4720
- await messageStore.deleteSession(sessionId);
5093
+ const requestId = getGatewayRequestId(req.headers);
5094
+ const traceId = getGatewayTraceId(req.headers, requestId);
5095
+ if (!await messageStore.deleteSessionForOwner(sessionId, getSessionOwner(chatUser))) {
5096
+ logSessionDecision({
5097
+ action: "delete",
5098
+ decision: "denied",
5099
+ chatUser,
5100
+ sessionId,
5101
+ requestId,
5102
+ traceId,
5103
+ reason: "session_not_owned"
5104
+ });
5105
+ res.status(404).json({ error: "Session not found" });
5106
+ return;
5107
+ }
5108
+ logSessionDecision({
5109
+ action: "delete",
5110
+ decision: "allowed",
5111
+ chatUser,
5112
+ sessionId,
5113
+ requestId,
5114
+ traceId
5115
+ });
4721
5116
  chatRunsBySession.delete(sessionId);
4722
5117
  res.json({ success: true });
4723
5118
  } catch (err) {
@@ -7935,6 +8330,11 @@ var SessionMessageStore = class {
7935
8330
  * 创建新会话
7936
8331
  */
7937
8332
  async createSession(session) {
8333
+ const existing = await this.getSession(session.id);
8334
+ if (existing) {
8335
+ if (existing.userId === session.userId && existing.tenantKey === session.tenantKey) return existing;
8336
+ throw new Error(`Session ${session.id} already exists for a different owner`);
8337
+ }
7938
8338
  const now = Date.now();
7939
8339
  const fullSession = {
7940
8340
  ...session,
@@ -7946,9 +8346,9 @@ var SessionMessageStore = class {
7946
8346
  sessionId: fullSession.id,
7947
8347
  userId: fullSession.userId
7948
8348
  });
7949
- const sessions = await this.readUserSessions(fullSession.userId);
8349
+ const sessions = await this.readUserSessions(fullSession.userId, fullSession.tenantKey);
7950
8350
  sessions.push(fullSession);
7951
- await this.writeUserSessions(fullSession.userId, sessions);
8351
+ await this.writeUserSessions(fullSession.userId, sessions, fullSession.tenantKey);
7952
8352
  return fullSession;
7953
8353
  }
7954
8354
  /**
@@ -7975,9 +8375,21 @@ var SessionMessageStore = class {
7975
8375
  /**
7976
8376
  * 列出指定用户的所有会话
7977
8377
  */
7978
- async listSessions(userId) {
7979
- log$24.debug("列出用户会话", { userId });
7980
- return await this.readUserSessions(userId);
8378
+ async listSessions(userId, tenantKey) {
8379
+ log$24.debug("列出用户会话", {
8380
+ userId,
8381
+ tenantKey
8382
+ });
8383
+ const sessions = await this.readUserSessions(userId, tenantKey);
8384
+ if (!tenantKey) return sessions;
8385
+ const ownedLegacySessions = (await this.readUserSessions(userId)).filter((session) => session.userId === userId && session.tenantKey === tenantKey);
8386
+ const seen = new Set(sessions.map((session) => session.id));
8387
+ return [...sessions, ...ownedLegacySessions.filter((session) => !seen.has(session.id))];
8388
+ }
8389
+ async getSessionForOwner(sessionId, owner) {
8390
+ const session = await this.getSession(sessionId);
8391
+ if (!session) return null;
8392
+ return isSessionOwnedBy(session, owner) ? session : null;
7981
8393
  }
7982
8394
  /**
7983
8395
  * 删除指定会话(同时删除消息文件)
@@ -8007,6 +8419,12 @@ var SessionMessageStore = class {
8007
8419
  throw err;
8008
8420
  }
8009
8421
  }
8422
+ async deleteSessionForOwner(sessionId, owner) {
8423
+ const session = await this.getSession(sessionId);
8424
+ if (!session || !isSessionOwnedBy(session, owner)) return false;
8425
+ await this.deleteSession(sessionId);
8426
+ return true;
8427
+ }
8010
8428
  /**
8011
8429
  * 追加消息到会话
8012
8430
  * 同时更新会话的 updatedAt 和 messageCount
@@ -8096,21 +8514,21 @@ var SessionMessageStore = class {
8096
8514
  /**
8097
8515
  * 获取用户会话索引文件路径
8098
8516
  */
8099
- getUserSessionFilePath(userId) {
8100
- return join(this.sessionsDir, `${userId}.json`);
8517
+ getUserSessionFilePath(userId, tenantKey) {
8518
+ return join(this.sessionsDir, `${buildOwnerKey(userId, tenantKey)}.json`);
8101
8519
  }
8102
8520
  /**
8103
8521
  * 读取用户的会话列表
8104
8522
  */
8105
- async readUserSessions(userId) {
8106
- const filePath = this.getUserSessionFilePath(userId);
8523
+ async readUserSessions(userId, tenantKey) {
8524
+ const filePath = this.getUserSessionFilePath(userId, tenantKey);
8107
8525
  return await this.readJsonFile(filePath) || [];
8108
8526
  }
8109
8527
  /**
8110
8528
  * 写入用户的会话列表
8111
8529
  */
8112
- async writeUserSessions(userId, sessions) {
8113
- const filePath = this.getUserSessionFilePath(userId);
8530
+ async writeUserSessions(userId, sessions, tenantKey) {
8531
+ const filePath = this.getUserSessionFilePath(userId, tenantKey);
8114
8532
  await this.writeJsonFile(filePath, sessions);
8115
8533
  }
8116
8534
  /**
@@ -8210,6 +8628,12 @@ var SessionMessageStore = class {
8210
8628
  } catch {}
8211
8629
  }
8212
8630
  };
8631
+ function isSessionOwnedBy(session, owner) {
8632
+ return session.userId === owner.openId && session.tenantKey === owner.tenantKey;
8633
+ }
8634
+ function buildOwnerKey(userId, tenantKey) {
8635
+ return (tenantKey ? `${tenantKey}__${userId}` : userId).replace(/[^A-Za-z0-9_.:-]/g, "_");
8636
+ }
8213
8637
  //#endregion
8214
8638
  //#region src/messaging/inbound/user-name-cache-store.ts
8215
8639
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-lark/larkpal",
3
- "version": "0.1.72",
3
+ "version": "0.1.73",
4
4
  "description": "LarkPal - Lark/Feishu bot service",
5
5
  "type": "module",
6
6
  "main": "./dist/main.mjs",