lark-remote 0.3.0 → 0.4.1

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 (60) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.en.md +6 -6
  3. package/README.md +8 -7
  4. package/dist/binder.d.ts +1 -1
  5. package/dist/binder.js +1 -1
  6. package/dist/bridge/final-usage.js +2 -2
  7. package/dist/bridge/inbound-media.d.ts +13 -20
  8. package/dist/bridge/inbound-media.js +107 -91
  9. package/dist/bridge/index.d.ts +6 -6
  10. package/dist/bridge/index.js +81 -87
  11. package/dist/cli.js +5 -1
  12. package/dist/clone.d.ts +1 -1
  13. package/dist/common/fs.js +1 -1
  14. package/dist/config/dir.d.ts +9 -0
  15. package/dist/config/dir.js +69 -0
  16. package/dist/config/index.d.ts +22 -3
  17. package/dist/config/index.js +26 -4
  18. package/dist/connector/index.d.ts +28 -1
  19. package/dist/connector/index.js +155 -31
  20. package/dist/error-classification.d.ts +7 -0
  21. package/dist/error-classification.js +37 -1
  22. package/dist/inbound/event.d.ts +41 -0
  23. package/dist/inbound/event.js +11 -0
  24. package/dist/inbound/placeholder.d.ts +26 -0
  25. package/dist/inbound/placeholder.js +149 -0
  26. package/dist/inbound/prompt.d.ts +20 -0
  27. package/dist/inbound/prompt.js +47 -0
  28. package/dist/inbound/receipt.d.ts +28 -0
  29. package/dist/inbound/receipt.js +62 -0
  30. package/dist/inbound/turn-assembler.d.ts +63 -0
  31. package/dist/inbound/turn-assembler.js +233 -0
  32. package/dist/inbound/turn.d.ts +48 -0
  33. package/dist/inbound/turn.js +10 -0
  34. package/dist/index.js +108 -73
  35. package/dist/platform/command.js +1 -1
  36. package/dist/platform/sleep-blocker.d.ts +17 -0
  37. package/dist/platform/sleep-blocker.js +73 -0
  38. package/dist/platform/terminator-win32.js +1 -1
  39. package/dist/restart.d.ts +4 -4
  40. package/dist/restart.js +5 -5
  41. package/dist/router/config/kimi.js +4 -3
  42. package/dist/router/config/opencode.d.ts +1 -1
  43. package/dist/router/config/opencode.js +11 -0
  44. package/dist/router/index.d.ts +17 -0
  45. package/dist/router/index.js +53 -15
  46. package/dist/runner/common/acp/base-acp-runner.js +7 -2
  47. package/dist/runner/common/acp/base-translator.d.ts +19 -0
  48. package/dist/runner/common/acp/base-translator.js +82 -0
  49. package/dist/runner/common/acp/protocol-types.d.ts +16 -2
  50. package/dist/runner/common/acp/protocol-types.js +3 -0
  51. package/dist/runner/common/connection-based-runner.js +6 -2
  52. package/dist/runner/kimi/acp/runner.d.ts +1 -1
  53. package/dist/runner/kimi/acp/runner.js +1 -1
  54. package/dist/runner/kimi/acp/translator.js +60 -4
  55. package/dist/runner/opencode/acp/runner.d.ts +1 -1
  56. package/dist/session/session-store.d.ts +1 -1
  57. package/dist/session/session-store.js +1 -1
  58. package/dist/workspace/index.d.ts +1 -1
  59. package/dist/workspace/index.js +1 -1
  60. package/package.json +1 -1
@@ -135,7 +135,6 @@ export class Bridge {
135
135
  this.mediaHandler = new InboundMediaHandler({
136
136
  resolveCwd: (userId) => this.resolveCwd(userId),
137
137
  getConfig: () => this.config,
138
- send: async (ctx, text) => this.sendResult({ text }, ctx),
139
138
  });
140
139
  }
141
140
  /**
@@ -209,7 +208,7 @@ export class Bridge {
209
208
  if (!settings)
210
209
  continue;
211
210
  void runner.updateApprovalMode(settings).catch((err) => {
212
- getLogger().warn(`[bridge] sync approval mode failed cwd=${cwd}: ${err.message}`);
211
+ getLogger().warn(`[lark-remote] sync approval mode failed cwd=${cwd}: ${err.message}`);
213
212
  });
214
213
  }
215
214
  }
@@ -521,7 +520,7 @@ export class Bridge {
521
520
  if (active.userId === input.userId && active.chatId === input.chatId) {
522
521
  if (input.runId && active.runId !== input.runId)
523
522
  continue;
524
- getLogger().info(`[bridge] interrupt hit runId=${active.runId} cwd=${cwd} userId=${input.userId}`);
523
+ getLogger().info(`[lark-remote] interrupt hit runId=${active.runId} cwd=${cwd} userId=${input.userId}`);
525
524
  // Stop the EXACT runner instance that is running (carried by activeRun),
526
525
  // not whatever getRunner(cwd) would return now. clearRunners() may have
527
526
  // emptied the cache mid-run, in which case getRunner creates a fresh empty
@@ -564,7 +563,7 @@ export class Bridge {
564
563
  if (stoppedSlot !== undefined) {
565
564
  this.queueManager.resetExecutingCount(cwd, stoppedSlot);
566
565
  }
567
- getLogger().info(`[bridge] interrupt stop done runId=${active.runId}`);
566
+ getLogger().info(`[lark-remote] interrupt stop done runId=${active.runId}`);
568
567
  return true;
569
568
  }
570
569
  }
@@ -576,16 +575,16 @@ export class Bridge {
576
575
  if (b.userId === input.userId && b.chatId === input.chatId) {
577
576
  if (input.runId && runId !== input.runId)
578
577
  continue;
579
- getLogger().info(`[bridge] interrupt hit bash runId=${runId} cwd=${b.cwd} userId=${input.userId}`);
578
+ getLogger().info(`[lark-remote] interrupt hit bash runId=${runId} cwd=${b.cwd} userId=${input.userId}`);
580
579
  await b.bashRunner.stop({ immediate: true });
581
580
  // Update card to show interrupted state
582
581
  await b.cardSession.finish('interrupted', { exitCode: -1 });
583
582
  this.activeBashRuns.delete(runId);
584
- getLogger().info(`[bridge] interrupt bash stop done runId=${runId}`);
583
+ getLogger().info(`[lark-remote] interrupt bash stop done runId=${runId}`);
585
584
  return true;
586
585
  }
587
586
  }
588
- getLogger().debug(`[bridge] interrupt miss userId=${input.userId}`);
587
+ getLogger().debug(`[lark-remote] interrupt miss userId=${input.userId}`);
589
588
  return false;
590
589
  }
591
590
  /** Send a result message to the user (§7). Returns true on success, false on failure. */
