pikiloom 0.4.26 → 0.4.27
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/dashboard/dist/assets/{AgentTab-BNJ73P0m.js → AgentTab-DIP-y4dl.js} +1 -1
- package/dashboard/dist/assets/ConnectionModal-BCfvhkiI.js +1 -0
- package/dashboard/dist/assets/{DirBrowser-D-GT-G-w.js → DirBrowser-Dot7ua9-.js} +1 -1
- package/dashboard/dist/assets/{ExtensionsTab-BrJMulJR.js → ExtensionsTab-GioMx4S6.js} +1 -1
- package/dashboard/dist/assets/{IMAccessTab-auPnx468.js → IMAccessTab-C9_r8jJP.js} +1 -1
- package/dashboard/dist/assets/{Modal-lYfO9m-I.js → Modal-M1AP1sgR.js} +1 -1
- package/dashboard/dist/assets/{Modals-PRdWOIY9.js → Modals-CCm3UUfJ.js} +1 -1
- package/dashboard/dist/assets/{Select-BhbU-rll.js → Select-YubDM16P.js} +1 -1
- package/dashboard/dist/assets/{SessionPanel-_0dLWvEb.js → SessionPanel-Cnv9o96n.js} +1 -1
- package/dashboard/dist/assets/SystemTab-VgNivIe3.js +1 -0
- package/dashboard/dist/assets/index-BY7l20bi.js +23 -0
- package/dashboard/dist/assets/index-BlqvzDUL.js +3 -0
- package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
- package/dashboard/dist/assets/{shared-C5uXvhZJ.js → shared-C0SAInnT.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/agent/acp-client.js +0 -59
- package/dist/agent/artifacts.js +1 -38
- package/dist/agent/auto-update.js +2 -78
- package/dist/agent/await-resume.js +0 -20
- package/dist/agent/cli/auth.js +3 -51
- package/dist/agent/cli/catalog.js +0 -3
- package/dist/agent/cli/detector.js +1 -19
- package/dist/agent/cli/index.js +0 -3
- package/dist/agent/cli/registry.js +0 -25
- package/dist/agent/driver.js +0 -14
- package/dist/agent/drivers/claude-tui.js +2 -769
- package/dist/agent/drivers/claude.js +12 -631
- package/dist/agent/drivers/codex.js +11 -162
- package/dist/agent/drivers/gemini.js +6 -114
- package/dist/agent/drivers/hermes.js +0 -128
- package/dist/agent/goal.js +0 -45
- package/dist/agent/handover.js +1 -46
- package/dist/agent/images.js +7 -133
- package/dist/agent/index.js +1 -29
- package/dist/agent/mcp/bridge.js +1 -197
- package/dist/agent/mcp/extensions.js +5 -84
- package/dist/agent/mcp/oauth.js +2 -69
- package/dist/agent/mcp/registry.js +2 -33
- package/dist/agent/mcp/session-server.js +2 -45
- package/dist/agent/mcp/tools/ask-user.js +0 -6
- package/dist/agent/mcp/tools/await-resume.js +0 -15
- package/dist/agent/mcp/tools/goal.js +0 -25
- package/dist/agent/mcp/tools/types.js +0 -5
- package/dist/agent/mcp/tools/workspace.js +0 -23
- package/dist/agent/npm.js +0 -14
- package/dist/agent/session.js +86 -196
- package/dist/agent/skill-installer.js +3 -70
- package/dist/agent/skills.js +0 -43
- package/dist/agent/stream.js +5 -148
- package/dist/agent/types.js +0 -12
- package/dist/agent/utils.js +2 -120
- package/dist/bot/bot.js +3 -468
- package/dist/bot/command-ui.js +0 -45
- package/dist/bot/commands.js +0 -51
- package/dist/bot/headless-bot.js +0 -21
- package/dist/bot/host.js +1 -5
- package/dist/bot/human-loop.js +0 -8
- package/dist/bot/menu.js +0 -3
- package/dist/bot/orchestration.js +0 -3
- package/dist/bot/render-shared.js +0 -63
- package/dist/bot/session-hub.js +14 -92
- package/dist/bot/session-status.js +1 -5
- package/dist/bot/streaming.js +0 -53
- package/dist/browser-profile.js +0 -51
- package/dist/browser-supervisor.js +0 -75
- package/dist/catalog/cli-tools.js +0 -53
- package/dist/catalog/index.js +0 -18
- package/dist/catalog/local-models.js +0 -18
- package/dist/catalog/mcp-servers.js +0 -35
- package/dist/catalog/skill-repos.js +0 -32
- package/dist/channels/base.js +0 -19
- package/dist/channels/dingtalk/bot.js +0 -3
- package/dist/channels/dingtalk/channel.js +0 -33
- package/dist/channels/discord/bot.js +0 -7
- package/dist/channels/discord/channel.js +0 -22
- package/dist/channels/feishu/bot.js +0 -71
- package/dist/channels/feishu/channel.js +0 -78
- package/dist/channels/feishu/markdown.js +0 -5
- package/dist/channels/feishu/render.js +0 -45
- package/dist/channels/health.js +0 -46
- package/dist/channels/slack/bot.js +0 -7
- package/dist/channels/slack/channel.js +1 -27
- package/dist/channels/states.js +0 -3
- package/dist/channels/telegram/bot.js +0 -60
- package/dist/channels/telegram/channel.js +2 -99
- package/dist/channels/telegram/directory.js +0 -3
- package/dist/channels/telegram/live-preview.js +0 -8
- package/dist/channels/telegram/render.js +0 -30
- package/dist/channels/wecom/bot.js +0 -3
- package/dist/channels/wecom/channel.js +0 -58
- package/dist/channels/weixin/api.js +0 -9
- package/dist/channels/weixin/bot.js +0 -88
- package/dist/channels/weixin/channel.js +0 -6
- package/dist/cli/autostart.js +1 -44
- package/dist/cli/channel-supervisor.js +1 -59
- package/dist/cli/channels.js +0 -6
- package/dist/cli/main.js +1 -145
- package/dist/cli/onboarding.js +0 -6
- package/dist/cli/run.js +0 -11
- package/dist/cli/setup-wizard.js +0 -3
- package/dist/core/config/runtime-config.js +0 -38
- package/dist/core/config/user-config.js +0 -63
- package/dist/core/config/validation.js +0 -9
- package/dist/core/constants.js +0 -268
- package/dist/core/git.js +0 -31
- package/dist/core/legacy-compat.js +1 -28
- package/dist/core/logging.js +0 -3
- package/dist/core/platform.js +0 -28
- package/dist/core/process-control.js +0 -8
- package/dist/core/secrets/index.js +0 -17
- package/dist/core/secrets/inline-seal.js +0 -7
- package/dist/core/secrets/ref.js +0 -19
- package/dist/core/secrets/resolver.js +0 -8
- package/dist/core/secrets/store.js +1 -12
- package/dist/core/utils.js +1 -37
- package/dist/core/version.js +0 -3
- package/dist/dashboard/platform.js +1 -35
- package/dist/dashboard/routes/agents.js +2 -71
- package/dist/dashboard/routes/cli.js +0 -27
- package/dist/dashboard/routes/config.js +0 -54
- package/dist/dashboard/routes/extensions.js +0 -132
- package/dist/dashboard/routes/local-models.js +0 -68
- package/dist/dashboard/routes/models.js +0 -46
- package/dist/dashboard/routes/sessions.js +0 -100
- package/dist/dashboard/runtime.js +1 -58
- package/dist/dashboard/server.js +0 -49
- package/dist/dashboard/session-control.js +2 -69
- package/dist/model/catalog.js +1 -17
- package/dist/model/index.js +0 -15
- package/dist/model/injector.js +1 -181
- package/dist/model/provider-models.js +1 -48
- package/dist/model/responses-bridge.js +3 -45
- package/dist/model/store.js +0 -17
- package/dist/model/types.js +0 -12
- package/dist/model/validation.js +1 -18
- package/dist/pikichannel/adapter-pikiloom.js +0 -31
- package/dist/pikichannel/code.js +0 -12
- package/dist/pikichannel/codec.js +0 -14
- package/dist/pikichannel/host.js +4 -33
- package/dist/pikichannel/protocol.js +0 -52
- package/dist/pikichannel/rendezvous-broker.js +5 -26
- package/dist/pikichannel/rendezvous-host.js +3 -16
- package/dist/pikichannel/server.js +6 -55
- package/dist/pikichannel/transport.js +0 -25
- package/dist/pikichannel/transports/webrtc-host.js +0 -18
- package/dist/pikichannel/transports/webrtc-shared.js +4 -34
- package/dist/pikichannel/transports/websocket-host.js +1 -14
- package/dist/pikichannel/turn.js +2 -69
- package/dist/pikichannel/web/sdk.js +14 -71
- package/package.json +1 -1
- package/dashboard/dist/assets/ConnectionModal-Dt05H2f9.js +0 -1
- package/dashboard/dist/assets/SystemTab-CN_QrRKt.js +0 -1
- package/dashboard/dist/assets/index-CP3LYJR2.js +0 -23
- package/dashboard/dist/assets/index-C_neLnaO.js +0 -3
- package/dashboard/dist/assets/index-CyCspgtc.css +0 -1
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* bot-feishu.ts — Feishu bot orchestration: commands, streaming, artifacts, lifecycle.
|
|
3
|
-
*
|
|
4
|
-
* Follows the same pattern as bot-telegram.ts:
|
|
5
|
-
* - Commands use shared data layer (bot-commands.ts) + Feishu renderer
|
|
6
|
-
* - Messages flow through the streaming pipeline
|
|
7
|
-
* - LivePreview provides real-time streaming updates via card edits
|
|
8
|
-
*/
|
|
9
1
|
import os from 'node:os';
|
|
10
2
|
import fs from 'node:fs';
|
|
11
3
|
import path from 'node:path';
|
|
@@ -44,11 +36,6 @@ function describeError(err) {
|
|
|
44
36
|
parts.push(`cause=${describeError(cause)}`);
|
|
45
37
|
return parts.join(' | ');
|
|
46
38
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Decision echo rendered as a separate Feishu message after a human-loop
|
|
49
|
-
* prompt resolves — so the answer becomes part of the conversation history
|
|
50
|
-
* rather than living only inside the now-closed card.
|
|
51
|
-
*/
|
|
52
39
|
function buildInteractionEchoMarkdown(summary) {
|
|
53
40
|
if (summary.status === 'cancelled')
|
|
54
41
|
return '*⊘ Prompt cancelled.*';
|
|
@@ -99,15 +86,11 @@ function formatToolLog(activity) {
|
|
|
99
86
|
const summary = deduped.slice(0, 6).join(' | ');
|
|
100
87
|
return summary.length <= 240 ? summary : `${summary.slice(0, 237).trimEnd()}...`;
|
|
101
88
|
}
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
|
-
// FeishuBot
|
|
104
|
-
// ---------------------------------------------------------------------------
|
|
105
89
|
export class FeishuBot extends Bot {
|
|
106
90
|
appId;
|
|
107
91
|
appSecret;
|
|
108
92
|
domain;
|
|
109
93
|
channel;
|
|
110
|
-
/** Maps chatId → (messageId → sessionKey) for reply-chain session tracking. */
|
|
111
94
|
sessionMessages = new SessionMessageRegistry();
|
|
112
95
|
nextTaskId = 1;
|
|
113
96
|
shutdownInFlight = false;
|
|
@@ -118,16 +101,10 @@ export class FeishuBot extends Bot {
|
|
|
118
101
|
constructor() {
|
|
119
102
|
super();
|
|
120
103
|
const config = getActiveUserConfig();
|
|
121
|
-
// Merge Feishu-specific allowed IDs into base
|
|
122
104
|
if (process.env.FEISHU_ALLOWED_CHAT_IDS) {
|
|
123
105
|
for (const id of parseAllowedChatIds(process.env.FEISHU_ALLOWED_CHAT_IDS))
|
|
124
106
|
this.allowedChatIds.add(id);
|
|
125
107
|
}
|
|
126
|
-
// NOTE: persisted known chats are restored into channel.knownChats in run()
|
|
127
|
-
// (for the startup greeting / per-chat menu) — deliberately NOT into
|
|
128
|
-
// allowedChatIds. allowedChatIds is the explicit allowlist; folding known
|
|
129
|
-
// chats into it flips _isAllowed() from allow-all into allowlist-only and
|
|
130
|
-
// silently drops every new chat.
|
|
131
108
|
this.appId = String(config.feishuAppId || process.env.FEISHU_APP_ID || '').trim();
|
|
132
109
|
this.appSecret = String(config.feishuAppSecret || process.env.FEISHU_APP_SECRET || '').trim();
|
|
133
110
|
this.domain = (process.env.FEISHU_DOMAIN || 'https://open.feishu.cn').trim();
|
|
@@ -169,7 +146,6 @@ export class FeishuBot extends Bot {
|
|
|
169
146
|
return;
|
|
170
147
|
void this.setupMenu().catch(err => this.log(`menu refresh failed: ${err}`));
|
|
171
148
|
}
|
|
172
|
-
// ---- signal handling ------------------------------------------------------
|
|
173
149
|
installSignalHandlers() {
|
|
174
150
|
this.removeSignalHandlers();
|
|
175
151
|
const onSigint = () => this.beginShutdown('SIGINT');
|
|
@@ -210,13 +186,8 @@ export class FeishuBot extends Bot {
|
|
|
210
186
|
shutdownAllDrivers();
|
|
211
187
|
}
|
|
212
188
|
buildRestartEnv() {
|
|
213
|
-
// Hand off only the explicit allowlist. Known chats persist to setting.json
|
|
214
|
-
// (recordKnownChatId) and are restored via loadKnownChatIds, so they must NOT
|
|
215
|
-
// ride along in the allowlist env — that would re-pollute allowedChatIds on
|
|
216
|
-
// the next boot and lock out new chats.
|
|
217
189
|
return buildKnownChatEnv(this.allowedChatIds, [], 'FEISHU_ALLOWED_CHAT_IDS');
|
|
218
190
|
}
|
|
219
|
-
// ---- session tracking -----------------------------------------------------
|
|
220
191
|
createTaskId(session) {
|
|
221
192
|
return buildSessionTaskId(session, this.nextTaskId++);
|
|
222
193
|
}
|
|
@@ -250,7 +221,6 @@ export class FeishuBot extends Bot {
|
|
|
250
221
|
return selected;
|
|
251
222
|
return this.ensureSession(ctx.chatId, text, files);
|
|
252
223
|
}
|
|
253
|
-
// ---- commands -------------------------------------------------------------
|
|
254
224
|
async cmdStart(ctx) {
|
|
255
225
|
const d = getStartData(this, ctx.chatId);
|
|
256
226
|
await ctx.reply(renderStart(d));
|
|
@@ -376,7 +346,6 @@ export class FeishuBot extends Bot {
|
|
|
376
346
|
return;
|
|
377
347
|
}
|
|
378
348
|
catch {
|
|
379
|
-
// Not a valid agent name — show list
|
|
380
349
|
}
|
|
381
350
|
}
|
|
382
351
|
await this.sendCommandView(ctx, buildAgentsCommandView(this, ctx.chatId));
|
|
@@ -534,12 +503,6 @@ export class FeishuBot extends Bot {
|
|
|
534
503
|
this.debug(`[human-loop] echo send failed chat=${chatId} prompt=${prompt.promptId}: ${err?.message || err}`);
|
|
535
504
|
}
|
|
536
505
|
}
|
|
537
|
-
/**
|
|
538
|
-
* IM presenter for programmatic submissions (e.g. /goal-driven turns) so
|
|
539
|
-
* they stream to Feishu the same way a typed message does. Without this,
|
|
540
|
-
* /goal just emits a confirmation reply and the rest of the turn is
|
|
541
|
-
* invisible to the IM user.
|
|
542
|
-
*/
|
|
543
506
|
async createImTaskPresenter(opts) {
|
|
544
507
|
if (typeof opts.chatId !== 'string')
|
|
545
508
|
return null;
|
|
@@ -623,7 +586,6 @@ export class FeishuBot extends Bot {
|
|
|
623
586
|
}
|
|
624
587
|
catch { }
|
|
625
588
|
}
|
|
626
|
-
// ---- streaming bridge -----------------------------------------------------
|
|
627
589
|
async handleMessage(msg, ctx) {
|
|
628
590
|
const text = msg.text.trim();
|
|
629
591
|
if (!text && !msg.files.length)
|
|
@@ -635,7 +597,6 @@ export class FeishuBot extends Bot {
|
|
|
635
597
|
await ctx.reply('Please answer the active prompt using the buttons above.');
|
|
636
598
|
return;
|
|
637
599
|
}
|
|
638
|
-
// Completed prompts close themselves via onInteractionAnswered.
|
|
639
600
|
if (!result.completed)
|
|
640
601
|
await this.refreshHumanLoopPrompt(ctx.chatId, result.prompt.promptId);
|
|
641
602
|
return;
|
|
@@ -643,11 +604,7 @@ export class FeishuBot extends Bot {
|
|
|
643
604
|
const session = this.resolveIncomingSession(ctx, text, msg.files);
|
|
644
605
|
const cs = this.chat(ctx.chatId);
|
|
645
606
|
this.applySessionSelection(cs, session);
|
|
646
|
-
// Tie the user's own message to its session so a future Feishu reply to it
|
|
647
|
-
// (their own message, or a quote-chain rooted at it) resolves back to this
|
|
648
|
-
// session instead of falling through to the chat's current default.
|
|
649
607
|
this.registerSessionMessage(ctx.chatId, ctx.messageId, session);
|
|
650
|
-
// File-only message: stage files
|
|
651
608
|
if (!text && msg.files.length) {
|
|
652
609
|
const hadPendingWork = this.sessionHasPendingWork(session);
|
|
653
610
|
const stageTask = this.queueSessionTask(session, async () => {
|
|
@@ -705,9 +662,6 @@ export class FeishuBot extends Bot {
|
|
|
705
662
|
this.emitStreamQueued(session.key, taskId);
|
|
706
663
|
const queuePosition = waiting ? this.getQueuePosition(session.key, taskId) : 0;
|
|
707
664
|
const placeholderKeyboard = this.buildStopKeyboard(this.actionIdForTask(taskId), { queued: waiting });
|
|
708
|
-
// Use the canonical resolver so the labels in the IM card match what
|
|
709
|
-
// runStream actually invokes the agent with (and what the dashboard's
|
|
710
|
-
// `start` event reports).
|
|
711
665
|
const startConfig = this.resolveSessionStreamConfig(session);
|
|
712
666
|
const model = startConfig.model;
|
|
713
667
|
const effort = startConfig.effort;
|
|
@@ -737,7 +691,6 @@ export class FeishuBot extends Bot {
|
|
|
737
691
|
return;
|
|
738
692
|
}
|
|
739
693
|
this.emitStreamStart(taskId, session);
|
|
740
|
-
// Task is now running — update keyboard from Recall/Steer to Stop
|
|
741
694
|
const runningKeyboard = this.buildStopKeyboard(this.actionIdForTask(taskId));
|
|
742
695
|
if (placeholderId && waiting) {
|
|
743
696
|
try {
|
|
@@ -766,7 +719,6 @@ export class FeishuBot extends Bot {
|
|
|
766
719
|
});
|
|
767
720
|
livePreview.start();
|
|
768
721
|
}
|
|
769
|
-
// MCP sendFile callback: sends files to IM in real-time during the stream
|
|
770
722
|
const mcpSendFile = this.createMcpSendFileCallback(ctx);
|
|
771
723
|
const result = await this.runStream(prompt, session, files, (nextText, nextThinking, nextActivity = '', meta, plan) => {
|
|
772
724
|
livePreview?.update(nextText, nextThinking, nextActivity, meta, plan);
|
|
@@ -794,8 +746,6 @@ export class FeishuBot extends Bot {
|
|
|
794
746
|
incomplete: !!result.incomplete,
|
|
795
747
|
...(result.ok ? {} : { error: result.error || result.message }),
|
|
796
748
|
});
|
|
797
|
-
// If LivePreview already gave up on the placeholder (Feishu rejected too
|
|
798
|
-
// many consecutive edits) skip the doomed edit attempt and send fresh.
|
|
799
749
|
const effectivePlaceholderId = livePreview?.isPlaceholderAbandoned() ? null : placeholderId;
|
|
800
750
|
const finalReplyIds = await this.sendFinalReply({ chatId: ctx.chatId }, effectivePlaceholderId, session.agent, result);
|
|
801
751
|
this.registerSessionMessages(ctx.chatId, finalReplyIds, session);
|
|
@@ -837,10 +787,6 @@ export class FeishuBot extends Bot {
|
|
|
837
787
|
this.finishTask(taskId);
|
|
838
788
|
});
|
|
839
789
|
}
|
|
840
|
-
/** Edit the placeholder if possible; otherwise send `text` as a fresh card
|
|
841
|
-
* anchored to the placeholder so the user always sees the result. Returns
|
|
842
|
-
* the message id that now carries `text` (the placeholder id if the edit
|
|
843
|
-
* succeeded, or a newly-created id on fallback). */
|
|
844
790
|
async editOrSendFresh(chatId, placeholderId, text, opts = {}) {
|
|
845
791
|
const tag = opts.logTag || 'editOrSendFresh';
|
|
846
792
|
if (placeholderId) {
|
|
@@ -851,8 +797,6 @@ export class FeishuBot extends Bot {
|
|
|
851
797
|
catch (e) {
|
|
852
798
|
this.debug(`[${tag}] placeholder edit failed, falling back to send chat=${chatId} placeholder=${placeholderId}: ${e?.message || e}`);
|
|
853
799
|
}
|
|
854
|
-
// Try anchoring the fresh send to the placeholder so the result stays in
|
|
855
|
-
// the same visual thread.
|
|
856
800
|
try {
|
|
857
801
|
const sent = await this.channel.send(chatId, text, {
|
|
858
802
|
replyTo: placeholderId,
|
|
@@ -865,7 +809,6 @@ export class FeishuBot extends Bot {
|
|
|
865
809
|
this.debug(`[${tag}] reply send failed, dropping reply anchor chat=${chatId} placeholder=${placeholderId}: ${e?.message || e}`);
|
|
866
810
|
}
|
|
867
811
|
}
|
|
868
|
-
// Last resort: standalone fresh card.
|
|
869
812
|
try {
|
|
870
813
|
return await this.channel.send(chatId, text, { keyboard: opts.keyboard });
|
|
871
814
|
}
|
|
@@ -891,14 +834,11 @@ export class FeishuBot extends Bot {
|
|
|
891
834
|
const messageIds = [];
|
|
892
835
|
const MAX_CARD = FEISHU_BOT_CARD_MAX;
|
|
893
836
|
if (rendered.fullText.length <= MAX_CARD) {
|
|
894
|
-
// Fits in one card — try to edit the placeholder, fall back to a fresh
|
|
895
|
-
// card if Feishu rejected the patch (message too old, recalled, ...).
|
|
896
837
|
const finalId = await this.editOrSendFresh(anchor.chatId, placeholderId, rendered.fullText, { logTag: 'final-reply' });
|
|
897
838
|
if (finalId)
|
|
898
839
|
messageIds.push(finalId);
|
|
899
840
|
}
|
|
900
841
|
else {
|
|
901
|
-
// Split: first card has header + truncated body + footer, continuation as separate cards
|
|
902
842
|
const maxFirst = MAX_CARD - rendered.headerText.length - rendered.footerText.length;
|
|
903
843
|
let firstBody;
|
|
904
844
|
let remaining;
|
|
@@ -926,7 +866,6 @@ export class FeishuBot extends Bot {
|
|
|
926
866
|
}
|
|
927
867
|
}
|
|
928
868
|
}
|
|
929
|
-
// Dispatch any image MessageBlocks the agent produced this turn.
|
|
930
869
|
const lastId = messageIds[messageIds.length - 1];
|
|
931
870
|
const dispatched = await dispatchImageBlocks(this.channel, result.assistantBlocks, {
|
|
932
871
|
chatId: anchor.chatId,
|
|
@@ -939,7 +878,6 @@ export class FeishuBot extends Bot {
|
|
|
939
878
|
}
|
|
940
879
|
return messageIds;
|
|
941
880
|
}
|
|
942
|
-
/** Create an MCP sendFile callback bound to a Feishu chat context. */
|
|
943
881
|
createMcpSendFileCallback(ctx) {
|
|
944
882
|
return async (filePath, opts) => {
|
|
945
883
|
try {
|
|
@@ -956,7 +894,6 @@ export class FeishuBot extends Bot {
|
|
|
956
894
|
}
|
|
957
895
|
};
|
|
958
896
|
}
|
|
959
|
-
// ---- command router -------------------------------------------------------
|
|
960
897
|
async handleCommand(cmd, args, ctx) {
|
|
961
898
|
try {
|
|
962
899
|
switch (cmd) {
|
|
@@ -1003,12 +940,10 @@ export class FeishuBot extends Bot {
|
|
|
1003
940
|
await this.cmdRestart(ctx);
|
|
1004
941
|
return;
|
|
1005
942
|
default:
|
|
1006
|
-
// Skill commands
|
|
1007
943
|
if (cmd.startsWith(FeishuBot.SKILL_CMD_PREFIX)) {
|
|
1008
944
|
await this.cmdSkill(cmd, args, ctx);
|
|
1009
945
|
return;
|
|
1010
946
|
}
|
|
1011
|
-
// Unknown command — treat as message
|
|
1012
947
|
await this.handleMessage({ text: `/${cmd}${args ? ' ' + args : ''}`, files: [] }, ctx);
|
|
1013
948
|
}
|
|
1014
949
|
}
|
|
@@ -1039,7 +974,6 @@ export class FeishuBot extends Bot {
|
|
|
1039
974
|
raw: ctx.raw,
|
|
1040
975
|
};
|
|
1041
976
|
}
|
|
1042
|
-
// ---- callback handlers ----------------------------------------------------
|
|
1043
977
|
async handleCallback(data, ctx) {
|
|
1044
978
|
try {
|
|
1045
979
|
if (await this.handleHumanLoopCallback(data, ctx))
|
|
@@ -1129,7 +1063,6 @@ export class FeishuBot extends Bot {
|
|
|
1129
1063
|
const result = await this.steerTaskByActionId(actionId);
|
|
1130
1064
|
if (!result.task)
|
|
1131
1065
|
return true;
|
|
1132
|
-
// The queued task will naturally run next after the running task is interrupted
|
|
1133
1066
|
return true;
|
|
1134
1067
|
}
|
|
1135
1068
|
async handleSwitchNavigateCallback(data, ctx) {
|
|
@@ -1187,7 +1120,6 @@ export class FeishuBot extends Bot {
|
|
|
1187
1120
|
}
|
|
1188
1121
|
}
|
|
1189
1122
|
catch {
|
|
1190
|
-
// non-critical
|
|
1191
1123
|
}
|
|
1192
1124
|
}
|
|
1193
1125
|
async handleMessageRecalled(messageId, chatId) {
|
|
@@ -1202,7 +1134,6 @@ export class FeishuBot extends Bot {
|
|
|
1202
1134
|
}
|
|
1203
1135
|
this.log(`[message-recalled] cancelled queued task chat=${chatId} msg=${messageId} session=${task.sessionKey}`);
|
|
1204
1136
|
}
|
|
1205
|
-
// ---- lifecycle ------------------------------------------------------------
|
|
1206
1137
|
async run() {
|
|
1207
1138
|
const tmpDir = path.join(os.tmpdir(), 'pikiloom');
|
|
1208
1139
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -1215,8 +1146,6 @@ export class FeishuBot extends Bot {
|
|
|
1215
1146
|
? this.allowedChatIds
|
|
1216
1147
|
: undefined,
|
|
1217
1148
|
});
|
|
1218
|
-
// Restore persisted known chats into the channel so the startup greeting and
|
|
1219
|
-
// per-chat menu can address them — without polluting the allowlist.
|
|
1220
1149
|
for (const id of loadKnownChatIds('feishu'))
|
|
1221
1150
|
this.channel.knownChats.add(id);
|
|
1222
1151
|
this.processRuntimeCleanup?.();
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Feishu channel — Feishu/Lark Open Platform transport using official SDK.
|
|
3
|
-
*
|
|
4
|
-
* Uses @larksuiteoapi/node-sdk for:
|
|
5
|
-
* - WSClient + EventDispatcher: WebSocket event receiving with auto-reconnect
|
|
6
|
-
* - Client.im: message send/edit/delete, image/file upload, resource download
|
|
7
|
-
* - Automatic tenant_access_token management
|
|
8
|
-
*
|
|
9
|
-
* All messages are sent as regular interactive cards (no CardKit streaming).
|
|
10
|
-
*/
|
|
11
1
|
import * as lark from '@larksuiteoapi/node-sdk';
|
|
12
2
|
import fs from 'node:fs';
|
|
13
3
|
import path from 'node:path';
|
|
@@ -75,7 +65,6 @@ function requireMessageId(resp, action) {
|
|
|
75
65
|
const msg = resp?.msg || resp?.message || 'no message_id returned';
|
|
76
66
|
throw new Error(`${action} failed: code=${code ?? '?'} msg=${msg}`);
|
|
77
67
|
}
|
|
78
|
-
/** Treat "card content didn't change" responses as a successful no-op edit. */
|
|
79
68
|
function isFeishuNotModifiedMessage(msg) {
|
|
80
69
|
if (!msg)
|
|
81
70
|
return false;
|
|
@@ -95,9 +84,6 @@ function buildPostContent(paragraphs, title = '') {
|
|
|
95
84
|
},
|
|
96
85
|
});
|
|
97
86
|
}
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
// Card builder helper
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
87
|
function inferActionLayout(actions) {
|
|
102
88
|
if (actions.length >= 3)
|
|
103
89
|
return 'trisection';
|
|
@@ -147,8 +133,6 @@ function buildCardFromView(view) {
|
|
|
147
133
|
element.layout = layout;
|
|
148
134
|
actionElements.push(element);
|
|
149
135
|
}
|
|
150
|
-
// Card JSON 2.0 supports tables in markdown but dropped `tag: action`.
|
|
151
|
-
// Use v2 for content-only cards; fall back to v1 when buttons are needed.
|
|
152
136
|
if (actionElements.length) {
|
|
153
137
|
const card = {
|
|
154
138
|
config: { wide_screen_mode: true, update_multi: true },
|
|
@@ -175,9 +159,6 @@ function buildCardFromView(view) {
|
|
|
175
159
|
}
|
|
176
160
|
return card;
|
|
177
161
|
}
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
// FeishuChannel
|
|
180
|
-
// ---------------------------------------------------------------------------
|
|
181
162
|
class FeishuChannel extends Channel {
|
|
182
163
|
capabilities = {
|
|
183
164
|
...DEFAULT_CHANNEL_CAPABILITIES,
|
|
@@ -186,10 +167,6 @@ class FeishuChannel extends Channel {
|
|
|
186
167
|
messageReactions: true,
|
|
187
168
|
sendImage: true,
|
|
188
169
|
};
|
|
189
|
-
/** Implementation of Channel.sendImage — uploads the buffer as an image
|
|
190
|
-
* message. When a caption is supplied, falls back to a post with both an
|
|
191
|
-
* img tag and a text line (Feishu's `msg_type:'image'` doesn't have a
|
|
192
|
-
* native caption field). */
|
|
193
170
|
async sendImage(chatId, bytes, opts) {
|
|
194
171
|
const caption = opts.caption?.trim() || '';
|
|
195
172
|
const replyTo = opts.replyTo ? String(opts.replyTo) : undefined;
|
|
@@ -217,11 +194,9 @@ class FeishuChannel extends Channel {
|
|
|
217
194
|
eventDispatcher;
|
|
218
195
|
running = false;
|
|
219
196
|
messageChains = new Map();
|
|
220
|
-
/** Recently processed message IDs — guards against Feishu server retries. */
|
|
221
197
|
_seenMessageIds = new Set();
|
|
222
198
|
_seenMessageIdQueue = [];
|
|
223
199
|
static SEEN_MESSAGE_CAP = 256;
|
|
224
|
-
/** Maps open_id → chat_id for resolving menu event context. */
|
|
225
200
|
_openIdToChat = new Map();
|
|
226
201
|
_hCommand = null;
|
|
227
202
|
_hMessage = null;
|
|
@@ -229,7 +204,6 @@ class FeishuChannel extends Channel {
|
|
|
229
204
|
_hRecall = null;
|
|
230
205
|
_hError = null;
|
|
231
206
|
knownChats = new Set();
|
|
232
|
-
/** Resolves when wsClient.start() settles (used by listen() to block). */
|
|
233
207
|
_listenResolve = null;
|
|
234
208
|
constructor(opts) {
|
|
235
209
|
super();
|
|
@@ -238,7 +212,6 @@ class FeishuChannel extends Channel {
|
|
|
238
212
|
this.domain = (opts.domain ?? 'https://open.feishu.cn').replace(/\/+$/, '');
|
|
239
213
|
this.workdir = opts.workdir ?? process.cwd();
|
|
240
214
|
this.allowedChatIds = opts.allowedChatIds ?? new Set();
|
|
241
|
-
// Resolve SDK domain enum or custom string
|
|
242
215
|
const sdkDomain = this.domain.includes('larksuite.com')
|
|
243
216
|
? lark.Domain.Lark
|
|
244
217
|
: this.domain === 'https://open.feishu.cn'
|
|
@@ -253,17 +226,12 @@ class FeishuChannel extends Channel {
|
|
|
253
226
|
this.eventDispatcher = new lark.EventDispatcher({});
|
|
254
227
|
this._registerEvents();
|
|
255
228
|
}
|
|
256
|
-
// ---- Hook registration ---------------------------------------------------
|
|
257
229
|
onCommand(h) { this._hCommand = h; }
|
|
258
230
|
onMessage(h) { this._hMessage = h; }
|
|
259
231
|
onCallback(h) { this._hCardAction = h; }
|
|
260
232
|
onMessageRecalled(h) { this._hRecall = h; }
|
|
261
233
|
onError(h) { this._hError = h; }
|
|
262
|
-
// ========================================================================
|
|
263
|
-
// Lifecycle
|
|
264
|
-
// ========================================================================
|
|
265
234
|
async connect() {
|
|
266
|
-
// Get bot info via raw request (SDK doesn't have a dedicated bot info method)
|
|
267
235
|
try {
|
|
268
236
|
const resp = await this.client.request({
|
|
269
237
|
method: 'GET',
|
|
@@ -329,7 +297,6 @@ class FeishuChannel extends Channel {
|
|
|
329
297
|
}
|
|
330
298
|
if (!this.running || !this.wsClient)
|
|
331
299
|
return;
|
|
332
|
-
// Block until disconnect() is called
|
|
333
300
|
await new Promise(resolve => {
|
|
334
301
|
this._listenResolve = resolve;
|
|
335
302
|
if (!this.running)
|
|
@@ -348,9 +315,6 @@ class FeishuChannel extends Channel {
|
|
|
348
315
|
this._listenResolve?.();
|
|
349
316
|
this._listenResolve = null;
|
|
350
317
|
}
|
|
351
|
-
// ========================================================================
|
|
352
|
-
// Event handling (via SDK EventDispatcher)
|
|
353
|
-
// ========================================================================
|
|
354
318
|
_registerEvents() {
|
|
355
319
|
this.eventDispatcher.register({
|
|
356
320
|
'im.message.receive_v1': (data) => {
|
|
@@ -394,7 +358,6 @@ class FeishuChannel extends Channel {
|
|
|
394
358
|
const msgType = msg.message_type;
|
|
395
359
|
if (!chatId || !messageId)
|
|
396
360
|
return;
|
|
397
|
-
// Dedup: Feishu server may retry events when the ack is slow
|
|
398
361
|
if (this._seenMessageIds.has(messageId)) {
|
|
399
362
|
this._debug(`[recv] dedup: message=${messageId} already processed, skipping`);
|
|
400
363
|
return;
|
|
@@ -410,7 +373,6 @@ class FeishuChannel extends Channel {
|
|
|
410
373
|
}
|
|
411
374
|
this._trackChat(chatId);
|
|
412
375
|
const sender = event.sender;
|
|
413
|
-
// Skip messages from the bot itself
|
|
414
376
|
if (sender?.sender_type === 'app')
|
|
415
377
|
return;
|
|
416
378
|
const from = {
|
|
@@ -418,10 +380,8 @@ class FeishuChannel extends Channel {
|
|
|
418
380
|
userId: sender?.sender_id?.user_id,
|
|
419
381
|
name: '',
|
|
420
382
|
};
|
|
421
|
-
// Track open_id → chat_id for menu event resolution
|
|
422
383
|
if (from.openId)
|
|
423
384
|
this._openIdToChat.set(from.openId, chatId);
|
|
424
|
-
// Group: require @mention
|
|
425
385
|
if (chatType === 'group') {
|
|
426
386
|
if (shouldLog('debug'))
|
|
427
387
|
this._debug(`[recv] group check mention: bot=${JSON.stringify(this.bot)}, mentions=${JSON.stringify(msg.mentions)}`);
|
|
@@ -432,7 +392,6 @@ class FeishuChannel extends Channel {
|
|
|
432
392
|
}
|
|
433
393
|
const parentId = typeof msg.parent_id === 'string' && msg.parent_id ? msg.parent_id : null;
|
|
434
394
|
const ctx = this._makeCtx(chatId, messageId, from, chatType, event, parentId);
|
|
435
|
-
// Parse message content
|
|
436
395
|
let text = '';
|
|
437
396
|
const files = [];
|
|
438
397
|
try {
|
|
@@ -474,11 +433,9 @@ class FeishuChannel extends Channel {
|
|
|
474
433
|
return;
|
|
475
434
|
}
|
|
476
435
|
const trimmedText = text.trim();
|
|
477
|
-
// Queue dispatch per chat to preserve ordering
|
|
478
436
|
const key = chatId;
|
|
479
437
|
const prev = this.messageChains.get(key) || Promise.resolve();
|
|
480
438
|
const current = prev.catch(() => { }).then(async () => {
|
|
481
|
-
// Command dispatch
|
|
482
439
|
if (trimmedText.startsWith('/') && this._hCommand) {
|
|
483
440
|
const spaceIdx = trimmedText.indexOf(' ');
|
|
484
441
|
const cmd = (spaceIdx > 0 ? trimmedText.slice(1, spaceIdx) : trimmedText.slice(1)).toLowerCase();
|
|
@@ -486,7 +443,6 @@ class FeishuChannel extends Channel {
|
|
|
486
443
|
await this._hCommand(cmd, args, ctx);
|
|
487
444
|
return;
|
|
488
445
|
}
|
|
489
|
-
// Message dispatch
|
|
490
446
|
if (!this._hMessage)
|
|
491
447
|
return;
|
|
492
448
|
if (!trimmedText && !files.length)
|
|
@@ -532,7 +488,6 @@ class FeishuChannel extends Channel {
|
|
|
532
488
|
const openId = event.operator?.operator_id?.open_id;
|
|
533
489
|
if (!eventKey || !openId || !this._hCommand)
|
|
534
490
|
return;
|
|
535
|
-
// Try: event payload → cache → API resolve
|
|
536
491
|
const chatId = this._openIdToChat.get(openId)
|
|
537
492
|
?? await this._resolveP2pChatId(openId);
|
|
538
493
|
if (!chatId) {
|
|
@@ -559,10 +514,6 @@ class FeishuChannel extends Channel {
|
|
|
559
514
|
this._debug(`[recv] message_recalled chat=${chatId} msg=${messageId}`);
|
|
560
515
|
await this._hRecall(messageId, chatId, event);
|
|
561
516
|
}
|
|
562
|
-
/**
|
|
563
|
-
* Resolve a p2p chat_id for a given open_id by sending a minimal message
|
|
564
|
-
* via open_id and extracting the chat_id from the API response.
|
|
565
|
-
*/
|
|
566
517
|
async _resolveP2pChatId(openId) {
|
|
567
518
|
try {
|
|
568
519
|
const resp = await this.client.im.message.create({
|
|
@@ -575,7 +526,6 @@ class FeishuChannel extends Channel {
|
|
|
575
526
|
});
|
|
576
527
|
const chatId = resp?.data?.chat_id ?? null;
|
|
577
528
|
const msgId = resp?.data?.message_id;
|
|
578
|
-
// Clean up the placeholder message
|
|
579
529
|
if (msgId) {
|
|
580
530
|
try {
|
|
581
531
|
await this.client.im.message.delete({ path: { message_id: msgId } });
|
|
@@ -594,9 +544,6 @@ class FeishuChannel extends Channel {
|
|
|
594
544
|
return null;
|
|
595
545
|
}
|
|
596
546
|
}
|
|
597
|
-
// ========================================================================
|
|
598
|
-
// Outgoing primitives (Channel interface)
|
|
599
|
-
// ========================================================================
|
|
600
547
|
async setMenu(commands) {
|
|
601
548
|
this._debug(`[menu] ${commands.length} commands. Configure in Feishu Developer Console → Bot → Custom Menu:`);
|
|
602
549
|
for (const c of commands) {
|
|
@@ -622,7 +569,6 @@ class FeishuChannel extends Channel {
|
|
|
622
569
|
async send(chatId, text, opts = {}) {
|
|
623
570
|
const rows = keyboardToRows(opts.keyboard);
|
|
624
571
|
const view = { markdown: text.trim() || '(empty)', rows };
|
|
625
|
-
// Reply to a specific message if replyTo is set
|
|
626
572
|
if (opts.replyTo) {
|
|
627
573
|
return await this.replyCard(String(opts.replyTo), view);
|
|
628
574
|
}
|
|
@@ -645,9 +591,6 @@ class FeishuChannel extends Channel {
|
|
|
645
591
|
return;
|
|
646
592
|
const card = buildCardFromView(view);
|
|
647
593
|
this._logOutgoing('edit', `chat=${chatId} msg_id=${msgId} chars=${view.markdown.length} rows=${view.rows?.length || 0}`);
|
|
648
|
-
// The Lark SDK's response interceptor returns the JSON body for any HTTP 2xx, so
|
|
649
|
-
// Feishu application errors (`code != 0`) never throw — they look like success here.
|
|
650
|
-
// Inspect the response code ourselves so callers can fall back to a fresh send.
|
|
651
594
|
let resp;
|
|
652
595
|
try {
|
|
653
596
|
resp = await this.client.im.message.patch({
|
|
@@ -692,7 +635,6 @@ class FeishuChannel extends Channel {
|
|
|
692
635
|
catch { }
|
|
693
636
|
}
|
|
694
637
|
async sendTyping(_chatId) {
|
|
695
|
-
// Feishu has no typing indicator API — no-op
|
|
696
638
|
}
|
|
697
639
|
async setMessageReaction(_chatId, msgId, reactions) {
|
|
698
640
|
const messageId = String(msgId || '').trim();
|
|
@@ -707,10 +649,6 @@ class FeishuChannel extends Channel {
|
|
|
707
649
|
}).catch(() => { });
|
|
708
650
|
}
|
|
709
651
|
}
|
|
710
|
-
// ========================================================================
|
|
711
|
-
// Feishu-specific outgoing
|
|
712
|
-
// ========================================================================
|
|
713
|
-
/** Send a text message (not card). For simple notifications. */
|
|
714
652
|
async sendText(chatId, text) {
|
|
715
653
|
const resp = await this.client.im.message.create({
|
|
716
654
|
params: { receive_id_type: 'chat_id' },
|
|
@@ -736,7 +674,6 @@ class FeishuChannel extends Channel {
|
|
|
736
674
|
});
|
|
737
675
|
return requireMessageId(resp, 'send post');
|
|
738
676
|
}
|
|
739
|
-
/** Upload an image and return the image_key. */
|
|
740
677
|
async uploadImage(imageBuffer) {
|
|
741
678
|
this._logOutgoing('uploadImage', `bytes=${imageBuffer.byteLength}`);
|
|
742
679
|
const resp = await this.client.im.image.create({
|
|
@@ -750,7 +687,6 @@ class FeishuChannel extends Channel {
|
|
|
750
687
|
throw new Error('Image upload failed: no image_key returned');
|
|
751
688
|
return imageKey;
|
|
752
689
|
}
|
|
753
|
-
/** Upload a file and return the file_key. */
|
|
754
690
|
async uploadFile(fileBuffer, fileName) {
|
|
755
691
|
const ext = path.extname(fileName).toLowerCase().slice(1);
|
|
756
692
|
const fileType = (['pdf', 'doc', 'xls', 'ppt'].includes(ext) ? ext : 'stream');
|
|
@@ -767,7 +703,6 @@ class FeishuChannel extends Channel {
|
|
|
767
703
|
throw new Error('File upload failed: no file_key returned');
|
|
768
704
|
return fileKey;
|
|
769
705
|
}
|
|
770
|
-
/** Upload and send a local file. */
|
|
771
706
|
async sendFile(chatId, filePath, opts = {}) {
|
|
772
707
|
const stat = fs.statSync(filePath);
|
|
773
708
|
if (stat.size > FILE_MAX_BYTES) {
|
|
@@ -807,9 +742,6 @@ class FeishuChannel extends Channel {
|
|
|
807
742
|
: await this.client.im.message.create({ params: { receive_id_type: 'chat_id' }, data: { receive_id: String(chatId), msg_type: 'file', content: msgContent } });
|
|
808
743
|
return requireMessageId(resp, 'send file');
|
|
809
744
|
}
|
|
810
|
-
// ========================================================================
|
|
811
|
-
// Download resources from received messages
|
|
812
|
-
// ========================================================================
|
|
813
745
|
async _downloadResource(messageId, fileKey, type, filename) {
|
|
814
746
|
const resp = await this.client.im.messageResource.get({
|
|
815
747
|
path: { message_id: messageId, file_key: fileKey },
|
|
@@ -820,7 +752,6 @@ class FeishuChannel extends Channel {
|
|
|
820
752
|
const localPath = path.join(this.workdir, `_feishu_${name}`);
|
|
821
753
|
fs.mkdirSync(this.workdir, { recursive: true });
|
|
822
754
|
await resp.writeFile(localPath);
|
|
823
|
-
// Check downloaded file size
|
|
824
755
|
const stat = fs.statSync(localPath);
|
|
825
756
|
if (stat.size > FILE_MAX_BYTES) {
|
|
826
757
|
fs.rmSync(localPath, { force: true });
|
|
@@ -828,9 +759,6 @@ class FeishuChannel extends Channel {
|
|
|
828
759
|
}
|
|
829
760
|
return localPath;
|
|
830
761
|
}
|
|
831
|
-
// ========================================================================
|
|
832
|
-
// Internal helpers
|
|
833
|
-
// ========================================================================
|
|
834
762
|
_makeCtx(chatId, messageId, from, chatType, raw, replyToMessageId) {
|
|
835
763
|
return {
|
|
836
764
|
chatId,
|
|
@@ -847,11 +775,6 @@ class FeishuChannel extends Channel {
|
|
|
847
775
|
_isAllowed(chatId) {
|
|
848
776
|
return this.allowedChatIds.size === 0 || this.allowedChatIds.has(chatId);
|
|
849
777
|
}
|
|
850
|
-
/**
|
|
851
|
-
* Add a chat to the in-memory `knownChats` set and persist it to setting.json
|
|
852
|
-
* so a future cold start (e.g. crash-respawn) can still address it. The
|
|
853
|
-
* persistence call is fire-and-forget — disk errors must not break receive.
|
|
854
|
-
*/
|
|
855
778
|
_trackChat(chatId) {
|
|
856
779
|
if (this.knownChats.has(chatId))
|
|
857
780
|
return;
|
|
@@ -873,7 +796,6 @@ class FeishuChannel extends Channel {
|
|
|
873
796
|
_cleanMention(text) {
|
|
874
797
|
return text.replace(/@_user_\d+/g, '').trim();
|
|
875
798
|
}
|
|
876
|
-
/** Extract plain text from a rich text (post) message content. */
|
|
877
799
|
_extractPostText(content) {
|
|
878
800
|
const post = content.zh_cn || content.en_us || content;
|
|
879
801
|
const parts = [];
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Feishu markdown adaptation helpers.
|
|
3
|
-
*/
|
|
4
1
|
function isGfmTableRow(line) {
|
|
5
2
|
return /^\|.+\|$/.test(line.trim());
|
|
6
3
|
}
|
|
@@ -21,7 +18,6 @@ function adaptLine(line) {
|
|
|
21
18
|
if (next.startsWith('**') && !next.endsWith('**'))
|
|
22
19
|
next = `${next}**`;
|
|
23
20
|
next = next.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1 <$2>');
|
|
24
|
-
// Strip inline backtick code — Feishu renders them with heavy styling
|
|
25
21
|
next = next.replace(/`([^`\n]+)`/g, '$1');
|
|
26
22
|
return next;
|
|
27
23
|
}
|
|
@@ -76,7 +72,6 @@ export function adaptMarkdownForFeishu(markdown) {
|
|
|
76
72
|
i++;
|
|
77
73
|
continue;
|
|
78
74
|
}
|
|
79
|
-
// Pass GFM tables through — rendered natively with card schema 2.0
|
|
80
75
|
if (i + 1 < lines.length && isGfmTableRow(lines[i]) && isGfmTableSeparator(lines[i + 1])) {
|
|
81
76
|
while (i < lines.length && isGfmTableRow(lines[i])) {
|
|
82
77
|
out.push(lines[i]);
|