@vibe-lark/larkpal 0.1.63 → 0.1.65

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 (3) hide show
  1. package/bin/larkpal.js +0 -0
  2. package/dist/main.mjs +255 -153
  3. package/package.json +16 -15
package/bin/larkpal.js CHANGED
File without changes
package/dist/main.mjs CHANGED
@@ -6,7 +6,7 @@ import * as path$2 from "node:path";
6
6
  import path, { basename, dirname, extname, join, resolve } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import "dotenv/config";
9
- import { access, lstat, mkdir, open, readFile, readdir, readlink, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
9
+ import { access, copyFile, lstat, mkdir, open, readFile, readdir, readlink, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
10
10
  import { homedir, tmpdir } from "node:os";
11
11
  import { execFileSync, spawn } from "node:child_process";
12
12
  import { createHash, randomUUID } from "node:crypto";
@@ -18,10 +18,10 @@ import { createCipheriv, createDecipheriv, createHmac, randomBytes } from "crypt
18
18
  import path$1 from "path";
19
19
  import os from "os";
20
20
  import { EventEmitter } from "node:events";
21
+ import { pipeline } from "node:stream/promises";
22
+ import { PassThrough, Readable, Transform } from "node:stream";
21
23
  import express, { Router } from "express";
22
24
  import Busboy from "busboy";
23
- import { PassThrough, Readable, Transform } from "node:stream";
24
- import { pipeline } from "node:stream/promises";
25
25
  import { EventEmitter as EventEmitter$1 } from "events";
26
26
  import cron from "node-cron";
27
27
  import * as Lark from "@larksuiteoapi/node-sdk";
@@ -2619,6 +2619,165 @@ function getLarkpalMcpServerCommand() {
2619
2619
  };
2620
2620
  }
2621
2621
  //#endregion
2622
+ //#region src/gateway/artifact-store.ts
2623
+ const DEFAULT_ARTIFACT_TTL_MS = 4320 * 60 * 1e3;
2624
+ const DEFAULT_MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
2625
+ var ArtifactUploadError = class extends Error {
2626
+ constructor(message, statusCode = 400) {
2627
+ super(message);
2628
+ this.statusCode = statusCode;
2629
+ this.name = "ArtifactUploadError";
2630
+ }
2631
+ };
2632
+ var GatewayArtifactStore = class {
2633
+ rootDir;
2634
+ maxUploadBytes;
2635
+ ttlMs;
2636
+ constructor(params) {
2637
+ const workspaceRoot = params?.workspaceRoot || resolveWorkspaceRoot();
2638
+ this.rootDir = join(workspaceRoot, ".artifacts");
2639
+ this.maxUploadBytes = params?.maxUploadBytes ?? readPositiveIntEnv("LARKPAL_ARTIFACT_MAX_UPLOAD_BYTES", DEFAULT_MAX_UPLOAD_BYTES);
2640
+ this.ttlMs = params?.ttlMs ?? readPositiveIntEnv("LARKPAL_ARTIFACT_TTL_MS", DEFAULT_ARTIFACT_TTL_MS);
2641
+ }
2642
+ async create(input) {
2643
+ validateContextId(input.sessionId, "sessionId");
2644
+ validateContextId(input.owner.tenantKey, "tenantKey");
2645
+ const artifactId = `art_${v4().replace(/-/g, "")}`;
2646
+ const createdAtMs = Date.now();
2647
+ const createdAt = new Date(createdAtMs).toISOString();
2648
+ const expiresAt = new Date(createdAtMs + this.ttlMs).toISOString();
2649
+ const artifactDir = this.getArtifactDir(input.owner.tenantKey, input.sessionId, artifactId);
2650
+ const blobPath = join(artifactDir, "blob");
2651
+ const tmpBlobPath = join(artifactDir, `.blob.${process.pid}.${Date.now()}.tmp`);
2652
+ await mkdir(artifactDir, { recursive: true });
2653
+ const hash = createHash("sha256");
2654
+ let size = 0;
2655
+ const metering = new Transform({ transform: (chunk, _encoding, callback) => {
2656
+ size += chunk.length;
2657
+ if (size > this.maxUploadBytes) {
2658
+ callback(new ArtifactUploadError(`Artifact exceeds max upload size ${this.maxUploadBytes} bytes`, 413));
2659
+ return;
2660
+ }
2661
+ hash.update(chunk);
2662
+ callback(null, chunk);
2663
+ } });
2664
+ try {
2665
+ await pipeline(input.stream, metering, createWriteStream(tmpBlobPath));
2666
+ await rename(tmpBlobPath, blobPath);
2667
+ } catch (err) {
2668
+ await rm(artifactDir, {
2669
+ recursive: true,
2670
+ force: true
2671
+ }).catch(() => void 0);
2672
+ throw err;
2673
+ }
2674
+ const metadata = {
2675
+ artifactId,
2676
+ sessionId: input.sessionId,
2677
+ tenantKey: input.owner.tenantKey,
2678
+ userId: input.owner.userId,
2679
+ openId: input.owner.openId,
2680
+ kind: "workspace-file",
2681
+ uri: `artifact://${artifactId}`,
2682
+ fileName: sanitizeFileName(input.fileName),
2683
+ mimeType: normalizeMimeType(input.mimeType),
2684
+ size,
2685
+ contentHash: `sha256:${hash.digest("hex")}`,
2686
+ createdAt,
2687
+ expiresAt,
2688
+ metadata: input.metadata,
2689
+ blobPath
2690
+ };
2691
+ await writeJson(join(artifactDir, "meta.json"), metadata);
2692
+ return toPublicRef(metadata);
2693
+ }
2694
+ async list(params) {
2695
+ validateContextId(params.sessionId, "sessionId");
2696
+ validateContextId(params.owner.tenantKey, "tenantKey");
2697
+ const sessionDir = this.getSessionDir(params.owner.tenantKey, params.sessionId);
2698
+ let entries;
2699
+ try {
2700
+ entries = await readdir(sessionDir);
2701
+ } catch {
2702
+ return [];
2703
+ }
2704
+ const results = [];
2705
+ for (const entry of entries) {
2706
+ if (!entry.startsWith("art_")) continue;
2707
+ const metadata = await readArtifactMetadata(join(sessionDir, entry, "meta.json"));
2708
+ if (!metadata) continue;
2709
+ if (!isOwnedBy(metadata, params.owner, params.sessionId)) continue;
2710
+ if (Date.parse(metadata.expiresAt) <= Date.now()) continue;
2711
+ results.push(toPublicRef(metadata));
2712
+ }
2713
+ return results.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
2714
+ }
2715
+ async statBlob(ref, owner) {
2716
+ const metadata = await readArtifactMetadata(this.getMetadataPath(owner.tenantKey, ref.sessionId, ref.artifactId));
2717
+ if (!metadata || !isOwnedBy(metadata, owner, ref.sessionId)) return null;
2718
+ const blobStat = await stat(metadata.blobPath).catch(() => null);
2719
+ return blobStat ? {
2720
+ path: metadata.blobPath,
2721
+ size: blobStat.size
2722
+ } : null;
2723
+ }
2724
+ async resolveBlob(params) {
2725
+ validateContextId(params.sessionId, "sessionId");
2726
+ validateContextId(params.owner.tenantKey, "tenantKey");
2727
+ validateContextId(params.artifactId, "artifactId");
2728
+ const metadata = await readArtifactMetadata(this.getMetadataPath(params.owner.tenantKey, params.sessionId, params.artifactId));
2729
+ if (!metadata || !isOwnedBy(metadata, params.owner, params.sessionId)) return null;
2730
+ if (Date.parse(metadata.expiresAt) <= Date.now()) return null;
2731
+ if (!await stat(metadata.blobPath).catch(() => null)) return null;
2732
+ return {
2733
+ ref: toPublicRef(metadata),
2734
+ blobPath: metadata.blobPath
2735
+ };
2736
+ }
2737
+ getSessionDir(tenantKey, sessionId) {
2738
+ return join(this.rootDir, tenantKey, sessionId);
2739
+ }
2740
+ getArtifactDir(tenantKey, sessionId, artifactId) {
2741
+ return join(this.getSessionDir(tenantKey, sessionId), artifactId);
2742
+ }
2743
+ getMetadataPath(tenantKey, sessionId, artifactId) {
2744
+ return join(this.getArtifactDir(tenantKey, sessionId, artifactId), "meta.json");
2745
+ }
2746
+ };
2747
+ function readPositiveIntEnv(name, fallback) {
2748
+ const raw = process.env[name];
2749
+ if (!raw) return fallback;
2750
+ const parsed = Number.parseInt(raw, 10);
2751
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
2752
+ }
2753
+ function validateContextId(value, fieldName) {
2754
+ if (!/^[A-Za-z0-9_.:-]{1,200}$/.test(value)) throw new ArtifactUploadError(`Invalid ${fieldName}`, 400);
2755
+ }
2756
+ function sanitizeFileName(fileName) {
2757
+ return fileName.replace(/[\\/\0\r\n]/g, "_").trim() || "upload.bin";
2758
+ }
2759
+ function normalizeMimeType(mimeType) {
2760
+ return mimeType?.trim() || "application/octet-stream";
2761
+ }
2762
+ function toPublicRef(metadata) {
2763
+ const { tenantKey: _tenantKey, userId: _userId, openId: _openId, blobPath: _blobPath, ...ref } = metadata;
2764
+ return ref;
2765
+ }
2766
+ function isOwnedBy(metadata, owner, sessionId) {
2767
+ return metadata.tenantKey === owner.tenantKey && metadata.userId === owner.userId && metadata.openId === owner.openId && metadata.sessionId === sessionId;
2768
+ }
2769
+ async function readArtifactMetadata(filePath) {
2770
+ try {
2771
+ return JSON.parse(await readFile(filePath, "utf-8"));
2772
+ } catch {
2773
+ return null;
2774
+ }
2775
+ }
2776
+ async function writeJson(filePath, data) {
2777
+ await mkdir(dirname(filePath), { recursive: true });
2778
+ await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
2779
+ }
2780
+ //#endregion
2622
2781
  //#region src/runtime/larkpal-agent-factory.ts
2623
2782
  /**
2624
2783
  * LarkpalAgent RuntimeAdapter 工厂
@@ -2766,6 +2925,7 @@ async function createLarkpalAgentAdapter() {
2766
2925
  });
2767
2926
  const { LarkpalAgentAdapter, ToolRegistry } = await import("@vibe-lark/larkpal-agent");
2768
2927
  const registry = new ToolRegistry();
2928
+ const visualMaxBatchSize = parseInt(process.env.LARKPAL_AGENT_VISUAL_MAX_BATCH_SIZE || "3", 10);
2769
2929
  const adapter = new LarkpalAgentAdapter({
2770
2930
  llm,
2771
2931
  systemPrompt,
@@ -2774,8 +2934,9 @@ async function createLarkpalAgentAdapter() {
2774
2934
  registry,
2775
2935
  scenarioManifestDirs,
2776
2936
  scenarioProviderConfig,
2937
+ artifactResolvers: [createGatewayArtifactResolver()],
2777
2938
  visualRuntime: {
2778
- maxBatchSize: parseInt(process.env.LARKPAL_AGENT_VISUAL_MAX_BATCH_SIZE || "3", 10),
2939
+ maxBatchSize: visualMaxBatchSize,
2779
2940
  allowedDirs: [process.env.LARK_CLI_WORKDIR || "/tmp/ai-knowledge-lark-cli", "/tmp"].filter(Boolean)
2780
2941
  }
2781
2942
  });
@@ -2799,6 +2960,51 @@ async function createLarkpalAgentAdapter() {
2799
2960
  else log$31.warn("未配置 LARKPAL_AGENT_MCP_SERVER_URL,Agent 无远程工具可用");
2800
2961
  return wrapLarkpalAgentAdapter(adapter);
2801
2962
  }
2963
+ function createGatewayArtifactResolver(params) {
2964
+ const store = params?.store || new GatewayArtifactStore();
2965
+ return {
2966
+ name: "larkpal-gateway-artifact-store",
2967
+ canResolve(artifact) {
2968
+ return getArtifactId(artifact) !== void 0;
2969
+ },
2970
+ async resolve(artifact, context) {
2971
+ const artifactId = getArtifactId(artifact);
2972
+ const tenantKey = context.tenantKey || context.authHeaders?.["x-tenant-key"];
2973
+ const userId = context.userId || context.authHeaders?.["x-user-id"];
2974
+ const openId = context.authHeaders?.["x-open-id"] || userId;
2975
+ if (!artifactId || !tenantKey || !userId || !openId) return void 0;
2976
+ const resolved = await store.resolveBlob({
2977
+ sessionId: context.sessionId,
2978
+ artifactId,
2979
+ owner: {
2980
+ tenantKey,
2981
+ userId,
2982
+ openId
2983
+ }
2984
+ });
2985
+ if (!resolved) return void 0;
2986
+ const targetDir = join(context.cwd, "source-artifacts", artifactId);
2987
+ const fileName = sanitizeWorkspaceFileName(resolved.ref.fileName || artifact.fileName || artifactId);
2988
+ const targetPath = join(targetDir, fileName);
2989
+ await mkdir(targetDir, { recursive: true });
2990
+ await copyFile(resolved.blobPath, targetPath);
2991
+ return {
2992
+ artifactId,
2993
+ kind: "workspace-file",
2994
+ path: targetPath,
2995
+ fileName,
2996
+ mimeType: resolved.ref.mimeType ?? artifact.mimeType,
2997
+ size: resolved.ref.size ?? artifact.size,
2998
+ contentHash: resolved.ref.contentHash ?? artifact.contentHash,
2999
+ source: artifact,
3000
+ refs: {
3001
+ uri: resolved.ref.uri,
3002
+ gatewayArtifactId: resolved.ref.artifactId
3003
+ }
3004
+ };
3005
+ }
3006
+ };
3007
+ }
2802
3008
  function wrapLarkpalAgentAdapter(adapter) {
2803
3009
  return {
2804
3010
  get name() {
@@ -2877,6 +3083,17 @@ function mergeSourceArtifacts(existing, imageArtifacts) {
2877
3083
  if (Array.isArray(existing)) return [...existing, ...imageArtifacts];
2878
3084
  return [existing, ...imageArtifacts];
2879
3085
  }
3086
+ function getArtifactId(artifact) {
3087
+ const artifactId = typeof artifact.artifactId === "string" ? artifact.artifactId.trim() : "";
3088
+ if (artifactId.startsWith("art_")) return artifactId;
3089
+ const uri = typeof artifact.uri === "string" ? artifact.uri.trim() : "";
3090
+ if (!uri.startsWith("artifact://")) return void 0;
3091
+ const idFromUri = uri.slice(11).split(/[/?#]/, 1)[0]?.trim();
3092
+ return idFromUri?.startsWith("art_") ? idFromUri : void 0;
3093
+ }
3094
+ function sanitizeWorkspaceFileName(fileName) {
3095
+ return basename(fileName).replace(/[\\/\0\r\n]/g, "_").trim() || "artifact.bin";
3096
+ }
2880
3097
  //#endregion
2881
3098
  //#region src/routing/session-router.ts
2882
3099
  /**
@@ -3216,6 +3433,7 @@ const MAX_SUMMARY_STRING_LENGTH = 200;
3216
3433
  const PROTECTED_CONTEXT_HEADERS = new Set([
3217
3434
  "x-tenant-key",
3218
3435
  "x-user-id",
3436
+ "x-open-id",
3219
3437
  "x-session-id",
3220
3438
  "x-conversation-id",
3221
3439
  "x-trace-id",
@@ -3258,6 +3476,7 @@ function buildAgentRuntimeConfig(input) {
3258
3476
  const authHeaders = buildAuthHeaders({
3259
3477
  tenantKey,
3260
3478
  userId,
3479
+ openId: input.identity?.openId,
3261
3480
  sessionId,
3262
3481
  conversationId,
3263
3482
  traceId,
@@ -3420,6 +3639,7 @@ function buildAuthHeaders(params) {
3420
3639
  const headers = {
3421
3640
  "x-tenant-key": params.tenantKey,
3422
3641
  "x-user-id": params.userId,
3642
+ ...params.openId ? { "x-open-id": params.openId } : {},
3423
3643
  "x-session-id": params.sessionId,
3424
3644
  "x-conversation-id": params.conversationId,
3425
3645
  "x-trace-id": params.traceId,
@@ -3557,6 +3777,30 @@ function serializeChatRun(run) {
3557
3777
  staleReason: run.staleReason
3558
3778
  };
3559
3779
  }
3780
+ function serializeRuntimeProcessInfo(processInfo, processManager) {
3781
+ return {
3782
+ ...processInfo,
3783
+ startedAt: processInfo.startedAt.toISOString(),
3784
+ lastActiveAt: processInfo.lastActiveAt.toISOString(),
3785
+ busy: processManager.isSessionBusy?.(processInfo.sessionId) ?? processInfo.status === "running"
3786
+ };
3787
+ }
3788
+ function buildOrphanedRunDiagnostic(sessionId, processManager) {
3789
+ const processInfo = processManager.getProcessInfo(sessionId);
3790
+ const adapterBusy = processManager.isSessionBusy?.(sessionId) ?? processInfo?.status === "running";
3791
+ if (!processInfo && !adapterBusy) return null;
3792
+ return {
3793
+ runId: null,
3794
+ sessionId,
3795
+ status: adapterBusy ? "orphaned" : "inconsistent",
3796
+ finalStatus: adapterBusy ? "orphaned" : "inconsistent",
3797
+ reasonCode: "RUN_REGISTRY_MISSING",
3798
+ reason: "Runtime process exists or reports busy, but gateway chat run registry has no visible run for this session.",
3799
+ processStatus: processInfo?.status,
3800
+ adapterBusy,
3801
+ process: processInfo ? serializeRuntimeProcessInfo(processInfo, processManager) : void 0
3802
+ };
3803
+ }
3560
3804
  function getRuntimeEventDedupeKey(event) {
3561
3805
  const record = event;
3562
3806
  const eventId = record.eventId;
@@ -4245,12 +4489,14 @@ function createChatRouter(config) {
4245
4489
  openId: chatUser.openId
4246
4490
  }) ? run : void 0;
4247
4491
  if (visibleRun) await reconcileRunWithAdapter(visibleRun, messageStore, processManager);
4492
+ const runDiagnostic = visibleRun ? null : buildOrphanedRunDiagnostic(sessionId, processManager);
4248
4493
  res.json({
4249
4494
  messages: result.messages,
4250
4495
  nextCursor: result.nextCursor,
4251
4496
  sessionId,
4252
4497
  activeRun: visibleRun?.status === "running" ? serializeChatRun(visibleRun) : null,
4253
- latestRun: visibleRun ? serializeChatRun(visibleRun) : null
4498
+ latestRun: visibleRun ? serializeChatRun(visibleRun) : runDiagnostic,
4499
+ runDiagnostic
4254
4500
  });
4255
4501
  } catch (err) {
4256
4502
  const errorMessage = err instanceof Error ? err.message : String(err);
@@ -4273,10 +4519,12 @@ function createChatRouter(config) {
4273
4519
  tenantKey: chatUser.tenantKey,
4274
4520
  openId: chatUser.openId
4275
4521
  })) {
4522
+ const runDiagnostic = buildOrphanedRunDiagnostic(sessionId, processManager);
4276
4523
  res.json({
4277
4524
  sessionId,
4278
- run: null,
4279
- activeRun: null
4525
+ run: runDiagnostic,
4526
+ activeRun: null,
4527
+ runDiagnostic
4280
4528
  });
4281
4529
  return;
4282
4530
  }
@@ -4999,152 +5247,6 @@ async function sendCallback(callbackUrl, taskId, status, result, error) {
4999
5247
  }
5000
5248
  }
5001
5249
  //#endregion
5002
- //#region src/gateway/artifact-store.ts
5003
- const DEFAULT_ARTIFACT_TTL_MS = 4320 * 60 * 1e3;
5004
- const DEFAULT_MAX_UPLOAD_BYTES = 50 * 1024 * 1024;
5005
- var ArtifactUploadError = class extends Error {
5006
- constructor(message, statusCode = 400) {
5007
- super(message);
5008
- this.statusCode = statusCode;
5009
- this.name = "ArtifactUploadError";
5010
- }
5011
- };
5012
- var GatewayArtifactStore = class {
5013
- rootDir;
5014
- maxUploadBytes;
5015
- ttlMs;
5016
- constructor(params) {
5017
- const workspaceRoot = params?.workspaceRoot || resolveWorkspaceRoot();
5018
- this.rootDir = join(workspaceRoot, ".artifacts");
5019
- this.maxUploadBytes = params?.maxUploadBytes ?? readPositiveIntEnv("LARKPAL_ARTIFACT_MAX_UPLOAD_BYTES", DEFAULT_MAX_UPLOAD_BYTES);
5020
- this.ttlMs = params?.ttlMs ?? readPositiveIntEnv("LARKPAL_ARTIFACT_TTL_MS", DEFAULT_ARTIFACT_TTL_MS);
5021
- }
5022
- async create(input) {
5023
- validateContextId(input.sessionId, "sessionId");
5024
- validateContextId(input.owner.tenantKey, "tenantKey");
5025
- const artifactId = `art_${v4().replace(/-/g, "")}`;
5026
- const createdAtMs = Date.now();
5027
- const createdAt = new Date(createdAtMs).toISOString();
5028
- const expiresAt = new Date(createdAtMs + this.ttlMs).toISOString();
5029
- const artifactDir = this.getArtifactDir(input.owner.tenantKey, input.sessionId, artifactId);
5030
- const blobPath = join(artifactDir, "blob");
5031
- const tmpBlobPath = join(artifactDir, `.blob.${process.pid}.${Date.now()}.tmp`);
5032
- await mkdir(artifactDir, { recursive: true });
5033
- const hash = createHash("sha256");
5034
- let size = 0;
5035
- const metering = new Transform({ transform: (chunk, _encoding, callback) => {
5036
- size += chunk.length;
5037
- if (size > this.maxUploadBytes) {
5038
- callback(new ArtifactUploadError(`Artifact exceeds max upload size ${this.maxUploadBytes} bytes`, 413));
5039
- return;
5040
- }
5041
- hash.update(chunk);
5042
- callback(null, chunk);
5043
- } });
5044
- try {
5045
- await pipeline(input.stream, metering, createWriteStream(tmpBlobPath));
5046
- await rename(tmpBlobPath, blobPath);
5047
- } catch (err) {
5048
- await rm(artifactDir, {
5049
- recursive: true,
5050
- force: true
5051
- }).catch(() => void 0);
5052
- throw err;
5053
- }
5054
- const metadata = {
5055
- artifactId,
5056
- sessionId: input.sessionId,
5057
- tenantKey: input.owner.tenantKey,
5058
- userId: input.owner.userId,
5059
- openId: input.owner.openId,
5060
- kind: "workspace-file",
5061
- uri: `artifact://${artifactId}`,
5062
- fileName: sanitizeFileName(input.fileName),
5063
- mimeType: normalizeMimeType(input.mimeType),
5064
- size,
5065
- contentHash: `sha256:${hash.digest("hex")}`,
5066
- createdAt,
5067
- expiresAt,
5068
- metadata: input.metadata,
5069
- blobPath
5070
- };
5071
- await writeJson(join(artifactDir, "meta.json"), metadata);
5072
- return toPublicRef(metadata);
5073
- }
5074
- async list(params) {
5075
- validateContextId(params.sessionId, "sessionId");
5076
- validateContextId(params.owner.tenantKey, "tenantKey");
5077
- const sessionDir = this.getSessionDir(params.owner.tenantKey, params.sessionId);
5078
- let entries;
5079
- try {
5080
- entries = await readdir(sessionDir);
5081
- } catch {
5082
- return [];
5083
- }
5084
- const results = [];
5085
- for (const entry of entries) {
5086
- if (!entry.startsWith("art_")) continue;
5087
- const metadata = await readArtifactMetadata(join(sessionDir, entry, "meta.json"));
5088
- if (!metadata) continue;
5089
- if (!isOwnedBy(metadata, params.owner, params.sessionId)) continue;
5090
- if (Date.parse(metadata.expiresAt) <= Date.now()) continue;
5091
- results.push(toPublicRef(metadata));
5092
- }
5093
- return results.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
5094
- }
5095
- async statBlob(ref, owner) {
5096
- const metadata = await readArtifactMetadata(this.getMetadataPath(owner.tenantKey, ref.sessionId, ref.artifactId));
5097
- if (!metadata || !isOwnedBy(metadata, owner, ref.sessionId)) return null;
5098
- const blobStat = await stat(metadata.blobPath).catch(() => null);
5099
- return blobStat ? {
5100
- path: metadata.blobPath,
5101
- size: blobStat.size
5102
- } : null;
5103
- }
5104
- getSessionDir(tenantKey, sessionId) {
5105
- return join(this.rootDir, tenantKey, sessionId);
5106
- }
5107
- getArtifactDir(tenantKey, sessionId, artifactId) {
5108
- return join(this.getSessionDir(tenantKey, sessionId), artifactId);
5109
- }
5110
- getMetadataPath(tenantKey, sessionId, artifactId) {
5111
- return join(this.getArtifactDir(tenantKey, sessionId, artifactId), "meta.json");
5112
- }
5113
- };
5114
- function readPositiveIntEnv(name, fallback) {
5115
- const raw = process.env[name];
5116
- if (!raw) return fallback;
5117
- const parsed = Number.parseInt(raw, 10);
5118
- return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
5119
- }
5120
- function validateContextId(value, fieldName) {
5121
- if (!/^[A-Za-z0-9_.:-]{1,200}$/.test(value)) throw new ArtifactUploadError(`Invalid ${fieldName}`, 400);
5122
- }
5123
- function sanitizeFileName(fileName) {
5124
- return fileName.replace(/[\\/\0\r\n]/g, "_").trim() || "upload.bin";
5125
- }
5126
- function normalizeMimeType(mimeType) {
5127
- return mimeType?.trim() || "application/octet-stream";
5128
- }
5129
- function toPublicRef(metadata) {
5130
- const { tenantKey: _tenantKey, userId: _userId, openId: _openId, blobPath: _blobPath, ...ref } = metadata;
5131
- return ref;
5132
- }
5133
- function isOwnedBy(metadata, owner, sessionId) {
5134
- return metadata.tenantKey === owner.tenantKey && metadata.userId === owner.userId && metadata.openId === owner.openId && metadata.sessionId === sessionId;
5135
- }
5136
- async function readArtifactMetadata(filePath) {
5137
- try {
5138
- return JSON.parse(await readFile(filePath, "utf-8"));
5139
- } catch {
5140
- return null;
5141
- }
5142
- }
5143
- async function writeJson(filePath, data) {
5144
- await mkdir(dirname(filePath), { recursive: true });
5145
- await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
5146
- }
5147
- //#endregion
5148
5250
  //#region src/gateway/artifact-handler.ts
5149
5251
  const log$28 = larkLogger("gateway/artifacts");
5150
5252
  function createArtifactRouter(config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-lark/larkpal",
3
- "version": "0.1.63",
3
+ "version": "0.1.65",
4
4
  "description": "LarkPal - Lark/Feishu bot service",
5
5
  "type": "module",
6
6
  "main": "./dist/main.mjs",
@@ -21,9 +21,22 @@
21
21
  "registry": "https://registry.npmjs.org",
22
22
  "access": "public"
23
23
  },
24
+ "packageManager": "pnpm@10.32.1",
24
25
  "engines": {
25
26
  "node": ">=22"
26
27
  },
28
+ "scripts": {
29
+ "start": "node --env-file=.env bin/larkpal.js",
30
+ "build": "tsdown",
31
+ "test": "vitest run",
32
+ "test:watch": "vitest",
33
+ "lint": "eslint src/",
34
+ "lint:fix": "eslint src/ --fix",
35
+ "sit:larkpal-agent": "node scripts/sit/larkpal-agent-0.1.9-sit.mjs",
36
+ "typecheck": "tsc --noEmit",
37
+ "format": "prettier --write src/**/*.ts",
38
+ "format:check": "prettier --check src/**/*.ts"
39
+ },
27
40
  "dependencies": {
28
41
  "@larksuiteoapi/node-sdk": "^1.60.0",
29
42
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -38,7 +51,7 @@
38
51
  "zod": "^4.3.6"
39
52
  },
40
53
  "optionalDependencies": {
41
- "@vibe-lark/larkpal-agent": "^0.2.12"
54
+ "@vibe-lark/larkpal-agent": "^0.2.19"
42
55
  },
43
56
  "devDependencies": {
44
57
  "@eslint/js": "^10.0.1",
@@ -56,17 +69,5 @@
56
69
  "typescript": "^5.9.3",
57
70
  "typescript-eslint": "^8.32.1",
58
71
  "vitest": "^4.1.1"
59
- },
60
- "scripts": {
61
- "start": "node --env-file=.env bin/larkpal.js",
62
- "build": "tsdown",
63
- "test": "vitest run",
64
- "test:watch": "vitest",
65
- "lint": "eslint src/",
66
- "lint:fix": "eslint src/ --fix",
67
- "sit:larkpal-agent": "node scripts/sit/larkpal-agent-0.1.9-sit.mjs",
68
- "typecheck": "tsc --noEmit",
69
- "format": "prettier --write src/**/*.ts",
70
- "format:check": "prettier --check src/**/*.ts"
71
72
  }
72
- }
73
+ }