@@ -595,26 +594,26 @@ export class Bridge {
595
594
  // 静态卡片体积保护:确保卡片不超过飞书大小限制
596
595
  const { card: safeCard, wasTruncated, reason, bytesBefore, bytesAfter, } = enforceCardBudget(result.card);
597
596
  if (wasTruncated) {
598
- getLogger().info(`[bridge] card size exceeded limit, applied budget enforcement: reason=${reason} bytesBefore=${bytesBefore} bytesAfter=${bytesAfter}`);
597
+ getLogger().info(`[lark-remote] card size exceeded limit, applied budget enforcement: reason=${reason} bytesBefore=${bytesBefore} bytesAfter=${bytesAfter}`);
599
598
  }
600
- getLogger().debug(`[bridge] sendResult kind=card chatId=${ctx.chatId}`);
599
+ getLogger().debug(`[lark-remote] sendResult kind=card chatId=${ctx.chatId}`);
601
600
  await this.connector.sendWithRetry(ctx.chatId, { card: safeCard }, { replyTo: ctx.messageId });
602
601
  return true;
603
602
  }
604
603
  else if (result.markdown) {
605
- getLogger().debug(`[bridge] sendResult kind=markdown chatId=${ctx.chatId}`);
604
+ getLogger().debug(`[lark-remote] sendResult kind=markdown chatId=${ctx.chatId}`);
606
605
  await this.connector.sendWithRetry(ctx.chatId, { markdown: result.markdown }, { replyTo: ctx.messageId });
607
606
  return true;
608
607
  }
609
608
  else if (result.text) {
610
- getLogger().debug(`[bridge] sendResult kind=text chatId=${ctx.chatId}`);
609
+ getLogger().debug(`[lark-remote] sendResult kind=text chatId=${ctx.chatId}`);
611
610
  await this.connector.sendWithRetry(ctx.chatId, { text: result.text }, { replyTo: ctx.messageId });
612
611
  return true;
613
612
  }
614
613
  return false;
615
614
  }
