@vibe-lark/larkpal 0.1.84 → 0.1.85

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 +99 -140
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -2566,13 +2566,13 @@ async function fetchFromApplicationApi(token) {
2566
2566
  const data = await (await fetch("https://open.feishu.cn/open-apis/application/v6/applications/me?lang=zh_cn", { headers: { Authorization: `Bearer ${token}` } })).json();
2567
2567
  log$29.info("application/v6 API 响应", {
2568
2568
  code: data.code,
2569
- msg: data.msg,
2569
+ msg: redactAppInfoSyncDiagnostic(data.msg),
2570
2570
  hasApp: !!data.data?.app
2571
2571
  });
2572
2572
  if (data.code !== 0 || !data.data?.app) {
2573
2573
  log$29.warn("application/v6 API 返回非零或无数据,将 fallback", {
2574
2574
  code: data.code,
2575
- msg: data.msg
2575
+ msg: redactAppInfoSyncDiagnostic(data.msg)
2576
2576
  });
2577
2577
  return null;
2578
2578
  }
@@ -2595,13 +2595,13 @@ async function fetchFromBotApi(token) {
2595
2595
  const data = await (await fetch("https://open.feishu.cn/open-apis/bot/v3/info", { headers: { Authorization: `Bearer ${token}` } })).json();
2596
2596
  log$29.info("bot/v3/info API 响应", {
2597
2597
  code: data.code,
2598
- msg: data.msg,
2598
+ msg: redactAppInfoSyncDiagnostic(data.msg),
2599
2599
  hasBot: !!data.bot
2600
2600
  });
2601
2601
  if (data.code !== 0 || !data.bot) {
2602
2602
  log$29.error("bot/v3/info API 失败", {
2603
2603
  code: data.code,
2604
- msg: data.msg
2604
+ msg: redactAppInfoSyncDiagnostic(data.msg)
2605
2605
  });
2606
2606
  return null;
2607
2607
  }
@@ -2628,7 +2628,7 @@ async function getTenantAccessToken(appId, appSecret) {
2628
2628
  if (data.code !== 0 || !data.tenant_access_token) {
2629
2629
  log$29.error("获取 tenant_access_token 失败", {
2630
2630
  code: data.code,
2631
- msg: data.msg
2631
+ msg: redactAppInfoSyncDiagnostic(data.msg)
2632
2632
  });
2633
2633
  return null;
2634
2634
  }
@@ -2638,100 +2638,9 @@ async function getTenantAccessToken(appId, appSecret) {
2638
2638
  return null;
2639
2639
  }
2640
2640
  }
2641
- /**
2642
- * 从飞书云文档 URL 中解析文档 token
2643
- *
2644
- * 支持的 URL 格式:
2645
- * - https://xxx.feishu.cn/docx/{token}
2646
- * - https://xxx.feishu.cn/wiki/{token}
2647
- * - https://xxx.larksuite.com/docx/{token}
2648
- */
2649
- function parseDocTokenFromUrl(url) {
2650
- try {
2651
- const parts = new URL(url).pathname.split("/").filter(Boolean);
2652
- for (let i = 0; i < parts.length; i++) {
2653
- if (parts[i] === "docx" && parts[i + 1]) return {
2654
- token: parts[i + 1],
2655
- type: "docx"
2656
- };
2657
- if (parts[i] === "wiki" && parts[i + 1]) return {
2658
- token: parts[i + 1],
2659
- type: "wiki"
2660
- };
2661
- }
2662
- return null;
2663
- } catch {
2664
- return null;
2665
- }
2666
- }
2667
- /**
2668
- * 从飞书云文档读取纯文本内容(作为人设文档)
2669
- *
2670
- * 对于 wiki 类型,先通过 wiki API 获取实际的 doc token,再读取内容。
2671
- */
2672
- async function fetchDocContent(docUrl, accessToken) {
2673
- const parsed = parseDocTokenFromUrl(docUrl);
2674
- if (!parsed) {
2675
- log$29.warn("无法从 URL 中解析文档 token", { url: docUrl });
2676
- return null;
2677
- }
2678
- log$29.info("开始读取人设文档", {
2679
- url: docUrl,
2680
- type: parsed.type,
2681
- token: parsed.token
2682
- });
2683
- let docToken = parsed.token;
2684
- if (parsed.type === "wiki") {
2685
- const realToken = await resolveWikiNodeToDocToken(parsed.token, accessToken);
2686
- if (!realToken) log$29.warn("wiki 节点解析失败,尝试直接使用 token 读取");
2687
- else docToken = realToken;
2688
- }
2689
- try {
2690
- const data = await (await fetch(`https://open.feishu.cn/open-apis/docx/v1/documents/${docToken}/raw_content`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
2691
- log$29.info("文档 raw_content API 响应", {
2692
- code: data.code,
2693
- msg: data.msg,
2694
- contentLength: data.data?.content?.length
2695
- });
2696
- if (data.code !== 0 || !data.data?.content) {
2697
- log$29.warn("读取文档内容失败", {
2698
- code: data.code,
2699
- msg: data.msg,
2700
- docToken
2701
- });
2702
- return null;
2703
- }
2704
- return data.data.content.trim();
2705
- } catch (err) {
2706
- log$29.error("读取文档内容异常", {
2707
- error: err instanceof Error ? err.message : String(err),
2708
- docToken
2709
- });
2710
- return null;
2711
- }
2712
- }
2713
- /** 将 wiki 节点 token 解析为实际的文档 token */
2714
- async function resolveWikiNodeToDocToken(wikiToken, accessToken) {
2715
- try {
2716
- const data = await (await fetch(`https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token=${wikiToken}`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
2717
- log$29.info("wiki get_node API 响应", {
2718
- code: data.code,
2719
- msg: data.msg,
2720
- objType: data.data?.node?.obj_type
2721
- });
2722
- if (data.code !== 0 || !data.data?.node?.obj_token) return null;
2723
- return data.data.node.obj_token;
2724
- } catch (err) {
2725
- log$29.warn("wiki get_node 请求异常", { error: err instanceof Error ? err.message : String(err) });
2726
- return null;
2727
- }
2728
- }
2729
2641
  /** CLAUDE.md 中的应用信息区块标记 */
2730
2642
  const APP_INFO_START = "<!-- APP_INFO_START -->";
2731
2643
  const APP_INFO_END = "<!-- APP_INFO_END -->";
2732
- /** CLAUDE.md 中的人设文档区块标记 */
2733
- const PERSONA_DOC_START = "<!-- PERSONA_DOC_START -->";
2734
- const PERSONA_DOC_END = "<!-- PERSONA_DOC_END -->";
2735
2644
  /** AGENTS.md 中的全局人设镜像区块标记(Codex 读取 AGENTS.md,不读取 CLAUDE.md) */
2736
2645
  const CODEX_GLOBAL_PERSONA_START = "<!-- LARKPAL_CODEX_GLOBAL_PERSONA_START -->";
2737
2646
  const CODEX_GLOBAL_PERSONA_END = "<!-- LARKPAL_CODEX_GLOBAL_PERSONA_END -->";
@@ -2767,39 +2676,6 @@ async function syncAppInfoToClaudeMd(appInfo) {
2767
2676
  });
2768
2677
  }
2769
2678
  /**
2770
- * 将人设文档内容同步到 ~/.claude/CLAUDE.md
2771
- *
2772
- * 在文件中维护一个 PERSONA_DOC 标记区块,内容来源于飞书云文档。
2773
- * 如果文件中已有标记区块则替换,否则追加到文件末尾。
2774
- */
2775
- async function syncPersonaDocToClaudeMd(personaContent) {
2776
- const claudeMdPath = join(homedir(), ".claude", "CLAUDE.md");
2777
- if (!existsSync(claudeMdPath)) {
2778
- log$29.warn("CLAUDE.md 不存在,无法同步人设文档(需先同步应用信息)");
2779
- return;
2780
- }
2781
- let content = await readFile(claudeMdPath, "utf-8");
2782
- const personaBlock = [
2783
- PERSONA_DOC_START,
2784
- "## 人设与行为规范",
2785
- "",
2786
- "> 以下内容来自飞书人设文档,是你的核心身份定义和行为准则。",
2787
- "",
2788
- personaContent,
2789
- PERSONA_DOC_END
2790
- ].join("\n");
2791
- const startIdx = content.indexOf(PERSONA_DOC_START);
2792
- const endIdx = content.indexOf(PERSONA_DOC_END);
2793
- if (startIdx !== -1 && endIdx !== -1) {
2794
- const before = content.substring(0, startIdx);
2795
- const after = content.substring(endIdx + 24);
2796
- content = before + personaBlock + after;
2797
- } else content = content.trimEnd() + "\n\n" + personaBlock + "\n";
2798
- await writeFile(claudeMdPath, content, "utf-8");
2799
- await syncClaudeMdToCodexAgentsMd();
2800
- log$29.info("CLAUDE.md 人设文档已同步", { contentLength: personaContent.length });
2801
- }
2802
- /**
2803
2679
  * 将全局 Claude 人设镜像到 workspace/AGENTS.md。
2804
2680
  *
2805
2681
  * Claude Code 的全局入口是 ~/.claude/CLAUDE.md;Codex 的项目指令入口是
@@ -2958,20 +2834,11 @@ async function syncAppInfo(credentials) {
2958
2834
  }
2959
2835
  await syncAppInfoToClaudeMd(appInfo);
2960
2836
  await syncClaudeMdToCodexAgentsMd();
2961
- if (appInfo.helpDocUrl) {
2962
- log$29.info("检测到帮助文档 URL,尝试同步人设文档", { helpDocUrl: appInfo.helpDocUrl });
2963
- const token = await getTenantAccessToken(credentials.appId, credentials.appSecret);
2964
- if (token) {
2965
- const personaContent = await fetchDocContent(appInfo.helpDocUrl, token);
2966
- if (personaContent) await syncPersonaDocToClaudeMd(personaContent);
2967
- else log$29.warn("人设文档内容为空或读取失败,跳过同步");
2968
- }
2969
- }
2970
2837
  await installSyncSkill();
2971
2838
  log$29.info("应用信息同步完成", {
2972
2839
  appName: appInfo.appName,
2973
2840
  description: appInfo.description?.substring(0, 50),
2974
- hasPersonaDoc: !!appInfo.helpDocUrl
2841
+ hasPersonaSourceCandidate: !!appInfo.helpDocUrl
2975
2842
  });
2976
2843
  return appInfo;
2977
2844
  }
@@ -2985,6 +2852,10 @@ function replaceManagedBlock(current, block, startMarker, endMarker) {
2985
2852
  }
2986
2853
  return current.trimEnd() ? `${current.trimEnd()}\n\n${block}\n` : `${block}\n`;
2987
2854
  }
2855
+ function redactAppInfoSyncDiagnostic(value) {
2856
+ if (!value) return value;
2857
+ return value.replace(/\bwss:\/\/\S+/gi, "[redacted-wss-url]").replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]").replace(/\b(token|secret|authorization|api[_-]?key|access[_-]?token|refresh[_-]?token|password)\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]");
2858
+ }
2988
2859
  const SYNC_SKILL_PATH = join(homedir(), ".claude", "commands", "sync-app-info.md");
2989
2860
  const SYNC_SKILL_VERSION = "v0.1.0";
2990
2861
  /**
@@ -13972,6 +13843,83 @@ function isAskUserToolName(toolName) {
13972
13843
  return normalized === "ask_user" || normalized.endsWith("__ask_user") || normalized.endsWith("/ask_user") || normalized.endsWith(".ask_user") || normalized.endsWith(":ask_user");
13973
13844
  }
13974
13845
  //#endregion
13846
+ //#region src/persona/source-card.ts
13847
+ function buildPersonaSourceCandidateCard(params) {
13848
+ const helpDocUrl = params.appInfo.helpDocUrl?.trim();
13849
+ if (!helpDocUrl) throw new Error("Persona source candidate card requires appInfo.helpDocUrl");
13850
+ return {
13851
+ schema: "2.0",
13852
+ config: {
13853
+ wide_screen_mode: true,
13854
+ update_multi: true,
13855
+ summary: { content: "人设文档候选待确认" }
13856
+ },
13857
+ header: {
13858
+ title: {
13859
+ tag: "plain_text",
13860
+ content: "人设文档候选"
13861
+ },
13862
+ template: "blue"
13863
+ },
13864
+ body: { elements: [{
13865
+ tag: "markdown",
13866
+ content: [
13867
+ `检测到应用「${params.appInfo.appName}」配置了帮助文档。`,
13868
+ "",
13869
+ "这个链接可以作为 agent 人设文档来源,但不会自动绑定,避免误把业务帮助文档当成人设。",
13870
+ "",
13871
+ `候选文档:${helpDocUrl}`
13872
+ ].join("\n")
13873
+ }, {
13874
+ tag: "action",
13875
+ actions: [
13876
+ {
13877
+ tag: "button",
13878
+ text: {
13879
+ tag: "plain_text",
13880
+ content: "绑定为人设文档"
13881
+ },
13882
+ type: "primary",
13883
+ value: {
13884
+ action: "persona_source_bind_candidate",
13885
+ accountId: params.accountId,
13886
+ appName: params.appInfo.appName,
13887
+ helpDocUrl
13888
+ }
13889
+ },
13890
+ {
13891
+ tag: "button",
13892
+ text: {
13893
+ tag: "plain_text",
13894
+ content: "创建新人设文档"
13895
+ },
13896
+ type: "default",
13897
+ value: {
13898
+ action: "persona_source_create_new",
13899
+ accountId: params.accountId,
13900
+ appName: params.appInfo.appName,
13901
+ helpDocUrl
13902
+ }
13903
+ },
13904
+ {
13905
+ tag: "button",
13906
+ text: {
13907
+ tag: "plain_text",
13908
+ content: "暂不配置"
13909
+ },
13910
+ type: "text",
13911
+ value: {
13912
+ action: "persona_source_defer",
13913
+ accountId: params.accountId,
13914
+ appName: params.appInfo.appName,
13915
+ helpDocUrl
13916
+ }
13917
+ }
13918
+ ]
13919
+ }] }
13920
+ };
13921
+ }
13922
+ //#endregion
13975
13923
  //#region src/messaging/inbound/dispatch-sync-app-info.ts
13976
13924
  /**
13977
13925
  * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
@@ -14007,8 +13955,19 @@ async function dispatchSyncAppInfoCommand(dc, replyToMessageId) {
14007
13955
  await sendSyncAppInfoReply(dc, ["应用信息同步完成。", ...[
14008
13956
  `应用名称:${appInfo.appName}`,
14009
13957
  appInfo.description ? `应用描述:${appInfo.description}` : void 0,
14010
- appInfo.helpDocUrl ? "人设文档:已同步" : void 0
13958
+ appInfo.helpDocUrl ? "人设文档候选:待确认" : void 0
14011
13959
  ].filter((line) => Boolean(line))].join("\n"), replyToMessageId);
13960
+ if (appInfo.helpDocUrl) await sendCardFeishu({
13961
+ cfg: dc.accountScopedCfg,
13962
+ to: dc.ctx.chatId,
13963
+ card: buildPersonaSourceCandidateCard({
13964
+ appInfo,
13965
+ accountId: dc.account.accountId
13966
+ }),
13967
+ replyToMessageId: replyToMessageId ?? dc.ctx.messageId,
13968
+ accountId: dc.account.accountId,
13969
+ replyInThread: dc.isThread
13970
+ });
14012
13971
  } catch (err) {
14013
13972
  const errMsg = err instanceof Error ? err.message : String(err);
14014
13973
  dc.error(`feishu[${dc.account.accountId}]: sync-app-info failed: ${errMsg}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-lark/larkpal",
3
- "version": "0.1.84",
3
+ "version": "0.1.85",
4
4
  "description": "LarkPal - Lark/Feishu bot service",
5
5
  "type": "module",
6
6
  "main": "./dist/main.mjs",