lark-remote 0.3.0 → 0.4.0
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 +17 -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.js +7 -5
- package/dist/bridge/index.js +75 -75
- package/dist/clone.d.ts +1 -1
- package/dist/common/fs.js +1 -1
- package/dist/config/index.js +1 -1
- package/dist/connector/index.d.ts +6 -0
- package/dist/connector/index.js +76 -17
- package/dist/error-classification.d.ts +7 -0
- package/dist/error-classification.js +37 -1
- package/dist/index.js +12 -2
- 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/index.js +16 -6
- 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/kimi/acp/translator.js +60 -4
- 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
|
@@ -209,7 +209,7 @@ export class Bridge {
|
|
|
209
209
|
if (!settings)
|
|
210
210
|
continue;
|
|
211
211
|
void runner.updateApprovalMode(settings).catch((err) => {
|
|
212
|
-
getLogger().warn(`[
|
|
212
|
+
getLogger().warn(`[lark-remote] sync approval mode failed cwd=${cwd}: ${err.message}`);
|
|
213
213
|
});
|
|
214
214
|
}
|
|
215
215
|
}
|
|
@@ -521,7 +521,7 @@ export class Bridge {
|
|
|
521
521
|
if (active.userId === input.userId && active.chatId === input.chatId) {
|
|
522
522
|
if (input.runId && active.runId !== input.runId)
|
|
523
523
|
continue;
|
|
524
|
-
getLogger().info(`[
|
|
524
|
+
getLogger().info(`[lark-remote] interrupt hit runId=${active.runId} cwd=${cwd} userId=${input.userId}`);
|
|
525
525
|
// Stop the EXACT runner instance that is running (carried by activeRun),
|
|
526
526
|
// not whatever getRunner(cwd) would return now. clearRunners() may have
|
|
527
527
|
// emptied the cache mid-run, in which case getRunner creates a fresh empty
|
|
@@ -564,7 +564,7 @@ export class Bridge {
|
|
|
564
564
|
if (stoppedSlot !== undefined) {
|
|
565
565
|
this.queueManager.resetExecutingCount(cwd, stoppedSlot);
|
|
566
566
|
}
|
|
567
|
-
getLogger().info(`[
|
|
567
|
+
getLogger().info(`[lark-remote] interrupt stop done runId=${active.runId}`);
|
|
568
568
|
return true;
|
|
569
569
|
}
|
|
570
570
|
}
|
|
@@ -576,16 +576,16 @@ export class Bridge {
|
|
|
576
576
|
if (b.userId === input.userId && b.chatId === input.chatId) {
|
|
577
577
|
if (input.runId && runId !== input.runId)
|
|
578
578
|
continue;
|
|
579
|
-
getLogger().info(`[
|
|
579
|
+
getLogger().info(`[lark-remote] interrupt hit bash runId=${runId} cwd=${b.cwd} userId=${input.userId}`);
|
|
580
580
|
await b.bashRunner.stop({ immediate: true });
|
|
581
581
|
// Update card to show interrupted state
|
|
582
582
|
await b.cardSession.finish('interrupted', { exitCode: -1 });
|
|
583
583
|
this.activeBashRuns.delete(runId);
|
|
584
|
-
getLogger().info(`[
|
|
584
|
+
getLogger().info(`[lark-remote] interrupt bash stop done runId=${runId}`);
|
|
585
585
|
return true;
|
|
586
586
|
}
|
|
587
587
|
}
|
|
588
|
-
getLogger().debug(`[
|
|
588
|
+
getLogger().debug(`[lark-remote] interrupt miss userId=${input.userId}`);
|
|
589
589
|
return false;
|
|
590
590
|
}
|
|
591
591
|
/** Send a result message to the user (§7). Returns true on success, false on failure. */
|
|
@@ -595,26 +595,26 @@ export class Bridge {
|
|
|
595
595
|
// 静态卡片体积保护:确保卡片不超过飞书大小限制
|
|
596
596
|
const { card: safeCard, wasTruncated, reason, bytesBefore, bytesAfter, } = enforceCardBudget(result.card);
|
|
597
597
|
if (wasTruncated) {
|
|
598
|
-
getLogger().info(`[
|
|
598
|
+
getLogger().info(`[lark-remote] card size exceeded limit, applied budget enforcement: reason=${reason} bytesBefore=${bytesBefore} bytesAfter=${bytesAfter}`);
|
|
599
599
|
}
|
|
600
|
-
getLogger().debug(`[
|
|
600
|
+
getLogger().debug(`[lark-remote] sendResult kind=card chatId=${ctx.chatId}`);
|
|
601
601
|
await this.connector.sendWithRetry(ctx.chatId, { card: safeCard }, { replyTo: ctx.messageId });
|
|
602
602
|
return true;
|
|
603
603
|
}
|
|
604
604
|
else if (result.markdown) {
|
|
605
|
-
getLogger().debug(`[
|
|
605
|
+
getLogger().debug(`[lark-remote] sendResult kind=markdown chatId=${ctx.chatId}`);
|
|
606
606
|
await this.connector.sendWithRetry(ctx.chatId, { markdown: result.markdown }, { replyTo: ctx.messageId });
|
|
607
607
|
return true;
|
|
608
608
|
}
|
|
609
609
|
else if (result.text) {
|
|
610
|
-
getLogger().debug(`[
|
|
610
|
+
getLogger().debug(`[lark-remote] sendResult kind=text chatId=${ctx.chatId}`);
|
|
611
611
|
await this.connector.sendWithRetry(ctx.chatId, { text: result.text }, { replyTo: ctx.messageId });
|
|
612
612
|
return true;
|
|
613
613
|
}
|
|
614
614
|
return false;
|
|
615
615
|
}
|
|
616
616
|
catch (err) {
|
|
617
|
-
getLogger().error('[
|
|
617
|
+
getLogger().error('[lark-remote] failed to send result:', err);
|
|
618
618
|
// 不静默:向用户发送一条纯文本消息,告知具体错误(如飞书 11310 element exceeds
|
|
619
619
|
// the limit)。如果用户看到空白无反馈,无法判断是命令无效还是卡片发送失败。
|
|
620
620
|
try {
|
|
@@ -622,7 +622,7 @@ export class Bridge {
|
|
|
622
622
|
await this.connector.sendWithRetry(ctx.chatId, { text: `⚠️ 卡片发送失败:${errMsg}` }, { replyTo: ctx.messageId });
|
|
623
623
|
}
|
|
624
624
|
catch (fallbackErr) {
|
|
625
|
-
getLogger().error('[
|
|
625
|
+
getLogger().error('[lark-remote] fallback text send also failed:', fallbackErr);
|
|
626
626
|
}
|
|
627
627
|
return false;
|
|
628
628
|
}
|
|
@@ -642,18 +642,18 @@ export class Bridge {
|
|
|
642
642
|
*/
|
|
643
643
|
async updateCardInPlace(card, ctx) {
|
|
644
644
|
if (!ctx.messageId) {
|
|
645
|
-
getLogger().warn('[
|
|
645
|
+
getLogger().warn('[lark-remote] updateCardInPlace missing messageId, falling back to sendResult');
|
|
646
646
|
await this.sendResult({ card }, ctx);
|
|
647
647
|
return false;
|
|
648
648
|
}
|
|
649
649
|
try {
|
|
650
|
-
getLogger().debug(`[
|
|
650
|
+
getLogger().debug(`[lark-remote] updateCardInPlace messageId=${ctx.messageId}`);
|
|
651
651
|
await this.connector.updateCard(ctx.messageId, card);
|
|
652
652
|
return true;
|
|
653
653
|
}
|
|
654
654
|
catch (err) {
|
|
655
655
|
const errMsg = err.message ?? String(err);
|
|
656
|
-
getLogger().warn(`[
|
|
656
|
+
getLogger().warn(`[lark-remote] updateCardInPlace failed, falling back to sendResult: ${errMsg}`);
|
|
657
657
|
// 先尝试 fallback 发卡片;若卡片本身有问题(如 11310),
|
|
658
658
|
// sendResult 内部 catch 会发纯文本通知用户。
|
|
659
659
|
await this.sendResult({ card }, ctx);
|
|
@@ -663,11 +663,11 @@ export class Bridge {
|
|
|
663
663
|
/** Send a file to the user via Feishu. */
|
|
664
664
|
async sendFile(filePath, ctx) {
|
|
665
665
|
try {
|
|
666
|
-
getLogger().debug(`[
|
|
666
|
+
getLogger().debug(`[lark-remote] sendFile path=${filePath} chatId=${ctx.chatId}`);
|
|
667
667
|
await this.connector.sendFile(ctx.chatId, filePath);
|
|
668
668
|
}
|
|
669
669
|
catch (err) {
|
|
670
|
-
getLogger().error('[
|
|
670
|
+
getLogger().error('[lark-remote] failed to send file:', err);
|
|
671
671
|
await this.sendResult({ text: `发送文件失败: ${err.message}` }, ctx);
|
|
672
672
|
}
|
|
673
673
|
}
|
|
@@ -697,7 +697,7 @@ export class Bridge {
|
|
|
697
697
|
// NOTE: fallback uses insertion order, not sort preference — by design
|
|
698
698
|
// (cwd fallback stays insertion-order for now)
|
|
699
699
|
cwd = workspaces[0].path;
|
|
700
|
-
getLogger().info(`[
|
|
700
|
+
getLogger().info(`[lark-remote] resolveCwd fallback cwd=${cwd} (first saved workspace)`);
|
|
701
701
|
}
|
|
702
702
|
}
|
|
703
703
|
return cwd;
|
|
@@ -724,11 +724,11 @@ export class Bridge {
|
|
|
724
724
|
return;
|
|
725
725
|
}
|
|
726
726
|
const entry = this.sessionStore.get(ctx.userId);
|
|
727
|
-
getLogger().debug(`[
|
|
727
|
+
getLogger().debug(`[lark-remote] forward entry userId=${ctx.userId} chatId=${ctx.chatId} cwd=${cwd} ` +
|
|
728
728
|
`agent=${agentKind} sessionId=${opts?.binding?.sessionId ?? entry?.sessions?.get(agentKind) ?? '(none)'} message=${message.slice(0, 100)}`);
|
|
729
729
|
// Check if current workspace already has a run in progress
|
|
730
730
|
if (this.activeRuns.has(cwd)) {
|
|
731
|
-
getLogger().warn(`[
|
|
731
|
+
getLogger().warn(`[lark-remote] workspace busy, dropping message userId=${ctx.userId} cwd=${cwd}`);
|
|
732
732
|
await this.sendResult({ text: '此 workspace 正在处理中,请 /stop 后重试' }, ctx);
|
|
733
733
|
return;
|
|
734
734
|
}
|
|
@@ -759,7 +759,7 @@ export class Bridge {
|
|
|
759
759
|
*/
|
|
760
760
|
createRunSession(ctx, cwd, runner, agentKind = this.config.defaultAgent) {
|
|
761
761
|
const runId = randomUUID();
|
|
762
|
-
getLogger().debug(`[
|
|
762
|
+
getLogger().debug(`[lark-remote] creating runCard runId=${runId}`);
|
|
763
763
|
// D5: agentKind 来自入队绑定(forwardToClaude 解析),不再读 live config。
|
|
764
764
|
// 缓存键 (cwd, agentKind) 和 eviction 都用这个值(review P2-3)。
|
|
765
765
|
const cardSession = new RunCardSession({
|
|
@@ -785,7 +785,7 @@ export class Bridge {
|
|
|
785
785
|
agentKind,
|
|
786
786
|
};
|
|
787
787
|
this.activeRuns.set(cwd, activeRun);
|
|
788
|
-
getLogger().info(`[
|
|
788
|
+
getLogger().info(`[lark-remote] activeRuns.set cwd=${cwd} runId=${runId}`);
|
|
789
789
|
return { runId, cardSession, activeRun };
|
|
790
790
|
}
|
|
791
791
|
/**
|
|
@@ -860,18 +860,18 @@ export class Bridge {
|
|
|
860
860
|
});
|
|
861
861
|
try {
|
|
862
862
|
try {
|
|
863
|
-
getLogger().debug(`[
|
|
863
|
+
getLogger().debug(`[lark-remote] cardSession.start() begin runId=${runId}`);
|
|
864
864
|
await cardSession.start();
|
|
865
|
-
getLogger().info(`[
|
|
865
|
+
getLogger().info(`[lark-remote] cardSession.start() ok runId=${runId}`);
|
|
866
866
|
}
|
|
867
867
|
catch (err) {
|
|
868
|
-
getLogger().warn('[
|
|
868
|
+
getLogger().warn('[lark-remote] card stream unavailable, will use static fallback:', err);
|
|
869
869
|
// Card stream failed - continue with static fallback below, but ensure
|
|
870
870
|
// we don't silently fail if the fallback also fails (covered in catch below).
|
|
871
871
|
}
|
|
872
872
|
if (cardSession.currentState.terminal !== 'running') {
|
|
873
873
|
// Card session did not enter running state - still try to send static fallback
|
|
874
|
-
getLogger().info(`[
|
|
874
|
+
getLogger().info(`[lark-remote] cardSession not running, using static fallback`);
|
|
875
875
|
}
|
|
876
876
|
// P2-1: 单 interval 看门狗。事件到来时只刷新 lastEventTs(无 timer 操作);
|
|
877
877
|
// interval 周期检查 `now - lastEventTs > idleTimeoutMs`,超时才触发 stop。
|
|
@@ -891,7 +891,7 @@ export class Bridge {
|
|
|
891
891
|
if (idleInterval)
|
|
892
892
|
clearInterval(idleInterval);
|
|
893
893
|
idleInterval = null;
|
|
894
|
-
getLogger().warn(`[
|
|
894
|
+
getLogger().warn(`[lark-remote] claude idle timeout, stopping process runId=${runId}`);
|
|
895
895
|
void Promise.allSettled([
|
|
896
896
|
cardSession.finish('idle_timeout', {
|
|
897
897
|
idleTimeoutMinutes: Math.max(1, Math.round(this.idleTimeoutMs / 60_000)),
|
|
@@ -912,7 +912,7 @@ export class Bridge {
|
|
|
912
912
|
}
|
|
913
913
|
resetIdle();
|
|
914
914
|
let contextLength;
|
|
915
|
-
getLogger().info(`[
|
|
915
|
+
getLogger().info(`[lark-remote] runner.run() begin runId=${runId} message=${message.slice(0, 100)}`);
|
|
916
916
|
// Extract agent-specific options from config
|
|
917
917
|
const agentOpts = this.getAgentRunOptions(agentKind);
|
|
918
918
|
const runOpts = { cwd, sessionId, ...agentOpts };
|
|
@@ -940,7 +940,7 @@ export class Bridge {
|
|
|
940
940
|
// task-notification 注入触发 Claude 重发 init,已终止的 run 被复活)。
|
|
941
941
|
const pointerMoved = this.sessionStore.getSessionEpoch(ctx.userId, agentKind) !== sessionEpochAtStart;
|
|
942
942
|
if (pointerMoved) {
|
|
943
|
-
getLogger().info(`[
|
|
943
|
+
getLogger().info(`[lark-remote] system.init write-back skipped: session pointer moved since run start runId=${runId} sessionId=${event.session_id}`);
|
|
944
944
|
}
|
|
945
945
|
else {
|
|
946
946
|
// L5: Use session's real directory from event.cwd (not runner's cwd parameter)
|
|
@@ -952,7 +952,7 @@ export class Bridge {
|
|
|
952
952
|
if (hasCwd && event.cwd && event.cwd !== cwd) {
|
|
953
953
|
// Session has its own cwd (e.g. EnterWorktree relocate). Keep the
|
|
954
954
|
// workspace cwd unchanged; record the session's actual cwd in sessionCwds.
|
|
955
|
-
getLogger().info(`[
|
|
955
|
+
getLogger().info(`[lark-remote] system.init: workspace cwd=${cwd}, session cwd=${event.cwd}`);
|
|
956
956
|
this.sessionStore.setSessionIdAndSessionCwd(ctx.userId, agentKind, event.session_id, realCwd);
|
|
957
957
|
}
|
|
958
958
|
else {
|
|
@@ -962,7 +962,7 @@ export class Bridge {
|
|
|
962
962
|
this.sessionStore.setSessionIdAndCwd(ctx.userId, agentKind, event.session_id, realCwd, bootstrapSessionCwd);
|
|
963
963
|
}
|
|
964
964
|
}
|
|
965
|
-
getLogger().info(`[
|
|
965
|
+
getLogger().info(`[lark-remote] system.init received runId=${runId} sessionId=${event.session_id} cwd=${event.cwd}`);
|
|
966
966
|
}
|
|
967
967
|
if (event.type === 'turn_started') {
|
|
968
968
|
// app-server runner 在 turn setup 成功后补发 synthetic system.init
|
|
@@ -972,10 +972,10 @@ export class Bridge {
|
|
|
972
972
|
const pointerMoved = this.sessionStore.getSessionEpoch(ctx.userId, agentKind) !== sessionEpochAtStart;
|
|
973
973
|
if (!pointerMoved) {
|
|
974
974
|
this.sessionStore.setSessionIdAndCwd(ctx.userId, agentKind, event.threadId, cwd);
|
|
975
|
-
getLogger().info(`[
|
|
975
|
+
getLogger().info(`[lark-remote] turn_started session write-back runId=${runId} sessionId=${event.threadId}`);
|
|
976
976
|
}
|
|
977
977
|
else {
|
|
978
|
-
getLogger().info(`[
|
|
978
|
+
getLogger().info(`[lark-remote] turn_started write-back skipped: session pointer moved since run start runId=${runId} sessionId=${event.threadId}`);
|
|
979
979
|
}
|
|
980
980
|
}
|
|
981
981
|
// Track context length from compaction events
|
|
@@ -999,7 +999,7 @@ export class Bridge {
|
|
|
999
999
|
// 卡片不会把半初始化的 run 当作已收尾。
|
|
1000
1000
|
const preInitResult = !sawInit;
|
|
1001
1001
|
if (preInitResult && usageAuthority !== 'live') {
|
|
1002
|
-
getLogger().info(`[
|
|
1002
|
+
getLogger().info(`[lark-remote] pre-init result ignored (resume replay) runId=${runId}`);
|
|
1003
1003
|
// Still reset idle timer — the CLI is alive and producing events.
|
|
1004
1004
|
resetIdle();
|
|
1005
1005
|
// Do NOT push to cardSession: the run-state reducer would also
|
|
@@ -1012,8 +1012,8 @@ export class Bridge {
|
|
|
1012
1012
|
}
|
|
1013
1013
|
resultNotSuccess = event.subtype !== 'success';
|
|
1014
1014
|
getLogger().info(preInitResult
|
|
1015
|
-
? `[
|
|
1016
|
-
: `[
|
|
1015
|
+
? `[lark-remote] pre-init result from live runner (app-server setup failure) runId=${runId}`
|
|
1016
|
+
: `[lark-remote] result event received runId=${runId}`);
|
|
1017
1017
|
// app-server 模式 setup 失败路径(thread/start 成功后 turn/start 失败)
|
|
1018
1018
|
// 不会有 turn_started 事件,store 不会写回新线程 id;runner 已在 result
|
|
1019
1019
|
// 里兜底上报(review P3-10)。此处仅当 runner 声明 live usage(app-server)
|
|
@@ -1023,7 +1023,7 @@ export class Bridge {
|
|
|
1023
1023
|
event.session_id &&
|
|
1024
1024
|
this.sessionStore.getSessionEpoch(ctx.userId, agentKind) === sessionEpochAtStart) {
|
|
1025
1025
|
this.sessionStore.setSessionIdAndCwd(ctx.userId, agentKind, event.session_id, cwd);
|
|
1026
|
-
getLogger().info(`[
|
|
1026
|
+
getLogger().info(`[lark-remote] result session write-back runId=${runId} sessionId=${event.session_id}`);
|
|
1027
1027
|
}
|
|
1028
1028
|
// result 后进入 finalizing 非终态(暂存 subtype/errorMsg),由 for-await
|
|
1029
1029
|
// 自然结束后在 finally 转 done/error。提取 usage 信息。
|
|
@@ -1065,13 +1065,13 @@ export class Bridge {
|
|
|
1065
1065
|
await this.connector.removeReactionByEmoji(ctx.messageId, 'Typing');
|
|
1066
1066
|
}
|
|
1067
1067
|
catch (err) {
|
|
1068
|
-
getLogger().warn(`[
|
|
1068
|
+
getLogger().warn(`[lark-remote] remove Typing reaction failed runId=${runId}: ${errorMessage(err)}`);
|
|
1069
1069
|
}
|
|
1070
1070
|
try {
|
|
1071
1071
|
await this.connector.addReaction(ctx.messageId, 'Typing');
|
|
1072
1072
|
}
|
|
1073
1073
|
catch (err) {
|
|
1074
|
-
getLogger().warn(`[
|
|
1074
|
+
getLogger().warn(`[lark-remote] re-add Typing reaction failed runId=${runId}: ${errorMessage(err)}`);
|
|
1075
1075
|
}
|
|
1076
1076
|
})();
|
|
1077
1077
|
let coordinator = this.approvalCoordinators.get(runId);
|
|
@@ -1095,7 +1095,7 @@ export class Bridge {
|
|
|
1095
1095
|
},
|
|
1096
1096
|
});
|
|
1097
1097
|
this.approvalCoordinators.set(runId, coordinator);
|
|
1098
|
-
getLogger().info(`[
|
|
1098
|
+
getLogger().info(`[lark-remote] approval coordinator created runId=${runId.slice(0, 8)} requestId=${event.requestId}`);
|
|
1099
1099
|
}
|
|
1100
1100
|
coordinator.onRequested(event);
|
|
1101
1101
|
}
|
|
@@ -1116,7 +1116,7 @@ export class Bridge {
|
|
|
1116
1116
|
resetIdle();
|
|
1117
1117
|
await cardSession.push(event);
|
|
1118
1118
|
}
|
|
1119
|
-
getLogger().info(`[
|
|
1119
|
+
getLogger().info(`[lark-remote] runner stream end runId=${runId} sawResult=${sawResult}`);
|
|
1120
1120
|
// §9.12 红线(§P1-2 方案 A 第 2 点):for-await 自然结束 = CLI 已退出,
|
|
1121
1121
|
// 看门狗使命结束,立即摘除 interval(不再等 finally),堵掉「stream 已结束、
|
|
1122
1122
|
// 收尾处理跨过死线被误标 idle_timeout」的同源小窗口(P2-4 随本修复一并关闭)。
|
|
@@ -1167,7 +1167,7 @@ export class Bridge {
|
|
|
1167
1167
|
finalCacheCreationTokens = finalUsage?.cacheCreationTokens ?? finalCacheCreationTokens;
|
|
1168
1168
|
finalTotalTokens = finalUsage?.totalTokens ?? finalTotalTokens;
|
|
1169
1169
|
}
|
|
1170
|
-
getLogger().info(`[
|
|
1170
|
+
getLogger().info(`[lark-remote] final usage runId=${runId} contextLength=${finalContextLength} contextLimit=${finalContextLimit ?? 'undefined'} compactCount=${finalCompactCount ?? 'undefined'} cacheRead=${finalCacheReadTokens ?? 'undefined'} cacheCreate=${finalCacheCreationTokens ?? 'undefined'}`);
|
|
1171
1171
|
// for-await 自然结束 = stream 关闭 = CLI 进程退出。
|
|
1172
1172
|
// 进程退出(for-await end)= 唯一终态触发源。
|
|
1173
1173
|
// 1) finalizing(result 已收到)-> 转 done/error + usage meta
|
|
@@ -1200,14 +1200,14 @@ export class Bridge {
|
|
|
1200
1200
|
// else: 已终态且未收到 result(如 spawn 失败后 interrupted)-> 无 usage 可补,跳过
|
|
1201
1201
|
}
|
|
1202
1202
|
catch (err) {
|
|
1203
|
-
getLogger().error(`[
|
|
1203
|
+
getLogger().error(`[lark-remote] claude run error runId=${runId}:`, err);
|
|
1204
1204
|
// P1-11 双保险:run() 的 finally 已负责杀子进程,但 catch 路径再补一次
|
|
1205
1205
|
// runner.stop()(与 bash 路径对齐),覆盖任何 finally 之外的残留窗口。
|
|
1206
1206
|
try {
|
|
1207
1207
|
await runner.stop();
|
|
1208
1208
|
}
|
|
1209
1209
|
catch (stopErr) {
|
|
1210
|
-
getLogger().warn(`[
|
|
1210
|
+
getLogger().warn(`[lark-remote] runner stop failed:`, stopErr);
|
|
1211
1211
|
}
|
|
1212
1212
|
// 终态守卫:running 或 finalizing(非终态)-> 转 error;
|
|
1213
1213
|
// 已终态(interrupted/idle_timeout/done/error)-> 保留首终态,仅补充 usage。
|
|
@@ -1221,7 +1221,7 @@ export class Bridge {
|
|
|
1221
1221
|
});
|
|
1222
1222
|
}
|
|
1223
1223
|
else {
|
|
1224
|
-
getLogger().info(`[
|
|
1224
|
+
getLogger().info(`[lark-remote] skip error finish: state already terminal (${catchTerminal}) runId=${runId}`);
|
|
1225
1225
|
if (sawResult) {
|
|
1226
1226
|
await cardSession.finish(catchTerminal, usageMeta());
|
|
1227
1227
|
}
|
|
@@ -1244,7 +1244,7 @@ export class Bridge {
|
|
|
1244
1244
|
async finalizeRun(cardSession, activeRun, ctx, cwd) {
|
|
1245
1245
|
const runId = activeRun.runId;
|
|
1246
1246
|
try {
|
|
1247
|
-
getLogger().debug(`[
|
|
1247
|
+
getLogger().debug(`[lark-remote] finally settle runId=${runId} state.terminal=${cardSession.currentState.terminal}`);
|
|
1248
1248
|
const settleResult = await cardSession.settle();
|
|
1249
1249
|
let runCardSent = false;
|
|
1250
1250
|
if (settleResult === 'unsent') {
|
|
@@ -1316,7 +1316,7 @@ export class Bridge {
|
|
|
1316
1316
|
});
|
|
1317
1317
|
}
|
|
1318
1318
|
}
|
|
1319
|
-
getLogger().info(`[
|
|
1319
|
+
getLogger().info(`[lark-remote] activeRuns.delete cwd=${cwd} runId=${runId}`);
|
|
1320
1320
|
}
|
|
1321
1321
|
}
|
|
1322
1322
|
}
|
|
@@ -1353,14 +1353,14 @@ export class Bridge {
|
|
|
1353
1353
|
// sessions produced >28KB cards that Feishu silently rejected).
|
|
1354
1354
|
const sent = await this.sendResult({ card }, ctx);
|
|
1355
1355
|
if (sent) {
|
|
1356
|
-
getLogger().debug(`[
|
|
1356
|
+
getLogger().debug(`[lark-remote] completion notification sent sessionId=${sessionId} chatId=${ctx.chatId}`);
|
|
1357
1357
|
}
|
|
1358
1358
|
else {
|
|
1359
|
-
getLogger().warn(`[
|
|
1359
|
+
getLogger().warn(`[lark-remote] failed to send completion notification card sessionId=${sessionId}`);
|
|
1360
1360
|
}
|
|
1361
1361
|
}
|
|
1362
1362
|
catch (err) {
|
|
1363
|
-
getLogger().warn(`[
|
|
1363
|
+
getLogger().warn(`[lark-remote] failed to send completion notification card:`, err);
|
|
1364
1364
|
}
|
|
1365
1365
|
}
|
|
1366
1366
|
// =============================================================================
|
|
@@ -1375,7 +1375,7 @@ export class Bridge {
|
|
|
1375
1375
|
if (!coordinator) {
|
|
1376
1376
|
// P2-7: run 已结束(coordinator 已释放)时点击审批按钮必须给用户明确反馈,
|
|
1377
1377
|
// 不能静默。router 会把该异常转成 error toast。
|
|
1378
|
-
getLogger().warn(`[
|
|
1378
|
+
getLogger().warn(`[lark-remote] no approval coordinator for runId=${runId.slice(0, 8)}`);
|
|
1379
1379
|
throw new Error('任务已结束,审批无法响应');
|
|
1380
1380
|
}
|
|
1381
1381
|
return fn(coordinator);
|
|
@@ -1386,7 +1386,7 @@ export class Bridge {
|
|
|
1386
1386
|
*/
|
|
1387
1387
|
async handleApprovalRespond(opts) {
|
|
1388
1388
|
const log = getLogger();
|
|
1389
|
-
log.info(`[
|
|
1389
|
+
log.info(`[lark-remote] handleApprovalRespond runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} decision=${opts.decision}`);
|
|
1390
1390
|
return this.withCoordinator(opts.runId, async (coordinator) => {
|
|
1391
1391
|
// Build ApprovalAction from the decision string
|
|
1392
1392
|
const action = decisionToApprovalAction(opts.decision);
|
|
@@ -1408,7 +1408,7 @@ export class Bridge {
|
|
|
1408
1408
|
*/
|
|
1409
1409
|
async handleApprovalToggle(opts) {
|
|
1410
1410
|
const log = getLogger();
|
|
1411
|
-
log.info(`[
|
|
1411
|
+
log.info(`[lark-remote] handleApprovalToggle runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} permId=${opts.permId}`);
|
|
1412
1412
|
return this.withCoordinator(opts.runId, async (coordinator) => {
|
|
1413
1413
|
// selected 由卡片根据当前渲染状态传回(取反),不再硬编码只授不撤。
|
|
1414
1414
|
const toggleAction = {
|
|
@@ -1424,7 +1424,7 @@ export class Bridge {
|
|
|
1424
1424
|
*/
|
|
1425
1425
|
async handleApprovalAnswer(opts) {
|
|
1426
1426
|
const log = getLogger();
|
|
1427
|
-
log.info(`[
|
|
1427
|
+
log.info(`[lark-remote] handleApprovalAnswer runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} question=${opts.questionIndex}`);
|
|
1428
1428
|
return this.withCoordinator(opts.runId, (coordinator) => coordinator.toggleAnswer({ questionIndex: opts.questionIndex, option: opts.option }, { requestId: opts.requestId, nonce: opts.nonce }));
|
|
1429
1429
|
}
|
|
1430
1430
|
/**
|
|
@@ -1433,7 +1433,7 @@ export class Bridge {
|
|
|
1433
1433
|
*/
|
|
1434
1434
|
async handleApprovalAnswerSubmit(opts) {
|
|
1435
1435
|
const log = getLogger();
|
|
1436
|
-
log.info(`[
|
|
1436
|
+
log.info(`[lark-remote] handleApprovalAnswerSubmit runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} question=${opts.questionIndex}`);
|
|
1437
1437
|
return this.withCoordinator(opts.runId, (coordinator) => coordinator.submitAnswers({ questionIndex: opts.questionIndex }, { requestId: opts.requestId, nonce: opts.nonce }));
|
|
1438
1438
|
}
|
|
1439
1439
|
/**
|
|
@@ -1442,7 +1442,7 @@ export class Bridge {
|
|
|
1442
1442
|
*/
|
|
1443
1443
|
async handleApprovalAnswerCustom(opts) {
|
|
1444
1444
|
const log = getLogger();
|
|
1445
|
-
log.info(`[
|
|
1445
|
+
log.info(`[lark-remote] handleApprovalAnswerCustom runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} question=${opts.questionIndex}`);
|
|
1446
1446
|
return this.withCoordinator(opts.runId, (coordinator) => coordinator.answerCustom({ questionIndex: opts.questionIndex, text: opts.text }, { requestId: opts.requestId, nonce: opts.nonce }));
|
|
1447
1447
|
}
|
|
1448
1448
|
/**
|
|
@@ -1452,7 +1452,7 @@ export class Bridge {
|
|
|
1452
1452
|
*/
|
|
1453
1453
|
async handleApprovalAnswerNote(opts) {
|
|
1454
1454
|
const log = getLogger();
|
|
1455
|
-
log.info(`[
|
|
1455
|
+
log.info(`[lark-remote] handleApprovalAnswerNote runId=${opts.runId.slice(0, 8)}... requestId=${opts.requestId} question=${opts.questionIndex}`);
|
|
1456
1456
|
return this.withCoordinator(opts.runId, (coordinator) => coordinator.answerNote({ questionIndex: opts.questionIndex, text: opts.text }, { requestId: opts.requestId, nonce: opts.nonce }));
|
|
1457
1457
|
}
|
|
1458
1458
|
/**
|
|
@@ -1463,7 +1463,7 @@ export class Bridge {
|
|
|
1463
1463
|
*/
|
|
1464
1464
|
async handleCodexCompact(value, ctx) {
|
|
1465
1465
|
const log = getLogger();
|
|
1466
|
-
log.info(`[
|
|
1466
|
+
log.info(`[lark-remote] handleCodexCompact userId=${ctx.userId} runId=${value.runId?.slice(0, 8)}...`);
|
|
1467
1467
|
const { runId } = value;
|
|
1468
1468
|
if (!runId) {
|
|
1469
1469
|
await this.sendResult({ text: '⚠️ 无效的 Compact 请求,缺少 runId' }, ctx);
|
|
@@ -1494,7 +1494,7 @@ export class Bridge {
|
|
|
1494
1494
|
await this.sendResult({ text: '⚠️ 当前运行模式不支持 Compact' }, ctx);
|
|
1495
1495
|
return;
|
|
1496
1496
|
}
|
|
1497
|
-
log.info(`[
|
|
1497
|
+
log.info(`[lark-remote] handleCodexCompact executing runCompact for runId=${runId} cwd=${cwd}`);
|
|
1498
1498
|
await this.streamCodexCompact({
|
|
1499
1499
|
sessionId: last.sessionId,
|
|
1500
1500
|
cwd,
|
|
@@ -1529,7 +1529,7 @@ export class Bridge {
|
|
|
1529
1529
|
}
|
|
1530
1530
|
}
|
|
1531
1531
|
catch (err) {
|
|
1532
|
-
getLogger().warn(`[
|
|
1532
|
+
getLogger().warn(`[lark-remote] readCompactionState failed sessionId=${sessionId}: ${errorMessage(err)}`);
|
|
1533
1533
|
}
|
|
1534
1534
|
return false;
|
|
1535
1535
|
}
|
|
@@ -1546,7 +1546,7 @@ export class Bridge {
|
|
|
1546
1546
|
async streamCodexCompact(opts) {
|
|
1547
1547
|
const log = getLogger();
|
|
1548
1548
|
const { sessionId, cwd, agentKind, runner, ctx } = opts;
|
|
1549
|
-
log.info(`[
|
|
1549
|
+
log.info(`[lark-remote] streamCodexCompact sessionId=${sessionId} cwd=${cwd}`);
|
|
1550
1550
|
// 防御性能力检查:调用方已校验(runId / resume 两条路径),此处双保险并
|
|
1551
1551
|
// 让 TS 收窄 runner 类型('runCompact' in runner 之后的 cast 才合法)。
|
|
1552
1552
|
if (!('runCompact' in runner) ||
|
|
@@ -1618,10 +1618,10 @@ export class Bridge {
|
|
|
1618
1618
|
cumulativeCacheReadTokens: finalUsage?.cumulativeCacheReadTokens,
|
|
1619
1619
|
cumulativeCacheCreationTokens: finalUsage?.cumulativeCacheCreationTokens,
|
|
1620
1620
|
});
|
|
1621
|
-
log.info(`[
|
|
1621
|
+
log.info(`[lark-remote] streamCodexCompact finished sessionId=${sessionId} sawResult=${sawResult} subtype=${resultSubtype ?? 'none'}`);
|
|
1622
1622
|
}
|
|
1623
1623
|
catch (err) {
|
|
1624
|
-
log.error(`[
|
|
1624
|
+
log.error(`[lark-remote] streamCodexCompact failed: ${errorMessage(err)}`);
|
|
1625
1625
|
await cardSession.finish('error', { errorMsg: errorMessage(err) });
|
|
1626
1626
|
}
|
|
1627
1627
|
}
|
|
@@ -1636,7 +1636,7 @@ export class Bridge {
|
|
|
1636
1636
|
*/
|
|
1637
1637
|
async handleResumeCompact(value, ctx) {
|
|
1638
1638
|
const log = getLogger();
|
|
1639
|
-
log.info(`[
|
|
1639
|
+
log.info(`[lark-remote] handleResumeCompact userId=${ctx.userId} sessionId=${value.sessionId?.slice(0, 16)} agent=${value.agent ?? 'default'}`);
|
|
1640
1640
|
const { sessionId } = value;
|
|
1641
1641
|
if (!sessionId) {
|
|
1642
1642
|
await this.sendResult({ text: '⚠️ 无效的 Compact 请求,缺少 sessionId' }, ctx);
|
|
@@ -1682,7 +1682,7 @@ export class Bridge {
|
|
|
1682
1682
|
await this.sendResult({ text: '⚠️ 当前运行模式不支持 Compact' }, ctx);
|
|
1683
1683
|
return;
|
|
1684
1684
|
}
|
|
1685
|
-
log.info(`[
|
|
1685
|
+
log.info(`[lark-remote] handleResumeCompact executing runCompact sessionId=${sessionId} cwd=${cwd}`);
|
|
1686
1686
|
await this.streamCodexCompact({ sessionId, cwd, agentKind, runner, ctx });
|
|
1687
1687
|
}
|
|
1688
1688
|
/**
|
|
@@ -1693,13 +1693,13 @@ export class Bridge {
|
|
|
1693
1693
|
*/
|
|
1694
1694
|
async executeBash(command, ctx) {
|
|
1695
1695
|
const cwd = this.resolveCwd(ctx.userId);
|
|
1696
|
-
getLogger().info(`[
|
|
1696
|
+
getLogger().info(`[lark-remote] executeBash start userId=${ctx.userId} sessionCwd=${this.sessionStore.get(ctx.userId)?.cwd} command="${command.slice(0, 30)}..."`);
|
|
1697
1697
|
if (!cwd) {
|
|
1698
|
-
getLogger().info(`[
|
|
1698
|
+
getLogger().info(`[lark-remote] executeBash no cwd, sending error`);
|
|
1699
1699
|
await this.sendResult({ text: '请先使用 /cd <path> 设置工作目录,或保存一个 workspace (/ws save <name>)' }, ctx);
|
|
1700
1700
|
return;
|
|
1701
1701
|
}
|
|
1702
|
-
getLogger().info(`[
|
|
1702
|
+
getLogger().info(`[lark-remote] executeBash proceeding cwd=${cwd}`);
|
|
1703
1703
|
// `!` commands bypass the serial queue entirely (design.md §9.6):
|
|
1704
1704
|
// index.ts dispatches `!` to router.handle → executeBash directly, NOT via
|
|
1705
1705
|
// bridge.enqueue. bash runs in parallel with same-workspace claude runs.
|
|
@@ -1715,7 +1715,7 @@ export class Bridge {
|
|
|
1715
1715
|
* bypassing the serial queue — see executeBash).
|
|
1716
1716
|
*/
|
|
1717
1717
|
async executeBashInternal(command, ctx, cwd) {
|
|
1718
|
-
getLogger().debug(`[
|
|
1718
|
+
getLogger().debug(`[lark-remote] executeBashInternal userId=${ctx.userId} chatId=${ctx.chatId} cwd=${cwd} command="${command.slice(0, 50)}..."`);
|
|
1719
1719
|
// Note: We don't check activeRuns here because bash bypasses the serial
|
|
1720
1720
|
// queue and runs in parallel with same-workspace agent runs (§9.6). The
|
|
1721
1721
|
// activeRuns check in forwardToClaude is for a different purpose (detecting
|
|
@@ -1752,13 +1752,13 @@ export class Bridge {
|
|
|
1752
1752
|
let stderr = '';
|
|
1753
1753
|
let exitCode = null;
|
|
1754
1754
|
try {
|
|
1755
|
-
getLogger().info(`[
|
|
1755
|
+
getLogger().info(`[lark-remote] executeBashInternal about to start stream chatId=${ctx.chatId} messageId=${ctx.messageId}`);
|
|
1756
1756
|
try {
|
|
1757
1757
|
await cardSession.start();
|
|
1758
|
-
getLogger().info(`[
|
|
1758
|
+
getLogger().info(`[lark-remote] executeBash stream started runId=${runId}`);
|
|
1759
1759
|
}
|
|
1760
1760
|
catch (err) {
|
|
1761
|
-
getLogger().warn('[
|
|
1761
|
+
getLogger().warn('[lark-remote] bash card stream unavailable:', err);
|
|
1762
1762
|
}
|
|
1763
1763
|
for await (const event of bashRunner.run(command, { cwd })) {
|
|
1764
1764
|
if (event.type === 'stdout') {
|
|
@@ -1780,7 +1780,7 @@ export class Bridge {
|
|
|
1780
1780
|
await cardSession.finish(exitCode === 0 ? 'done' : 'error', { exitCode, output, stderr });
|
|
1781
1781
|
}
|
|
1782
1782
|
catch (err) {
|
|
1783
|
-
getLogger().error(`[
|
|
1783
|
+
getLogger().error(`[lark-remote] bash run error runId=${runId}:`, err);
|
|
1784
1784
|
// If for-await threw mid-stream the bash child may still be running.
|
|
1785
1785
|
// Stop it before finalizing the card — otherwise the finally block
|
|
1786
1786
|
// removes the activeBashRuns entry and /stop can no longer reach it,
|
|
@@ -1789,7 +1789,7 @@ export class Bridge {
|
|
|
1789
1789
|
await bashRunner.stop();
|
|
1790
1790
|
}
|
|
1791
1791
|
catch (stopErr) {
|
|
1792
|
-
getLogger().warn(`[
|
|
1792
|
+
getLogger().warn(`[lark-remote] bash stop failed:`, stopErr);
|
|
1793
1793
|
}
|
|
1794
1794
|
await cardSession.finish('error', {
|
|
1795
1795
|
exitCode: 1,
|
|
@@ -1811,7 +1811,7 @@ export class Bridge {
|
|
|
1811
1811
|
}
|
|
1812
1812
|
if (this.activeBashRuns.has(runId)) {
|
|
1813
1813
|
this.activeBashRuns.delete(runId);
|
|
1814
|
-
getLogger().info(`[
|
|
1814
|
+
getLogger().info(`[lark-remote] executeBash done runId=${runId} exitCode=${exitCode}`);
|
|
1815
1815
|
}
|
|
1816
1816
|
// Add a Done emoji reaction to the user's original message to signal completion
|
|
1817
1817
|
void this.connector.addReaction(ctx.messageId, 'Done');
|
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
|
/**
|
package/dist/config/index.js
CHANGED
|
@@ -256,7 +256,7 @@ export const AppConfigSchema = z.object({
|
|
|
256
256
|
inboundMedia: InboundMediaConfigSchema.default(InboundMediaConfigSchema.parse({})),
|
|
257
257
|
/** Default agent for new runs. */
|
|
258
258
|
defaultAgent: z.enum(['claude', 'codex', 'opencode', 'pi', 'kimi', 'dsh']).default('claude'),
|
|
259
|
-
/** Check for updates on
|
|
259
|
+
/** Check for updates on lark-remote startup (default: false). */
|
|
260
260
|
checkUpdateOnStartup: z.boolean().default(false),
|
|
261
261
|
});
|
|
262
262
|
const TEMPLATE = `feishu:
|
|
@@ -82,6 +82,12 @@ type InboundMediaDetectedHandler = (msg: InboundMediaMessage) => void | Promise<
|
|
|
82
82
|
* means "no immediate response" (original behavior).
|
|
83
83
|
*/
|
|
84
84
|
type CardActionHandler = (action: CardActionEvent) => void | CardActionResponse | Promise<void | CardActionResponse>;
|
|
85
|
+
/**
|
|
86
|
+
* 卡片 patch 的重试上限。含义:1 次原始尝试 + 最多 PATCH_MAX_RETRIES 次重试。
|
|
87
|
+
*/
|
|
88
|
+
export declare const PATCH_MAX_RETRIES = 3;
|
|
89
|
+
/** 重试退避基数(ms);第 n 次重试前等待 n × 基数,给对端恢复的时间。 */
|
|
90
|
+
export declare const PATCH_RETRY_BASE_DELAY_MS = 150;
|
|
85
91
|
export declare class FeishuConnector {
|
|
86
92
|
/** 飞书 channel 实例(public:测试直接访问/注入 mock,替代 as unknown as)。 */
|
|
87
93
|
channel: LarkChannel;
|