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.
Files changed (155) hide show
  1. package/dashboard/dist/assets/{AgentTab-BNJ73P0m.js → AgentTab-DIP-y4dl.js} +1 -1
  2. package/dashboard/dist/assets/ConnectionModal-BCfvhkiI.js +1 -0
  3. package/dashboard/dist/assets/{DirBrowser-D-GT-G-w.js → DirBrowser-Dot7ua9-.js} +1 -1
  4. package/dashboard/dist/assets/{ExtensionsTab-BrJMulJR.js → ExtensionsTab-GioMx4S6.js} +1 -1
  5. package/dashboard/dist/assets/{IMAccessTab-auPnx468.js → IMAccessTab-C9_r8jJP.js} +1 -1
  6. package/dashboard/dist/assets/{Modal-lYfO9m-I.js → Modal-M1AP1sgR.js} +1 -1
  7. package/dashboard/dist/assets/{Modals-PRdWOIY9.js → Modals-CCm3UUfJ.js} +1 -1
  8. package/dashboard/dist/assets/{Select-BhbU-rll.js → Select-YubDM16P.js} +1 -1
  9. package/dashboard/dist/assets/{SessionPanel-_0dLWvEb.js → SessionPanel-Cnv9o96n.js} +1 -1
  10. package/dashboard/dist/assets/SystemTab-VgNivIe3.js +1 -0
  11. package/dashboard/dist/assets/index-BY7l20bi.js +23 -0
  12. package/dashboard/dist/assets/index-BlqvzDUL.js +3 -0
  13. package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
  14. package/dashboard/dist/assets/{shared-C5uXvhZJ.js → shared-C0SAInnT.js} +1 -1
  15. package/dashboard/dist/index.html +2 -2
  16. package/dist/agent/acp-client.js +0 -59
  17. package/dist/agent/artifacts.js +1 -38
  18. package/dist/agent/auto-update.js +2 -78
  19. package/dist/agent/await-resume.js +0 -20
  20. package/dist/agent/cli/auth.js +3 -51
  21. package/dist/agent/cli/catalog.js +0 -3
  22. package/dist/agent/cli/detector.js +1 -19
  23. package/dist/agent/cli/index.js +0 -3
  24. package/dist/agent/cli/registry.js +0 -25
  25. package/dist/agent/driver.js +0 -14
  26. package/dist/agent/drivers/claude-tui.js +2 -769
  27. package/dist/agent/drivers/claude.js +12 -631
  28. package/dist/agent/drivers/codex.js +11 -162
  29. package/dist/agent/drivers/gemini.js +6 -114
  30. package/dist/agent/drivers/hermes.js +0 -128
  31. package/dist/agent/goal.js +0 -45
  32. package/dist/agent/handover.js +1 -46
  33. package/dist/agent/images.js +7 -133
  34. package/dist/agent/index.js +1 -29
  35. package/dist/agent/mcp/bridge.js +1 -197
  36. package/dist/agent/mcp/extensions.js +5 -84
  37. package/dist/agent/mcp/oauth.js +2 -69
  38. package/dist/agent/mcp/registry.js +2 -33
  39. package/dist/agent/mcp/session-server.js +2 -45
  40. package/dist/agent/mcp/tools/ask-user.js +0 -6
  41. package/dist/agent/mcp/tools/await-resume.js +0 -15
  42. package/dist/agent/mcp/tools/goal.js +0 -25
  43. package/dist/agent/mcp/tools/types.js +0 -5
  44. package/dist/agent/mcp/tools/workspace.js +0 -23
  45. package/dist/agent/npm.js +0 -14
  46. package/dist/agent/session.js +86 -196
  47. package/dist/agent/skill-installer.js +3 -70
  48. package/dist/agent/skills.js +0 -43
  49. package/dist/agent/stream.js +5 -148
  50. package/dist/agent/types.js +0 -12
  51. package/dist/agent/utils.js +2 -120
  52. package/dist/bot/bot.js +3 -468
  53. package/dist/bot/command-ui.js +0 -45
  54. package/dist/bot/commands.js +0 -51
  55. package/dist/bot/headless-bot.js +0 -21
  56. package/dist/bot/host.js +1 -5
  57. package/dist/bot/human-loop.js +0 -8
  58. package/dist/bot/menu.js +0 -3
  59. package/dist/bot/orchestration.js +0 -3
  60. package/dist/bot/render-shared.js +0 -63
  61. package/dist/bot/session-hub.js +14 -92
  62. package/dist/bot/session-status.js +1 -5
  63. package/dist/bot/streaming.js +0 -53
  64. package/dist/browser-profile.js +0 -51
  65. package/dist/browser-supervisor.js +0 -75
  66. package/dist/catalog/cli-tools.js +0 -53
  67. package/dist/catalog/index.js +0 -18
  68. package/dist/catalog/local-models.js +0 -18
  69. package/dist/catalog/mcp-servers.js +0 -35
  70. package/dist/catalog/skill-repos.js +0 -32
  71. package/dist/channels/base.js +0 -19
  72. package/dist/channels/dingtalk/bot.js +0 -3
  73. package/dist/channels/dingtalk/channel.js +0 -33
  74. package/dist/channels/discord/bot.js +0 -7
  75. package/dist/channels/discord/channel.js +0 -22
  76. package/dist/channels/feishu/bot.js +0 -71
  77. package/dist/channels/feishu/channel.js +0 -78
  78. package/dist/channels/feishu/markdown.js +0 -5
  79. package/dist/channels/feishu/render.js +0 -45
  80. package/dist/channels/health.js +0 -46
  81. package/dist/channels/slack/bot.js +0 -7
  82. package/dist/channels/slack/channel.js +1 -27
  83. package/dist/channels/states.js +0 -3
  84. package/dist/channels/telegram/bot.js +0 -60
  85. package/dist/channels/telegram/channel.js +2 -99
  86. package/dist/channels/telegram/directory.js +0 -3
  87. package/dist/channels/telegram/live-preview.js +0 -8
  88. package/dist/channels/telegram/render.js +0 -30
  89. package/dist/channels/wecom/bot.js +0 -3
  90. package/dist/channels/wecom/channel.js +0 -58
  91. package/dist/channels/weixin/api.js +0 -9
  92. package/dist/channels/weixin/bot.js +0 -88
  93. package/dist/channels/weixin/channel.js +0 -6
  94. package/dist/cli/autostart.js +1 -44
  95. package/dist/cli/channel-supervisor.js +1 -59
  96. package/dist/cli/channels.js +0 -6
  97. package/dist/cli/main.js +1 -145
  98. package/dist/cli/onboarding.js +0 -6
  99. package/dist/cli/run.js +0 -11
  100. package/dist/cli/setup-wizard.js +0 -3
  101. package/dist/core/config/runtime-config.js +0 -38
  102. package/dist/core/config/user-config.js +0 -63
  103. package/dist/core/config/validation.js +0 -9
  104. package/dist/core/constants.js +0 -268
  105. package/dist/core/git.js +0 -31
  106. package/dist/core/legacy-compat.js +1 -28
  107. package/dist/core/logging.js +0 -3
  108. package/dist/core/platform.js +0 -28
  109. package/dist/core/process-control.js +0 -8
  110. package/dist/core/secrets/index.js +0 -17
  111. package/dist/core/secrets/inline-seal.js +0 -7
  112. package/dist/core/secrets/ref.js +0 -19
  113. package/dist/core/secrets/resolver.js +0 -8
  114. package/dist/core/secrets/store.js +1 -12
  115. package/dist/core/utils.js +1 -37
  116. package/dist/core/version.js +0 -3
  117. package/dist/dashboard/platform.js +1 -35
  118. package/dist/dashboard/routes/agents.js +2 -71
  119. package/dist/dashboard/routes/cli.js +0 -27
  120. package/dist/dashboard/routes/config.js +0 -54
  121. package/dist/dashboard/routes/extensions.js +0 -132
  122. package/dist/dashboard/routes/local-models.js +0 -68
  123. package/dist/dashboard/routes/models.js +0 -46
  124. package/dist/dashboard/routes/sessions.js +0 -100
  125. package/dist/dashboard/runtime.js +1 -58
  126. package/dist/dashboard/server.js +0 -49
  127. package/dist/dashboard/session-control.js +2 -69
  128. package/dist/model/catalog.js +1 -17
  129. package/dist/model/index.js +0 -15
  130. package/dist/model/injector.js +1 -181
  131. package/dist/model/provider-models.js +1 -48
  132. package/dist/model/responses-bridge.js +3 -45
  133. package/dist/model/store.js +0 -17
  134. package/dist/model/types.js +0 -12
  135. package/dist/model/validation.js +1 -18
  136. package/dist/pikichannel/adapter-pikiloom.js +0 -31
  137. package/dist/pikichannel/code.js +0 -12
  138. package/dist/pikichannel/codec.js +0 -14
  139. package/dist/pikichannel/host.js +4 -33
  140. package/dist/pikichannel/protocol.js +0 -52
  141. package/dist/pikichannel/rendezvous-broker.js +5 -26
  142. package/dist/pikichannel/rendezvous-host.js +3 -16
  143. package/dist/pikichannel/server.js +6 -55
  144. package/dist/pikichannel/transport.js +0 -25
  145. package/dist/pikichannel/transports/webrtc-host.js +0 -18
  146. package/dist/pikichannel/transports/webrtc-shared.js +4 -34
  147. package/dist/pikichannel/transports/websocket-host.js +1 -14
  148. package/dist/pikichannel/turn.js +2 -69
  149. package/dist/pikichannel/web/sdk.js +14 -71
  150. package/package.json +1 -1
  151. package/dashboard/dist/assets/ConnectionModal-Dt05H2f9.js +0 -1
  152. package/dashboard/dist/assets/SystemTab-CN_QrRKt.js +0 -1
  153. package/dashboard/dist/assets/index-CP3LYJR2.js +0 -23
  154. package/dashboard/dist/assets/index-C_neLnaO.js +0 -3
  155. 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]);