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.
- package/CHANGELOG.md +35 -0
- package/README.en.md +6 -6
- package/README.md +8 -7
- package/dist/binder.d.ts +1 -1
- package/dist/binder.js +1 -1
- package/dist/bridge/final-usage.js +2 -2
- package/dist/bridge/inbound-media.d.ts +13 -20
- package/dist/bridge/inbound-media.js +107 -91
- package/dist/bridge/index.d.ts +6 -6
- package/dist/bridge/index.js +81 -87
- package/dist/cli.js +5 -1
- package/dist/clone.d.ts +1 -1
- package/dist/common/fs.js +1 -1
- package/dist/config/dir.d.ts +9 -0
- package/dist/config/dir.js +69 -0
- package/dist/config/index.d.ts +22 -3
- package/dist/config/index.js +26 -4
- package/dist/connector/index.d.ts +28 -1
- package/dist/connector/index.js +155 -31
- package/dist/error-classification.d.ts +7 -0
- package/dist/error-classification.js +37 -1
- package/dist/inbound/event.d.ts +41 -0
- package/dist/inbound/event.js +11 -0
- package/dist/inbound/placeholder.d.ts +26 -0
- package/dist/inbound/placeholder.js +149 -0
- package/dist/inbound/prompt.d.ts +20 -0
- package/dist/inbound/prompt.js +47 -0
- package/dist/inbound/receipt.d.ts +28 -0
- package/dist/inbound/receipt.js +62 -0
- package/dist/inbound/turn-assembler.d.ts +63 -0
- package/dist/inbound/turn-assembler.js +233 -0
- package/dist/inbound/turn.d.ts +48 -0
- package/dist/inbound/turn.js +10 -0
- package/dist/index.js +108 -73
- package/dist/platform/command.js +1 -1
- package/dist/platform/sleep-blocker.d.ts +17 -0
- package/dist/platform/sleep-blocker.js +73 -0
- package/dist/platform/terminator-win32.js +1 -1
- package/dist/restart.d.ts +4 -4
- package/dist/restart.js +5 -5
- package/dist/router/config/kimi.js +4 -3
- package/dist/router/config/opencode.d.ts +1 -1
- package/dist/router/config/opencode.js +11 -0
- package/dist/router/index.d.ts +17 -0
- package/dist/router/index.js +53 -15
- package/dist/runner/common/acp/base-acp-runner.js +7 -2
- package/dist/runner/common/acp/base-translator.d.ts +19 -0
- package/dist/runner/common/acp/base-translator.js +82 -0
- package/dist/runner/common/acp/protocol-types.d.ts +16 -2
- package/dist/runner/common/acp/protocol-types.js +3 -0
- package/dist/runner/common/connection-based-runner.js +6 -2
- package/dist/runner/kimi/acp/runner.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.js +1 -1
- package/dist/runner/kimi/acp/translator.js +60 -4
- package/dist/runner/opencode/acp/runner.d.ts +1 -1
- package/dist/session/session-store.d.ts +1 -1
- package/dist/session/session-store.js +1 -1
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/index.js +1 -1
- package/package.json +1 -1
package/dist/bridge/index.js
CHANGED
|
@@ -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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
583
|
+
getLogger().info(`[lark-remote] interrupt bash stop done runId=${runId}`);
|
|
585
584
|
return true;
|
|
586
585
|
}
|
|
587
586
|
}
|
|
588
|
-
getLogger().debug(`[
|
|
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(`[
|
|
597
|
+
getLogger().info(`[lark-remote] card size exceeded limit, applied budget enforcement: reason=${reason} bytesBefore=${bytesBefore} bytesAfter=${bytesAfter}`);
|
|
599
598
|
}
|
|
600
|
-
getLogger().debug(`[
|
|
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(`[
|
|
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(`[
|
|
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('[
|
|
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('[
|
|
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('[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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('[
|
|
669
|
+
getLogger().error('[lark-remote] failed to send file:', err);
|
|
671
670
|
await this.sendResult({ text: `发送文件失败: ${err.message}` }, ctx);
|
|
672
671
|
}
|
|
673
672
|
}
|
|
674
|
-
/**
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
857
|
+
getLogger().debug(`[lark-remote] cardSession.start() begin runId=${runId}`);
|
|
864
858
|
await cardSession.start();
|
|
865
|
-
getLogger().info(`[
|
|
859
|
+
getLogger().info(`[lark-remote] cardSession.start() ok runId=${runId}`);
|
|
866
860
|
}
|
|
867
861
|
catch (err) {
|
|
868
|
-
getLogger().warn('[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
969
|
+
getLogger().info(`[lark-remote] turn_started session write-back runId=${runId} sessionId=${event.threadId}`);
|
|
976
970
|
}
|
|
977
971
|
else {
|
|
978
|
-
getLogger().info(`[
|
|
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(`[
|
|
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
|
-
? `[
|
|
1016
|
-
: `[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
1350
|
+
getLogger().debug(`[lark-remote] completion notification sent sessionId=${sessionId} chatId=${ctx.chatId}`);
|
|
1357
1351
|
}
|
|
1358
1352
|
else {
|
|
1359
|
-
getLogger().warn(`[
|
|
1353
|
+
getLogger().warn(`[lark-remote] failed to send completion notification card sessionId=${sessionId}`);
|
|
1360
1354
|
}
|
|
1361
1355
|
}
|
|
1362
1356
|
catch (err) {
|
|
1363
|
-
getLogger().warn(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
1615
|
+
log.info(`[lark-remote] streamCodexCompact finished sessionId=${sessionId} sawResult=${sawResult} subtype=${resultSubtype ?? 'none'}`);
|
|
1622
1616
|
}
|
|
1623
1617
|
catch (err) {
|
|
1624
|
-
log.error(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
1752
|
+
getLogger().info(`[lark-remote] executeBash stream started runId=${runId}`);
|
|
1759
1753
|
}
|
|
1760
1754
|
catch (err) {
|
|
1761
|
-
getLogger().warn('[
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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
|
-
* 在当前
|
|
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
|
-
* 问题),立即失败——避免忙等阻塞单线程
|
|
25
|
+
* 问题),立即失败——避免忙等阻塞单线程 lark-remote 的事件循环。
|
|
26
26
|
*/
|
|
27
27
|
const BUSY_CODES = new Set(['EPERM', 'EBUSY']);
|
|
28
28
|
/**
|