616
615
  catch (err) {
617
- getLogger().error('[bridge] failed to send result:', err);
616
+ getLogger().error('[lark-remote] failed to send result:', err);
618
617
  // 不静默:向用户发送一条纯文本消息,告知具体错误(如飞书 11310 element exceeds
619
618
  // the limit)。如果用户看到空白无反馈,无法判断是命令无效还是卡片发送失败。
620
619
  try {
@@ -622,7 +621,7 @@ export class Bridge {
622
621
  await this.connector.sendWithRetry(ctx.chatId, { text: `⚠️ 卡片发送失败:${errMsg}` }, { replyTo: ctx.messageId });
623
622
  }
624
623
  catch (fallbackErr) {
625
- getLogger().error('[bridge] fallback text send also failed:', fallbackErr);
624
+ getLogger().error('[lark-remote] fallback text send also failed:', fallbackErr);
626
625
  }
627
626
  return false;
628
627
  }
@@ -642,18 +641,18 @@ export class Bridge {
642
641
  */
643
642
  async updateCardInPlace(card, ctx) {
644
643
  if (!ctx.messageId) {
645
- getLogger().warn('[bridge] updateCardInPlace missing messageId, falling back to sendResult');
644
+ getLogger().warn('[lark-remote] updateCardInPlace missing messageId, falling back to sendResult');
646
645
  await this.sendResult({ card }, ctx);
647
646
  return false;
648
647
  }
649
648
  try {
650
- getLogger().debug(`[bridge] updateCardInPlace messageId=${ctx.messageId}`);
649
+ getLogger().debug(`[lark-remote] updateCardInPlace messageId=${ctx.messageId}`);
651
650
  await this.connector.updateCard(ctx.messageId, card);
652
651
  return true;
653
652
  }
654
653
  catch (err) {
655
654
  const errMsg = err.message ?? String(err);
656
- getLogger().warn(`[bridge] updateCardInPlace failed, falling back to sendResult: ${errMsg}`);
655
+ getLogger().warn(`[lark-remote] updateCardInPlace failed, falling back to sendResult: ${errMsg}`);
657
656
  // 先尝试 fallback 发卡片;若卡片本身有问题(如 11310),
658
657
  // sendResult 内部 catch 会发纯文本通知用户。
659
658
  await this.sendResult({ card }, ctx);
@@ -663,25 +662,20 @@ export class Bridge {
663
662
  /** Send a file to the user via Feishu. */
664
663
  async sendFile(filePath, ctx) {
665
664
  try {
666
- getLogger().debug(`[bridge] sendFile path=${filePath} chatId=${ctx.chatId}`);
665
+ getLogger().debug(`[lark-remote] sendFile path=${filePath} chatId=${ctx.chatId}`);
667
666
  await this.connector.sendFile(ctx.chatId, filePath);
668
667
  }
669
668
  catch (err) {
670
- getLogger().error('[bridge] failed to send file:', err);
669
+ getLogger().error('[lark-remote] failed to send file:', err);
671
670
  await this.sendResult({ text: `发送文件失败: ${err.message}` }, ctx);
672
671
  }
673
672
  }
674
- /** 入站媒体落盘入口(connector 下载完成后调用)。 */
675
- async onInboundMedia(payload) {
676
- await this.mediaHandler.handle(payload);
677
- }
678
- /** 文本消息到达时立即冲刷待合批的媒体保存提示。 */
679
- flushMediaNotifications(userId, chatId) {
680
- this.mediaHandler.flushAll(userId, chatId);
681
- }
682
- /** 冲刷全部待合批提示(/exit、/restart 干净退出前调用)。 */
683
- async flushAllMediaNotifications() {
684
- await this.mediaHandler.flushAllPending();
673
+ /**
674
+ * 入站媒体落盘(connector 下载完成后调用):只落盘并回报结果,
675
+ * 时间语义与回执由 `InboundTurnAssembler` 统一负责。
676
+ */
677
+ async saveInboundMedia(payload) {
678
+ return this.mediaHandler.save(payload);
685
679
  }
686
680
  /**
687
681
  * Resolve the working directory for a user.
@@ -697,7 +691,7 @@ export class Bridge {
697
691
  // NOTE: fallback uses insertion order, not sort preference — by design
698
692
  // (cwd fallback stays insertion-order for now)
699
693
  cwd = workspaces[0].path;
700
- getLogger().info(`[bridge] resolveCwd fallback cwd=${cwd} (first saved workspace)`);
694
+ getLogger().info(`[lark-remote] resolveCwd fallback cwd=${cwd} (first saved workspace)`);
701
695
  }
702
696
  }
703
697
  return cwd;
@@ -724,11 +718,11 @@ export class Bridge {
724
718
  return;
725
719
  }
726
720
  const entry = this.sessionStore.get(ctx.userId);
727
- getLogger().debug(`[bridge] forward entry userId=${ctx.userId} chatId=${ctx.chatId} cwd=${cwd} ` +
721
+ getLogger().debug(`[lark-remote] forward entry userId=${ctx.userId} chatId=${ctx.chatId} cwd=${cwd} ` +
728
722
  `agent=${agentKind} sessionId=${opts?.binding?.sessionId ?? entry?.sessions?.get(agentKind) ?? '(none)'} message=${message.slice(0, 100)}`);
729
723
  // Check if current workspace already has a run in progress
730
724
  if (this.activeRuns.has(cwd)) {
731
- getLogger().warn(`[bridge] workspace busy, dropping message userId=${ctx.userId} cwd=${cwd}`);
725
+ getLogger().warn(`[lark-remote] workspace busy, dropping message userId=${ctx.userId} cwd=${cwd}`);
732
726
  await this.sendResult({ text: '此 workspace 正在处理中,请 /stop 后重试' }, ctx);
733
727
  return;
734
728
  }
@@ -759,7 +753,7 @@ export class Bridge {
759
753
  */
760
754
  createRunSession(ctx, cwd, runner, agentKind = this.config.defaultAgent) {
761
755
  const runId = randomUUID();
762
- getLogger().debug(`[bridge] creating runCard runId=${runId}`);
756
+ getLogger().debug(`[lark-remote] creating runCard runId=${runId}`);
763
757
  // D5: agentKind 来自入队绑定(forwardToClaude 解析),不再读 live config。
764
758
  // 缓存键 (cwd, agentKind) 和 eviction 都用这个值(review P2-3)。
765
759
  const cardSession = new RunCardSession({
@@ -785,7 +779,7 @@ export class Bridge {
785
779
  agentKind,
786
780
  };
787
781
  this.activeRuns.set(cwd, activeRun);
788
- getLogger().info(`[bridge] activeRuns.set cwd=${cwd} runId=${runId}`);
782
+ getLogger().info(`[lark-remote] activeRuns.set cwd=${cwd} runId=${runId}`);
789
783
  return { runId, cardSession, activeRun };
790
784
  }
791
785
  /**
@@ -860,18 +854,18 @@ export class Bridge {
860
854
  });
861
855
  try {
862
856
  try {
863
- getLogger().debug(`[bridge] cardSession.start() begin runId=${runId}`);
857
+ getLogger().debug(`[lark-remote] cardSession.start() begin runId=${runId}`);
864
858
  await cardSession.start();
865
- getLogger().info(`[bridge] cardSession.start() ok runId=${runId}`);
859
+ getLogger().info(`[lark-remote] cardSession.start() ok runId=${runId}`);
866
860
  }
867
861
  catch (err) {
868
- getLogger().warn('[bridge] card stream unavailable, will use static fallback:', err);
862
+ getLogger().warn('[lark-remote] card stream unavailable, will use static fallback:', err);
869
863
  // Card stream failed - continue with static fallback below, but ensure
870
864
  // we don't silently fail if the fallback also fails (covered in catch below).
871
865
  }
872
866
  if (cardSession.currentState.terminal !== 'running') {
873
867
  // Card session did not enter running state - still try to send static fallback
874
- getLogger().info(`[bridge] cardSession not running, using static fallback`);
868
+ getLogger().info(`[lark-remote] cardSession not running, using static fallback`);
875
869
  }
876
870
  // P2-1: 单 interval 看门狗。事件到来时只刷新 lastEventTs(无 timer 操作);
877
871
  // interval 周期检查 `now - lastEventTs > idleTimeoutMs`,超时才触发 stop。
@@ -891,7 +885,7 @@ export class Bridge {
891
885
  if (idleInterval)
892
886
  clearInterval(idleInterval);
893
887
  idleInterval = null;
894
- getLogger().warn(`[bridge] claude idle timeout, stopping process runId=${runId}`);
888
+ getLogger().warn(`[lark-remote] claude idle timeout, stopping process runId=${runId}`);
895
889
  void Promise.allSettled([
896
890
  cardSession.finish('idle_timeout', {
897
891
  idleTimeoutMinutes: Math.max(1, Math.round(this.idleTimeoutMs / 60_000)),
@@ -912,7 +906,7 @@ export class Bridge {
912
906
  }
913
907
  resetIdle();
914
908
  let contextLength;
915
- getLogger().info(`[bridge] runner.run() begin runId=${runId} message=${message.slice(0, 100)}`);
909
+ getLogger().info(`[lark-remote] runner.run() begin runId=${runId} message=${message.slice(0, 100)}`);
916
910
  // Extract agent-specific options from config
917
911
  const agentOpts = this.getAgentRunOptions(agentKind);
918
912
  const runOpts = { cwd, sessionId, ...agentOpts };
@@ -940,7 +934,7 @@ export class Bridge {
940
934
  // task-notification 注入触发 Claude 重发 init,已终止的 run 被复活)。
941
935
  const pointerMoved = this.sessionStore.getSessionEpoch(ctx.userId, agentKind) !== sessionEpochAtStart;
942
936
  if (pointerMoved) {
943
- getLogger().info(`[bridge] system.init write-back skipped: session pointer moved since run start runId=${runId} sessionId=${event.session_id}`);
937
+ getLogger().info(`[lark-remote] system.init write-back skipped: session pointer moved since run start runId=${runId} sessionId=${event.session_id}`);
944
938
  }
945
939
  else {
946
940
  // L5: Use session's real directory from event.cwd (not runner's cwd parameter)
@@ -952,7 +946,7 @@ export class Bridge {
952
946
  if (hasCwd && event.cwd && event.cwd !== cwd) {
953
947
  // Session has its own cwd (e.g. EnterWorktree relocate). Keep the
954
948
  // workspace cwd unchanged; record the session's actual cwd in sessionCwds.
955
- getLogger().info(`[bridge] system.init: workspace cwd=${cwd}, session cwd=${event.cwd}`);
949
+ getLogger().info(`[lark-remote] system.init: workspace cwd=${cwd}, session cwd=${event.cwd}`);
956
950
  this.sessionStore.setSessionIdAndSessionCwd(ctx.userId, agentKind, event.session_id, realCwd);
957
951
  }
958
952
  else {
@@ -962,7 +956,7 @@ export class Bridge {
962
956
  this.sessionStore.setSessionIdAndCwd(ctx.userId, agentKind, event.session_id, realCwd, bootstrapSessionCwd);
963
957
  }
964
958
  }
965
- getLogger().info(`[bridge] system.init received runId=${runId} sessionId=${event.session_id} cwd=${event.cwd}`);
959
+ getLogger().info(`[lark-remote] system.init received runId=${runId} sessionId=${event.session_id} cwd=${event.cwd}`);
966
960
  }
967
961
  if (event.type === 'turn_started') {
968
962
  // app-server runner 在 turn setup 成功后补发 synthetic system.init
@@ -972,10 +966,10 @@ export class Bridge {
972
966
  const pointerMoved = this.sessionStore.getSessionEpoch(ctx.userId, agentKind) !== sessionEpochAtStart;
973
967
  if (!pointerMoved) {
974
968
  this.sessionStore.setSessionIdAndCwd(ctx.userId, agentKind, event.threadId, cwd);
975
- getLogger().info(`[bridge] turn_started session write-back runId=${runId} sessionId=${event.threadId}`);
969
+ getLogger().info(`[lark-remote] turn_started session write-back runId=${runId} sessionId=${event.threadId}`);
976
970
  }
977
971
  else {
978
- getLogger().info(`[bridge] turn_started write-back skipped: session pointer moved since run start runId=${runId} sessionId=${event.threadId}`);
972
+ getLogger().info(`[lark-remote] turn_started write-back skipped: session pointer moved since run start runId=${runId} sessionId=${event.threadId}`);
979
973
  }
980
974
  }
981
975
  // Track context length from compaction events
@@ -999,7 +993,7 @@ export class Bridge {
999
993
  // 卡片不会把半初始化的 run 当作已收尾。
1000
994
  const preInitResult = !sawInit;
1001
995
  if (preInitResult && usageAuthority !== 'live') {
1002
- getLogger().info(`[bridge] pre-init result ignored (resume replay) runId=${runId}`);
996
+ getLogger().info(`[lark-remote] pre-init result ignored (resume replay) runId=${runId}`);
1003
997
  // Still reset idle timer — the CLI is alive and producing events.
1004
998
  resetIdle();
1005
999
  // Do NOT push to cardSession: the run-state reducer would also
@@ -1012,8 +1006,8 @@ export class Bridge {
1012
1006
  }
1013
1007
  resultNotSuccess = event.subtype !== 'success';
1014
1008
  getLogger().info(preInitResult
1015
- ? `[bridge] pre-init result from live runner (app-server setup failure) runId=${runId}`
1016
- : `[bridge] result event received runId=${runId}`);
1009
+ ? `[lark-remote] pre-init result from live runner (app-server setup failure) runId=${runId}`
1010
+ : `[lark-remote] result event received runId=${runId}`);
1017
1011
  // app-server 模式 setup 失败路径(thread/start 成功后 turn/start 失败)
1018
1012
  // 不会有 turn_started 事件,store 不会写回新线程 id;runner 已在 result
1019
1013
  // 里兜底上报(review P3-10)。此处仅当 runner 声明 live usage(app-server)
@@ -1023,7 +1017,7 @@ export class Bridge {
1023
1017
  event.session_id &&
1024
1018
  this.sessionStore.getSessionEpoch(ctx.userId, agentKind) === sessionEpochAtStart) {
1025
1019
  this.sessionStore.setSessionIdAndCwd(ctx.userId, agentKind, event.session_id, cwd);
1026
- getLogger().info(`[bridge] result session write-back runId=${runId} sessionId=${event.session_id}`);
1020
+ getLogger().info(`[lark-remote] result session write-back runId=${runId} sessionId=${event.session_id}`);
1027
1021
  }
1028
1022
  // result 后进入 finalizing 非终态(暂存 subtype/errorMsg),由 for-await
1029
1023
  // 自然结束后在 finally 转 done/error。提取 usage 信息。
@@ -1065,13 +1059,13 @@ export class Bridge {
1065
1059
  await this.connector.removeReactionByEmoji(ctx.messageId, 'Typing');
1066
1060
  }
1067
1061
  catch (err) {
1068
- getLogger().warn(`[bridge] remove Typing reaction failed runId=${runId}: ${errorMessage(err)}`);
1062
+ getLogger().warn(`[lark-remote] remove Typing reaction failed runId=${runId}: ${errorMessage(err)}`);
1069
1063
  }
1070
1064
  try {
1071
1065
  await this.connector.addReaction(ctx.messageId, 'Typing');
1072
1066
  }
1073
1067
  catch (err) {
1074
- getLogger().warn(`[bridge] re-add Typing reaction failed runId=${runId}: ${errorMessage(err)}`);
1068
+ getLogger().warn(`[lark-remote] re-add Typing reaction failed runId=${runId}: ${errorMessage(err)}`);
1075
1069
  }
1076
1070
  })();
1077
1071
  let coordinator = this.approvalCoordinators.get(runId);
@@ -1095,7 +1089,7 @@ export class Bridge {
1095
1089
  },
1096
1090
  });
1097
1091
  this.approvalCoordinators.set(runId, coordinator);
1098
- getLogger().info(`[bridge] approval coordinator created runId=${runId.slice(0, 8)} requestId=${event.requestId}`);
1092
+ getLogger().info(`[lark-remote] approval coordinator created runId=${runId.slice(0, 8)} requestId=${event.requestId}`);
1099
1093
  }
1100
1094
  coordinator.onRequested(event);
1101
1095
  }
@@ -1116,7 +1110,7 @@ export class Bridge {
1116
1110
  resetIdle();
1117
1111
  await cardSession.push(event);
1118
1112
  }
1119
- getLogger().info(`[bridge] runner stream end runId=${runId} sawResult=${sawResult}`);
1113
+ getLogger().info(`[lark-remote] runner stream end runId=${runId} sawResult=${sawResult}`);
1120
1114
  // §9.12 红线(§P1-2 方案 A 第 2 点):for-await 自然结束 = CLI 已退出,
1121
1115
  // 看门狗使命结束,立即摘除 interval(不再等 finally),堵掉「stream 已结束、
1122
1116
  // 收尾处理跨过死线被误标 idle_timeout」的同源小窗口(P2-4 随本修复一并关闭)。
@@ -1167,7 +1161,7 @@ export class Bridge {
1167
1161
  finalCacheCreationTokens = finalUsage?.cacheCreationTokens ?? finalCacheCreationTokens;
1168
1162
  finalTotalTokens = finalUsage?.totalTokens ?? finalTotalTokens;
1169
1163
  }
1170
- getLogger().info(`[bridge] final usage runId=${runId} contextLength=${finalContextLength} contextLimit=${finalContextLimit ?? 'undefined'} compactCount=${finalCompactCount ?? 'undefined'} cacheRead=${finalCacheReadTokens ?? 'undefined'} cacheCreate=${finalCacheCreationTokens ?? 'undefined'}`);
1164
+ getLogger().info(`[lark-remote] final usage runId=${runId} contextLength=${finalContextLength} contextLimit=${finalContextLimit ?? 'undefined'} compactCount=${finalCompactCount ?? 'undefined'} cacheRead=${finalCacheReadTokens ?? 'undefined'} cacheCreate=${finalCacheCreationTokens ?? 'undefined'}`);
1171
1165
  // for-await 自然结束 = stream 关闭 = CLI 进程退出。
1172
1166
  // 进程退出(for-await end)= 唯一终态触发源。
1173
1167
  // 1) finalizing(result 已收到)-> 转 done/error + usage meta
@@ -1200,14 +1194,14 @@ export class Bridge {
1200
1194
  // else: 已终态且未收到 result(如 spawn 失败后 interrupted)-> 无 usage 可补,跳过
1201
1195
  }
1202
1196
  catch (err) {
1203
- getLogger().error(`[bridge] claude run error runId=${runId}:`, err);
1197
+ getLogger().error(`[lark-remote] claude run error runId=${runId}:`, err);
1204
1198
  // P1-11 双保险:run() 的 finally 已负责杀子进程,但 catch 路径再补一次
1205
1199
  // runner.stop()(与 bash 路径对齐),覆盖任何 finally 之外的残留窗口。
1206
1200
  try {
1207
1201
  await runner.stop();
1208
1202
  }
1209
1203
  catch (stopErr) {
1210
- getLogger().warn(`[bridge] runner stop failed:`, stopErr);
1204
+ getLogger().warn(`[lark-remote] runner stop failed:`, stopErr);
1211
1205
  }
1212
1206
  // 终态守卫:running 或 finalizing(非终态)-> 转 error;
1213
1207
  // 已终态(interrupted/idle_timeout/done/error)-> 保留首终态,仅补充 usage。
@@ -1221,7 +1215,7 @@ export class Bridge {
1221
1215
  });
1222
1216
  }
1223
1217
  else {
1224
- getLogger().info(`[bridge] skip error finish: state already terminal (${catchTerminal}) runId=${runId}`);
1218
+ getLogger().info(`[lark-remote] skip error finish: state already terminal (${catchTerminal}) runId=${runId}`);
1225
1219
  if (sawResult) {
1226
1220
  await cardSession.finish(catchTerminal, usageMeta());
1227
1221
  }
@@ -1244,7 +1238,7 @@ export class Bridge {
1244
1238
  async finalizeRun(cardSession, activeRun, ctx, cwd) {
1245
1239
  const runId = activeRun.runId;
1246
1240
  try {
1247
- getLogger().debug(`[bridge] finally settle runId=${runId} state.terminal=${cardSession.currentState.terminal}`);
1241
+ getLogger().debug(`[lark-remote] finally settle runId=${runId} state.terminal=${cardSession.currentState.terminal}`);
1248
1242
  const settleResult = await cardSession.settle();
1249
1243
  let runCardSent = false;
1250
1244
  if (settleResult === 'unsent') {
@@ -1316,7 +1310,7 @@ export class Bridge {
1316
1310
  });
1317
1311
  }
1318
1312
  }
1319
- getLogger().info(`[bridge] activeRuns.delete cwd=${cwd} runId=${runId}`);
1313
+ getLogger().info(`[lark-remote] activeRuns.delete cwd=${cwd} runId=${runId}`);
1320
1314
  }
1321
1315
  }
1322
1316
  }
@@ -1353,14 +1347,14 @@ export class Bridge {
1353
1347
  // sessions produced >28KB cards that Feishu silently rejected).
1354
1348
  const sent = await this.sendResult({ card }, ctx);
1355
1349
  if (sent) {
1356
- getLogger().debug(`[bridge] completion notification sent sessionId=${sessionId} chatId=${ctx.chatId}`);
1350
+ getLogger().debug(`[lark-remote] completion notification sent sessionId=${sessionId} chatId=${ctx.chatId}`);
1357
1351
  }
1358
1352
  else {
1359
- getLogger().warn(`[bridge] failed to send completion notification card sessionId=${sessionId}`);
1353
+ getLogger().warn(`[lark-remote] failed to send completion notification card sessionId=${sessionId}`);
1360
1354
  }
1361
1355
  }
1362
1356
  catch (err) {
1363
- getLogger().warn(`[bridge] failed to send completion notification card:`, err);
1357
+ getLogger().warn(`[lark-remote] failed to send completion notification card:`, err);
1364
1358
  }
1365
1359
  }
1366
1360
  // =============================================================================
@@ -1375,7 +1369,7 @@ export class Bridge {
1375
1369
  if (!coordinator) {
1376
1370
  // P2-7: run 已结束(coordinator 已释放)时点击审批按钮必须给用户明确反馈,
1377
1371
  // 不能静默。router 会把该异常转成 error toast。
1378
- getLogger().warn(`[bridge] no approval coordinator for runId=${runId.slice(0, 8)}`);
1372
+ getLogger().warn(`[lark-remote] no approval coordinator for runId=${runId.slice(0, 8)}`);
1379
1373
  throw new Error('任务已结束,审批无法响应');
1380
1374
  }
1381
1375
  return fn(coordinator);
@@ -1386,7 +1380,7 @@ export class Bridge {
1386
1380
  */
1387
1381
  async handleApprovalRespond(opts) {
1388
1382
  const log = getLogger();
1389
- log.info(`[bridge] handleApprovalRespond runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} decision=${opts.decision}`);
1383
+ log.info(`[lark-remote] handleApprovalRespond runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} decision=${opts.decision}`);
1390
1384
  return this.withCoordinator(opts.runId, async (coordinator) => {
1391
1385
  // Build ApprovalAction from the decision string
1392
1386
  const action = decisionToApprovalAction(opts.decision);
@@ -1408,7 +1402,7 @@ export class Bridge {
1408
1402
  */
1409
1403
  async handleApprovalToggle(opts) {
1410
1404
  const log = getLogger();
1411
- log.info(`[bridge] handleApprovalToggle runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} permId=${opts.permId}`);
1405
+ log.info(`[lark-remote] handleApprovalToggle runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} permId=${opts.permId}`);
1412
1406
  return this.withCoordinator(opts.runId, async (coordinator) => {
1413
1407
  // selected 由卡片根据当前渲染状态传回(取反),不再硬编码只授不撤。
1414
1408
  const toggleAction = {
@@ -1424,7 +1418,7 @@ export class Bridge {
1424
1418
  */
1425
1419
  async handleApprovalAnswer(opts) {
1426
1420
  const log = getLogger();
1427
- log.info(`[bridge] handleApprovalAnswer runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} question=${opts.questionIndex}`);
1421
+ log.info(`[lark-remote] handleApprovalAnswer runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} question=${opts.questionIndex}`);
1428
1422
  return this.withCoordinator(opts.runId, (coordinator) => coordinator.toggleAnswer({ questionIndex: opts.questionIndex, option: opts.option }, { requestId: opts.requestId, nonce: opts.nonce }));
1429
1423
  }
1430
1424
  /**
@@ -1433,7 +1427,7 @@ export class Bridge {
1433
1427
  */
1434
1428
  async handleApprovalAnswerSubmit(opts) {
1435
1429
  const log = getLogger();
1436
- log.info(`[bridge] handleApprovalAnswerSubmit runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} question=${opts.questionIndex}`);
1430
+ log.info(`[lark-remote] handleApprovalAnswerSubmit runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} question=${opts.questionIndex}`);
1437
1431
  return this.withCoordinator(opts.runId, (coordinator) => coordinator.submitAnswers({ questionIndex: opts.questionIndex }, { requestId: opts.requestId, nonce: opts.nonce }));
1438
1432
  }
1439
1433
  /**
@@ -1442,7 +1436,7 @@ export class Bridge {
1442
1436
  */
1443
1437
  async handleApprovalAnswerCustom(opts) {
1444
1438
  const log = getLogger();
1445
- log.info(`[bridge] handleApprovalAnswerCustom runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} question=${opts.questionIndex}`);
1439
+ log.info(`[lark-remote] handleApprovalAnswerCustom runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} question=${opts.questionIndex}`);
1446
1440
  return this.withCoordinator(opts.runId, (coordinator) => coordinator.answerCustom({ questionIndex: opts.questionIndex, text: opts.text }, { requestId: opts.requestId, nonce: opts.nonce }));
1447
1441
  }
1448
1442
  /**
@@ -1452,7 +1446,7 @@ export class Bridge {
1452
1446
  */
1453
1447
  async handleApprovalAnswerNote(opts) {
1454
1448
  const log = getLogger();
1455
- log.info(`[bridge] handleApprovalAnswerNote runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} question=${opts.questionIndex}`);
1449
+ log.info(`[lark-remote] handleApprovalAnswerNote runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} question=${opts.questionIndex}`);
1456
1450
  return this.withCoordinator(opts.runId, (coordinator) => coordinator.answerNote({ questionIndex: opts.questionIndex, text: opts.text }, { requestId: opts.requestId, nonce: opts.nonce }));
1457
1451
  }
1458
1452
  /**
@@ -1463,7 +1457,7 @@ export class Bridge {
1463
1457
  */
1464
1458
  async handleCodexCompact(value, ctx) {
1465
1459
  const log = getLogger();
1466
- log.info(`[bridge] handleCodexCompact userId=${ctx.userId} runId=${value.runId?.slice(0, 8)}...`);
1460
+ log.info(`[lark-remote] handleCodexCompact userId=${ctx.userId} runId=${value.runId?.slice(0, 8)}...`);
1467
1461
  const { runId } = value;
1468
1462
  if (!runId) {
1469
1463
  await this.sendResult({ text: '⚠️ 无效的 Compact 请求,缺少 runId' }, ctx);
@@ -1494,7 +1488,7 @@ export class Bridge {
1494
1488
  await this.sendResult({ text: '⚠️ 当前运行模式不支持 Compact' }, ctx);
1495
1489
  return;
1496
1490
  }
1497
- log.info(`[bridge] handleCodexCompact executing runCompact for runId=${runId} cwd=${cwd}`);
1491
+ log.info(`[lark-remote] handleCodexCompact executing runCompact for runId=${runId} cwd=${cwd}`);
1498
1492
  await this.streamCodexCompact({
1499
1493
  sessionId: last.sessionId,
1500
1494
  cwd,
@@ -1529,7 +1523,7 @@ export class Bridge {
1529
1523
  }
1530
1524
  }
1531
1525
  catch (err) {
1532
- getLogger().warn(`[bridge] readCompactionState failed sessionId=${sessionId}: ${errorMessage(err)}`);
1526
+ getLogger().warn(`[lark-remote] readCompactionState failed sessionId=${sessionId}: ${errorMessage(err)}`);
1533
1527
  }
1534
1528
  return false;
1535
1529
  }
@@ -1546,7 +1540,7 @@ export class Bridge {
1546
1540
  async streamCodexCompact(opts) {
1547
1541
  const log = getLogger();
1548
1542
  const { sessionId, cwd, agentKind, runner, ctx } = opts;
1549
- log.info(`[bridge] streamCodexCompact sessionId=${sessionId} cwd=${cwd}`);
1543
+ log.info(`[lark-remote] streamCodexCompact sessionId=${sessionId} cwd=${cwd}`);
1550
1544
  // 防御性能力检查:调用方已校验(runId / resume 两条路径),此处双保险并
1551
1545
  // 让 TS 收窄 runner 类型('runCompact' in runner 之后的 cast 才合法)。
1552
1546
  if (!('runCompact' in runner) ||
@@ -1618,10 +1612,10 @@ export class Bridge {
1618
1612
  cumulativeCacheReadTokens: finalUsage?.cumulativeCacheReadTokens,
1619
1613
  cumulativeCacheCreationTokens: finalUsage?.cumulativeCacheCreationTokens,
1620
1614
  });
1621
- log.info(`[bridge] streamCodexCompact finished sessionId=${sessionId} sawResult=${sawResult} subtype=${resultSubtype ?? 'none'}`);
1615
+ log.info(`[lark-remote] streamCodexCompact finished sessionId=${sessionId} sawResult=${sawResult} subtype=${resultSubtype ?? 'none'}`);
1622
1616
  }
1623
1617
  catch (err) {
1624
- log.error(`[bridge] streamCodexCompact failed: ${errorMessage(err)}`);
1618
+ log.error(`[lark-remote] streamCodexCompact failed: ${errorMessage(err)}`);
1625
1619
  await cardSession.finish('error', { errorMsg: errorMessage(err) });
1626
1620
  }
1627
1621
  }
@@ -1636,7 +1630,7 @@ export class Bridge {
1636
1630
  */
1637
1631
  async handleResumeCompact(value, ctx) {
1638
1632
  const log = getLogger();
1639
- log.info(`[bridge] handleResumeCompact userId=${ctx.userId} sessionId=${value.sessionId?.slice(0, 16)} agent=${value.agent ?? 'default'}`);
1633
+ log.info(`[lark-remote] handleResumeCompact userId=${ctx.userId} sessionId=${value.sessionId?.slice(0, 16)} agent=${value.agent ?? 'default'}`);
1640
1634
  const { sessionId } = value;
1641
1635
  if (!sessionId) {
1642
1636
  await this.sendResult({ text: '⚠️ 无效的 Compact 请求,缺少 sessionId' }, ctx);
@@ -1682,7 +1676,7 @@ export class Bridge {
1682
1676
  await this.sendResult({ text: '⚠️ 当前运行模式不支持 Compact' }, ctx);
1683
1677
  return;
1684
1678
  }
1685
- log.info(`[bridge] handleResumeCompact executing runCompact sessionId=${sessionId} cwd=${cwd}`);
1679
+ log.info(`[lark-remote] handleResumeCompact executing runCompact sessionId=${sessionId} cwd=${cwd}`);
1686
1680
  await this.streamCodexCompact({ sessionId, cwd, agentKind, runner, ctx });
1687
1681
  }
1688
1682
  /**
@@ -1693,13 +1687,13 @@ export class Bridge {
1693
1687
  */
1694
1688
  async executeBash(command, ctx) {
1695
1689
  const cwd = this.resolveCwd(ctx.userId);
1696
- getLogger().info(`[bridge] executeBash start userId=${ctx.userId} sessionCwd=${this.sessionStore.get(ctx.userId)?.cwd} command="${command.slice(0, 30)}..."`);
1690
+ getLogger().info(`[lark-remote] executeBash start userId=${ctx.userId} sessionCwd=${this.sessionStore.get(ctx.userId)?.cwd} command="${command.slice(0, 30)}..."`);
1697
1691
  if (!cwd) {
1698
- getLogger().info(`[bridge] executeBash no cwd, sending error`);
1692
+ getLogger().info(`[lark-remote] executeBash no cwd, sending error`);
1699
1693
  await this.sendResult({ text: '请先使用 /cd <path> 设置工作目录,或保存一个 workspace (/ws save <name>)' }, ctx);
1700
1694
  return;
1701
1695
  }
1702
- getLogger().info(`[bridge] executeBash proceeding cwd=${cwd}`);
1696
+ getLogger().info(`[lark-remote] executeBash proceeding cwd=${cwd}`);
1703
1697
  // `!` commands bypass the serial queue entirely (design.md §9.6):
1704
1698
  // index.ts dispatches `!` to router.handle → executeBash directly, NOT via
1705
1699
  // bridge.enqueue. bash runs in parallel with same-workspace claude runs.
@@ -1715,7 +1709,7 @@ export class Bridge {
1715
1709
  * bypassing the serial queue — see executeBash).
1716
1710
  */
1717
1711
  async executeBashInternal(command, ctx, cwd) {
1718
- getLogger().debug(`[bridge] executeBashInternal userId=${ctx.userId} chatId=${ctx.chatId} cwd=${cwd} command="${command.slice(0, 50)}..."`);
1712
+ getLogger().debug(`[lark-remote] executeBashInternal userId=${ctx.userId} chatId=${ctx.chatId} cwd=${cwd} command="${command.slice(0, 50)}..."`);
1719
1713
  // Note: We don't check activeRuns here because bash bypasses the serial
1720
1714
  // queue and runs in parallel with same-workspace agent runs (§9.6). The
1721
1715
  // activeRuns check in forwardToClaude is for a different purpose (detecting
@@ -1752,13 +1746,13 @@ export class Bridge {
1752
1746
  let stderr = '';
1753
1747
  let exitCode = null;
1754
1748
  try {
1755
- getLogger().info(`[bridge] executeBashInternal about to start stream chatId=${ctx.chatId} messageId=${ctx.messageId}`);
1749
+ getLogger().info(`[lark-remote] executeBashInternal about to start stream chatId=${ctx.chatId} messageId=${ctx.messageId}`);
1756
1750
  try {
1757
1751
  await cardSession.start();
1758
- getLogger().info(`[bridge] executeBash stream started runId=${runId}`);
1752
+ getLogger().info(`[lark-remote] executeBash stream started runId=${runId}`);
1759
1753
  }
1760
1754
  catch (err) {
1761
- getLogger().warn('[bridge] bash card stream unavailable:', err);
1755
+ getLogger().warn('[lark-remote] bash card stream unavailable:', err);
1762
1756
  }
1763
1757
  for await (const event of bashRunner.run(command, { cwd })) {
1764
1758
  if (event.type === 'stdout') {
@@ -1780,7 +1774,7 @@ export class Bridge {
1780
1774
  await cardSession.finish(exitCode === 0 ? 'done' : 'error', { exitCode, output, stderr });
1781
1775
  }
1782
1776
  catch (err) {
1783
- getLogger().error(`[bridge] bash run error runId=${runId}:`, err);
1777
+ getLogger().error(`[lark-remote] bash run error runId=${runId}:`, err);
1784
1778
  // If for-await threw mid-stream the bash child may still be running.
1785
1779
  // Stop it before finalizing the card — otherwise the finally block
1786
1780
  // removes the activeBashRuns entry and /stop can no longer reach it,
@@ -1789,7 +1783,7 @@ export class Bridge {
1789
1783
  await bashRunner.stop();
1790
1784
  }
1791
1785
  catch (stopErr) {
1792
- getLogger().warn(`[bridge] bash stop failed:`, stopErr);
1786
+ getLogger().warn(`[lark-remote] bash stop failed:`, stopErr);
1793
1787
  }
1794
1788
  await cardSession.finish('error', {
1795
1789
  exitCode: 1,
@@ -1811,7 +1805,7 @@ export class Bridge {
1811
1805
  }
1812
1806
  if (this.activeBashRuns.has(runId)) {
1813
1807
  this.activeBashRuns.delete(runId);
1814
- getLogger().info(`[bridge] executeBash done runId=${runId} exitCode=${exitCode}`);
1808
+ getLogger().info(`[lark-remote] executeBash done runId=${runId} exitCode=${exitCode}`);
1815
1809
  }
1816
1810
  // Add a Done emoji reaction to the user's original message to signal completion
1817
1811
  void this.connector.addReaction(ctx.messageId, 'Done');
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { spawn, spawnSync } from 'node:child_process';
5
5
  import { fileURLToPath, pathToFileURL } from 'node:url';
6
- import { printHelp, printVersion } from './config/dir.js';
6
+ import { printAdvanceHelp, printHelp, printVersion } from './config/dir.js';
7
7
  const entryUrl = new URL('./index.js', import.meta.url);
8
8
  const entry = fileURLToPath(entryUrl);
9
9
  /**
@@ -32,6 +32,10 @@ export function handlePreflight(args, nodeVersion = process.versions.node) {
32
32
  printHelp();
33
33
  return 0;
34
34
  }
35
+ if (arg === '--advance-help') {
36
+ printAdvanceHelp();
37
+ return 0;
38
+ }
35
39
  }
36
40
  const major = parseInt(String(nodeVersion).split('.')[0], 10);
37
41
  if (!Number.isNaN(major) && major > 0 && major < MIN_NODE_MAJOR) {
package/dist/clone.d.ts CHANGED
@@ -2,7 +2,7 @@ import { type QRCodeInfo, type RegisterAppResult } from '@larksuite/channel';
2
2
  /**
3
3
  * 复制分身(/clone)状态机。
4
4
  *
5
- * 在当前 bridge 进程内引导用户扫码创建一个新飞书应用,并生成一份"分身"
5
+ * 在当前 lark-remote 进程内引导用户扫码创建一个新飞书应用,并生成一份"分身"
6
6
  * 配置目录:除 feishu.appId/appSecret 来自新应用外,其余配置原样复制;
7
7
  * 创建完成后用新应用拿到的用户 openId 直接写好绑定(owner 无需再输入
8
8
  * 任意内容认领),并 detached 拉起指向新配置目录的新实例——启动后新
package/dist/common/fs.js CHANGED
@@ -22,7 +22,7 @@ export function silentlyUnlink(path) {
22
22
  * win32 句柄占用重试:rename/unlink 目标被
23
23
  * 其他句柄持有时报 EPERM/EBUSY(共享冲突),短暂退避后重试通常即可成功。
24
24
  * 仅 win32 启用重试:posix 上这两个错误码不是「占用瞬态」语义(如 NFS 权限
25
- * 问题),立即失败——避免忙等阻塞单线程 bridge 的事件循环。
25
+ * 问题),立即失败——避免忙等阻塞单线程 lark-remote 的事件循环。
26
26
  */
27
27
  const BUSY_CODES = new Set(['EPERM', 'EBUSY']);
28
28
  /**