remote-claude 0.1.7 → 0.2.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.
|
@@ -11,10 +11,27 @@
|
|
|
11
11
|
|
|
12
12
|
import logging
|
|
13
13
|
import re as _re
|
|
14
|
+
import pathlib as _pl
|
|
15
|
+
import json as _json
|
|
14
16
|
from typing import Dict, Any, List, Optional
|
|
15
17
|
|
|
16
18
|
_cb_logger = logging.getLogger('CardBuilder')
|
|
17
19
|
|
|
20
|
+
# 版本号:从 package.json 读取,import 时只读一次
|
|
21
|
+
try:
|
|
22
|
+
_pkg = _pl.Path(__file__).parent.parent / "package.json"
|
|
23
|
+
_VERSION = "v" + _json.loads(_pkg.read_text())["version"]
|
|
24
|
+
except Exception:
|
|
25
|
+
_VERSION = ""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _build_header(title: str, template: str) -> dict:
|
|
29
|
+
"""构建卡片 header,自动附加版本号副标题"""
|
|
30
|
+
h: dict = {"title": {"tag": "plain_text", "content": title}, "template": template}
|
|
31
|
+
if _VERSION:
|
|
32
|
+
h["subtitle"] = {"tag": "plain_text", "content": _VERSION}
|
|
33
|
+
return h
|
|
34
|
+
|
|
18
35
|
# ANSI SGR 前景色码 → 飞书颜色
|
|
19
36
|
# 飞书支持: blue, wathet, turquoise, green, yellow, orange, red, carmine, violet, purple, indigo, grey
|
|
20
37
|
_SGR_FG_TO_LARK = {
|
|
@@ -261,6 +278,7 @@ def _build_menu_button_row(session_name: Optional[str] = None, disconnected: boo
|
|
|
261
278
|
"width": "auto",
|
|
262
279
|
"elements": [{
|
|
263
280
|
"tag": "button",
|
|
281
|
+
"name": "enter_submit",
|
|
264
282
|
"text": {"tag": "plain_text", "content": "Enter ↵"},
|
|
265
283
|
"type": "primary",
|
|
266
284
|
"action_type": "form_submit",
|
|
@@ -290,6 +308,7 @@ def _build_menu_button_row(session_name: Optional[str] = None, disconnected: boo
|
|
|
290
308
|
"width": "auto",
|
|
291
309
|
"elements": [{
|
|
292
310
|
"tag": "button",
|
|
311
|
+
"name": "enter_submit",
|
|
293
312
|
"text": {"tag": "plain_text", "content": "Enter ↵"},
|
|
294
313
|
"type": "primary",
|
|
295
314
|
"action_type": "form_submit",
|
|
@@ -312,6 +331,7 @@ def _build_menu_button_row(session_name: Optional[str] = None, disconnected: boo
|
|
|
312
331
|
("Ctrl+O", {"action": "send_key", "key": "ctrl_o"}),
|
|
313
332
|
("Shift+Tab", {"action": "send_key", "key": "shift_tab"}),
|
|
314
333
|
("ESC", {"action": "send_key", "key": "esc"}),
|
|
334
|
+
("(↹)×3", {"action": "send_key", "key": "shift_tab", "times": 3}),
|
|
315
335
|
]
|
|
316
336
|
|
|
317
337
|
def _make_key_column(label, value):
|
|
@@ -722,10 +742,7 @@ def build_stream_card(
|
|
|
722
742
|
return {
|
|
723
743
|
"schema": "2.0",
|
|
724
744
|
"config": {"wide_screen_mode": True, "enable_forward": True},
|
|
725
|
-
"header":
|
|
726
|
-
"title": {"tag": "plain_text", "content": title},
|
|
727
|
-
"template": template,
|
|
728
|
-
},
|
|
745
|
+
"header": _build_header(title, template),
|
|
729
746
|
"body": {"elements": elements},
|
|
730
747
|
}
|
|
731
748
|
|
|
@@ -851,10 +868,7 @@ def build_status_card(connected: bool, session_name: Optional[str] = None) -> Di
|
|
|
851
868
|
return {
|
|
852
869
|
"schema": "2.0",
|
|
853
870
|
"config": {"wide_screen_mode": True},
|
|
854
|
-
"header":
|
|
855
|
-
"title": {"tag": "plain_text", "content": title},
|
|
856
|
-
"template": template,
|
|
857
|
-
},
|
|
871
|
+
"header": _build_header(title, template),
|
|
858
872
|
"body": {"elements": [
|
|
859
873
|
{"tag": "markdown", "content": content},
|
|
860
874
|
{"tag": "hr"},
|
|
@@ -871,7 +885,7 @@ def _dir_session_name(path: str) -> str:
|
|
|
871
885
|
return name or "session"
|
|
872
886
|
|
|
873
887
|
|
|
874
|
-
def build_dir_card(target, entries: List[Dict], sessions: List[Dict], tree: bool = False, session_groups: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
|
888
|
+
def build_dir_card(target, entries: List[Dict], sessions: List[Dict], tree: bool = False, session_groups: Optional[Dict[str, str]] = None, page: int = 0) -> Dict[str, Any]:
|
|
875
889
|
"""构建目录浏览卡片
|
|
876
890
|
|
|
877
891
|
顶层目录(depth==0)带两个操作按钮:
|
|
@@ -906,9 +920,17 @@ def build_dir_card(target, entries: List[Dict], sessions: List[Dict], tree: bool
|
|
|
906
920
|
})
|
|
907
921
|
elements.append({"tag": "hr"})
|
|
908
922
|
|
|
909
|
-
|
|
923
|
+
PER_PAGE = 12
|
|
910
924
|
total = len(entries)
|
|
911
|
-
|
|
925
|
+
if tree:
|
|
926
|
+
# tree 模式不分页,直接展示全部(已有 max_items 上限)
|
|
927
|
+
shown = entries
|
|
928
|
+
page = 0
|
|
929
|
+
total_pages = 1
|
|
930
|
+
else:
|
|
931
|
+
total_pages = max(1, (total + PER_PAGE - 1) // PER_PAGE)
|
|
932
|
+
page = max(0, min(page, total_pages - 1))
|
|
933
|
+
shown = entries[page * PER_PAGE : (page + 1) * PER_PAGE]
|
|
912
934
|
|
|
913
935
|
for entry in shown:
|
|
914
936
|
name = entry["name"]
|
|
@@ -920,6 +942,22 @@ def build_dir_card(target, entries: List[Dict], sessions: List[Dict], tree: bool
|
|
|
920
942
|
|
|
921
943
|
if is_dir and depth == 0:
|
|
922
944
|
auto_session = _dir_session_name(full_path)
|
|
945
|
+
group_btn = {
|
|
946
|
+
"tag": "button",
|
|
947
|
+
"text": {"tag": "plain_text", "content": "进入群聊" if (session_groups and auto_session in session_groups) else "创建群聊"},
|
|
948
|
+
"type": "default",
|
|
949
|
+
"behaviors": [{"type": "open_url",
|
|
950
|
+
"default_url": f"https://applink.feishu.cn/client/chat/open?openChatId={session_groups[auto_session]}",
|
|
951
|
+
"android_url": f"https://applink.feishu.cn/client/chat/open?openChatId={session_groups[auto_session]}",
|
|
952
|
+
"ios_url": f"https://applink.feishu.cn/client/chat/open?openChatId={session_groups[auto_session]}",
|
|
953
|
+
"pc_url": f"https://applink.feishu.cn/client/chat/open?openChatId={session_groups[auto_session]}"}]
|
|
954
|
+
if (session_groups and auto_session in session_groups) else
|
|
955
|
+
[{"type": "callback", "value": {
|
|
956
|
+
"action": "dir_new_group",
|
|
957
|
+
"path": full_path,
|
|
958
|
+
"session_name": auto_session
|
|
959
|
+
}}]
|
|
960
|
+
}
|
|
923
961
|
elements.append({
|
|
924
962
|
"tag": "column_set",
|
|
925
963
|
"flex_mode": "none",
|
|
@@ -927,65 +965,77 @@ def build_dir_card(target, entries: List[Dict], sessions: List[Dict], tree: bool
|
|
|
927
965
|
{
|
|
928
966
|
"tag": "column",
|
|
929
967
|
"width": "weighted",
|
|
930
|
-
"weight":
|
|
931
|
-
"elements": [{"tag": "markdown", "content": f"{icon} **{name}**"}]
|
|
932
|
-
},
|
|
933
|
-
{
|
|
934
|
-
"tag": "column",
|
|
935
|
-
"width": "weighted",
|
|
936
|
-
"weight": 2,
|
|
968
|
+
"weight": 4,
|
|
937
969
|
"elements": [{
|
|
938
|
-
"tag": "
|
|
939
|
-
"
|
|
940
|
-
"
|
|
970
|
+
"tag": "interactive_container",
|
|
971
|
+
"width": "fill",
|
|
972
|
+
"height": "auto",
|
|
941
973
|
"behaviors": [{"type": "callback", "value": {
|
|
942
974
|
"action": "dir_browse", "path": full_path
|
|
943
|
-
}}]
|
|
975
|
+
}}],
|
|
976
|
+
"elements": [{"tag": "markdown", "content": f"📁 **{name}**"}]
|
|
944
977
|
}]
|
|
945
978
|
},
|
|
946
979
|
{
|
|
947
980
|
"tag": "column",
|
|
948
981
|
"width": "weighted",
|
|
949
982
|
"weight": 2,
|
|
950
|
-
"elements": [
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
"
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
"width": "weighted",
|
|
964
|
-
"weight": 2,
|
|
965
|
-
"elements": [{
|
|
966
|
-
"tag": "button",
|
|
967
|
-
"text": {"tag": "plain_text", "content": "进入群聊" if (session_groups and auto_session in session_groups) else "创建群聊"},
|
|
968
|
-
"type": "default",
|
|
969
|
-
"behaviors": [{"type": "open_url",
|
|
970
|
-
"default_url": f"https://applink.feishu.cn/client/chat/open?openChatId={session_groups[auto_session]}",
|
|
971
|
-
"android_url": f"https://applink.feishu.cn/client/chat/open?openChatId={session_groups[auto_session]}",
|
|
972
|
-
"ios_url": f"https://applink.feishu.cn/client/chat/open?openChatId={session_groups[auto_session]}",
|
|
973
|
-
"pc_url": f"https://applink.feishu.cn/client/chat/open?openChatId={session_groups[auto_session]}"}]
|
|
974
|
-
if (session_groups and auto_session in session_groups) else
|
|
975
|
-
[{"type": "callback", "value": {
|
|
976
|
-
"action": "dir_new_group",
|
|
977
|
-
"path": full_path,
|
|
978
|
-
"session_name": auto_session
|
|
979
|
-
}}]
|
|
980
|
-
}]
|
|
983
|
+
"elements": [
|
|
984
|
+
{
|
|
985
|
+
"tag": "button",
|
|
986
|
+
"text": {"tag": "plain_text", "content": "Claude"},
|
|
987
|
+
"type": "primary",
|
|
988
|
+
"behaviors": [{"type": "callback", "value": {
|
|
989
|
+
"action": "dir_start",
|
|
990
|
+
"path": full_path,
|
|
991
|
+
"session_name": auto_session
|
|
992
|
+
}}]
|
|
993
|
+
},
|
|
994
|
+
group_btn
|
|
995
|
+
]
|
|
981
996
|
}
|
|
982
997
|
]
|
|
983
998
|
})
|
|
999
|
+
elements.append({"tag": "hr"})
|
|
984
1000
|
else:
|
|
985
1001
|
elements.append({"tag": "markdown", "content": f"{indent}{icon} {name}"})
|
|
986
1002
|
|
|
987
|
-
if total >
|
|
988
|
-
|
|
1003
|
+
if not tree and total > PER_PAGE:
|
|
1004
|
+
page_cols = []
|
|
1005
|
+
if page > 0:
|
|
1006
|
+
page_cols.append({
|
|
1007
|
+
"tag": "column",
|
|
1008
|
+
"width": "auto",
|
|
1009
|
+
"elements": [{
|
|
1010
|
+
"tag": "button",
|
|
1011
|
+
"text": {"tag": "plain_text", "content": "⬅️ 上一页"},
|
|
1012
|
+
"type": "default",
|
|
1013
|
+
"behaviors": [{"type": "callback", "value": {
|
|
1014
|
+
"action": "dir_page", "path": target_str, "page": page - 1
|
|
1015
|
+
}}]
|
|
1016
|
+
}]
|
|
1017
|
+
})
|
|
1018
|
+
page_cols.append({
|
|
1019
|
+
"tag": "column",
|
|
1020
|
+
"width": "weighted",
|
|
1021
|
+
"weight": 2,
|
|
1022
|
+
"vertical_align": "center",
|
|
1023
|
+
"elements": [{"tag": "markdown", "content": f"第 {page + 1}/{total_pages} 页"}]
|
|
1024
|
+
})
|
|
1025
|
+
if page < total_pages - 1:
|
|
1026
|
+
page_cols.append({
|
|
1027
|
+
"tag": "column",
|
|
1028
|
+
"width": "auto",
|
|
1029
|
+
"elements": [{
|
|
1030
|
+
"tag": "button",
|
|
1031
|
+
"text": {"tag": "plain_text", "content": "下一页 ➡️"},
|
|
1032
|
+
"type": "default",
|
|
1033
|
+
"behaviors": [{"type": "callback", "value": {
|
|
1034
|
+
"action": "dir_page", "path": target_str, "page": page + 1
|
|
1035
|
+
}}]
|
|
1036
|
+
}]
|
|
1037
|
+
})
|
|
1038
|
+
elements.append({"tag": "column_set", "flex_mode": "none", "columns": page_cols})
|
|
989
1039
|
|
|
990
1040
|
elements.append({"tag": "hr"})
|
|
991
1041
|
elements.append(_build_menu_button_only())
|
|
@@ -993,7 +1043,7 @@ def build_dir_card(target, entries: List[Dict], sessions: List[Dict], tree: bool
|
|
|
993
1043
|
return {
|
|
994
1044
|
"schema": "2.0",
|
|
995
1045
|
"config": {"wide_screen_mode": True},
|
|
996
|
-
"header":
|
|
1046
|
+
"header": _build_header(title, "blue"),
|
|
997
1047
|
"body": {"elements": elements}
|
|
998
1048
|
}
|
|
999
1049
|
|
|
@@ -1025,10 +1075,7 @@ def build_help_card() -> Dict[str, Any]:
|
|
|
1025
1075
|
return {
|
|
1026
1076
|
"schema": "2.0",
|
|
1027
1077
|
"config": {"wide_screen_mode": True},
|
|
1028
|
-
"header":
|
|
1029
|
-
"title": {"tag": "plain_text", "content": "📖 Remote Claude 帮助"},
|
|
1030
|
-
"template": "blue",
|
|
1031
|
-
},
|
|
1078
|
+
"header": _build_header("📖 Remote Claude 帮助", "blue"),
|
|
1032
1079
|
"body": {"elements": [
|
|
1033
1080
|
{"tag": "markdown", "content": help_content},
|
|
1034
1081
|
{"tag": "hr"},
|
|
@@ -1042,10 +1089,7 @@ def build_session_closed_card(session_name: str) -> Dict[str, Any]:
|
|
|
1042
1089
|
return {
|
|
1043
1090
|
"schema": "2.0",
|
|
1044
1091
|
"config": {"wide_screen_mode": True},
|
|
1045
|
-
"header":
|
|
1046
|
-
"title": {"tag": "plain_text", "content": "🔴 会话已关闭"},
|
|
1047
|
-
"template": "red",
|
|
1048
|
-
},
|
|
1092
|
+
"header": _build_header("🔴 会话已关闭", "red"),
|
|
1049
1093
|
"body": {"elements": [
|
|
1050
1094
|
{"tag": "markdown", "content": f"会话 **{session_name}** 已关闭,连接已自动断开。\n\n如需继续,请重新启动会话或连接到其他会话。"},
|
|
1051
1095
|
{"tag": "hr"},
|
|
@@ -1093,17 +1137,6 @@ def build_menu_card(sessions: List[Dict], current_session: Optional[str] = None,
|
|
|
1093
1137
|
"tag": "column_set",
|
|
1094
1138
|
"flex_mode": "none",
|
|
1095
1139
|
"columns": [
|
|
1096
|
-
{
|
|
1097
|
-
"tag": "column",
|
|
1098
|
-
"width": "weighted",
|
|
1099
|
-
"weight": 1,
|
|
1100
|
-
"elements": [{
|
|
1101
|
-
"tag": "button",
|
|
1102
|
-
"text": {"tag": "plain_text", "content": "📖 帮助"},
|
|
1103
|
-
"type": "default",
|
|
1104
|
-
"behaviors": [{"type": "callback", "value": {"action": "menu_help"}}]
|
|
1105
|
-
}]
|
|
1106
|
-
},
|
|
1107
1140
|
{
|
|
1108
1141
|
"tag": "column",
|
|
1109
1142
|
"width": "weighted",
|
|
@@ -1132,9 +1165,6 @@ def build_menu_card(sessions: List[Dict], current_session: Optional[str] = None,
|
|
|
1132
1165
|
return {
|
|
1133
1166
|
"schema": "2.0",
|
|
1134
1167
|
"config": {"wide_screen_mode": True},
|
|
1135
|
-
"header":
|
|
1136
|
-
"title": {"tag": "plain_text", "content": "⚡ 快捷操作"},
|
|
1137
|
-
"template": "turquoise",
|
|
1138
|
-
},
|
|
1168
|
+
"header": _build_header("⚡ 快捷操作", "turquoise"),
|
|
1139
1169
|
"body": {"elements": elements}
|
|
1140
1170
|
}
|
|
@@ -22,6 +22,23 @@ from lark_oapi.api.cardkit.v1 import (
|
|
|
22
22
|
|
|
23
23
|
from . import config
|
|
24
24
|
|
|
25
|
+
|
|
26
|
+
def _is_element_limit_error(msg: str) -> bool:
|
|
27
|
+
"""判断飞书 API 返回的错误是否为元素超限"""
|
|
28
|
+
if not msg:
|
|
29
|
+
return False
|
|
30
|
+
lower = msg.lower()
|
|
31
|
+
return "element exceeds" in lower or "超限" in lower
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class _ElementLimitResult:
|
|
35
|
+
"""元素超限的哨兵返回值,__bool__ 为 False 兼容现有 if not success 逻辑"""
|
|
36
|
+
is_element_limit = True
|
|
37
|
+
|
|
38
|
+
def __bool__(self):
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
|
|
25
42
|
import sys as _sys
|
|
26
43
|
_sys.path.insert(0, str(__import__('pathlib').Path(__file__).parent.parent))
|
|
27
44
|
try:
|
|
@@ -193,6 +210,10 @@ class CardService:
|
|
|
193
210
|
return True
|
|
194
211
|
else:
|
|
195
212
|
logger.warning(f"更新卡片失败(attempt={attempt+1}): card_id={card_id} seq={sequence} code={response.code} msg={response.msg}")
|
|
213
|
+
if _is_element_limit_error(response.msg):
|
|
214
|
+
# 元素超限是内容问题,重试无意义,直接返回哨兵值
|
|
215
|
+
logger.warning(f"检测到元素超限错误,跳过重试: card_id={card_id}")
|
|
216
|
+
return _ElementLimitResult()
|
|
196
217
|
except Exception as e:
|
|
197
218
|
logger.error(f"更新卡片异常(attempt={attempt+1}): card_id={card_id} seq={sequence} error={e}")
|
|
198
219
|
|
|
@@ -531,7 +531,7 @@ class LarkHandler:
|
|
|
531
531
|
await self._send_or_update_card(chat_id, card, message_id)
|
|
532
532
|
|
|
533
533
|
async def _cmd_ls(self, user_id: str, chat_id: str, args: str,
|
|
534
|
-
tree: bool = False, message_id: Optional[str] = None):
|
|
534
|
+
tree: bool = False, message_id: Optional[str] = None, page: int = 0):
|
|
535
535
|
"""查看目录文件结构"""
|
|
536
536
|
all_sessions = list_active_sessions()
|
|
537
537
|
sessions_info = []
|
|
@@ -571,7 +571,7 @@ class LarkHandler:
|
|
|
571
571
|
return
|
|
572
572
|
|
|
573
573
|
session_groups = {sname: cid for cid, sname in self._chat_bindings.items() if cid.startswith("oc_")}
|
|
574
|
-
card = build_dir_card(target, entries, sessions_info, tree=tree, session_groups=session_groups)
|
|
574
|
+
card = build_dir_card(target, entries, sessions_info, tree=tree, session_groups=session_groups, page=page)
|
|
575
575
|
await self._send_or_update_card(chat_id, card, message_id)
|
|
576
576
|
|
|
577
577
|
async def _cmd_new_group(self, user_id: str, chat_id: str, args: str,
|
|
@@ -854,7 +854,7 @@ class LarkHandler:
|
|
|
854
854
|
})
|
|
855
855
|
except PermissionError:
|
|
856
856
|
pass
|
|
857
|
-
return entries
|
|
857
|
+
return entries
|
|
858
858
|
|
|
859
859
|
@staticmethod
|
|
860
860
|
def _collect_tree_entries(target, max_depth: int = 2, max_items: int = 60) -> list:
|
package/lark_client/main.py
CHANGED
|
@@ -176,6 +176,14 @@ def handle_card_action(event: P2CardActionTrigger) -> P2CardActionTriggerRespons
|
|
|
176
176
|
asyncio.create_task(handler._cmd_ls(user_id, chat_id, path, message_id=message_id))
|
|
177
177
|
return None
|
|
178
178
|
|
|
179
|
+
# 目录卡片:翻页
|
|
180
|
+
if action_type == "dir_page":
|
|
181
|
+
path = action_value.get("path", "")
|
|
182
|
+
page = int(action_value.get("page", 0))
|
|
183
|
+
print(f"[Lark] dir_page: path={path}, page={page}")
|
|
184
|
+
asyncio.create_task(handler._cmd_ls(user_id, chat_id, path, message_id=message_id, page=page))
|
|
185
|
+
return None
|
|
186
|
+
|
|
179
187
|
# 目录卡片:在该目录创建新 Claude 会话
|
|
180
188
|
if action_type == "dir_start":
|
|
181
189
|
path = action_value.get("path", "")
|
|
@@ -230,8 +238,13 @@ def handle_card_action(event: P2CardActionTrigger) -> P2CardActionTriggerRespons
|
|
|
230
238
|
# 快捷键按钮(callback 模式)
|
|
231
239
|
if action_type == "send_key":
|
|
232
240
|
key_name = action_value.get("key", "")
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
times = action_value.get("times", 1)
|
|
242
|
+
print(f"[Lark] send_key: key={key_name}" + (f" ×{times}" if times > 1 else ""))
|
|
243
|
+
async def _multi_send(k=key_name, t=times):
|
|
244
|
+
for _ in range(t):
|
|
245
|
+
await handler.send_raw_key(user_id, chat_id, k)
|
|
246
|
+
await asyncio.sleep(0.15)
|
|
247
|
+
asyncio.create_task(_multi_send())
|
|
235
248
|
return None
|
|
236
249
|
|
|
237
250
|
# 各卡片底部菜单按钮:辅助卡片就地→菜单,流式卡片降级新卡
|
|
@@ -247,7 +247,13 @@ class SharedMemoryPoller:
|
|
|
247
247
|
card_content=card_dict,
|
|
248
248
|
)
|
|
249
249
|
|
|
250
|
-
if
|
|
250
|
+
if getattr(success, 'is_element_limit', False):
|
|
251
|
+
# 元素超限:冻结旧卡 + 推新流式卡
|
|
252
|
+
await self._handle_element_limit(
|
|
253
|
+
tracker, blocks, status_line, bottom_bar, agent_panel, option_block
|
|
254
|
+
)
|
|
255
|
+
return
|
|
256
|
+
elif not success:
|
|
251
257
|
# 降级:创建新卡片替代
|
|
252
258
|
logger.warning(
|
|
253
259
|
f"update_card 失败 card_id={active.card_id} seq={active.sequence},降级为新卡片"
|
|
@@ -305,6 +311,48 @@ class SharedMemoryPoller:
|
|
|
305
311
|
else:
|
|
306
312
|
logger.warning(f"create_card 失败 session={tracker.session_name}")
|
|
307
313
|
|
|
314
|
+
async def _handle_element_limit(
|
|
315
|
+
self, tracker: StreamTracker, blocks: List[dict],
|
|
316
|
+
status_line: Optional[dict], bottom_bar: Optional[dict],
|
|
317
|
+
agent_panel: Optional[dict] = None,
|
|
318
|
+
option_block: Optional[dict] = None,
|
|
319
|
+
) -> None:
|
|
320
|
+
"""元素超限:冻结旧卡片 + 推送新流式卡片"""
|
|
321
|
+
active = tracker.cards[-1]
|
|
322
|
+
logger.warning(f"元素超限,冻结卡片 {active.card_id} 并推新卡")
|
|
323
|
+
|
|
324
|
+
# 1. 冻结旧卡片(灰色 header,无状态区和按钮)
|
|
325
|
+
from .card_builder import build_stream_card
|
|
326
|
+
blocks_slice = blocks[active.start_idx:]
|
|
327
|
+
frozen_card = build_stream_card(blocks_slice, None, None, is_frozen=True)
|
|
328
|
+
active.sequence += 1
|
|
329
|
+
await self._card_service.update_card(active.card_id, active.sequence, frozen_card)
|
|
330
|
+
active.frozen = True
|
|
331
|
+
_track_stats('card', 'freeze', session_name=tracker.session_name,
|
|
332
|
+
chat_id=tracker.chat_id)
|
|
333
|
+
|
|
334
|
+
# 2. 创建新流式卡片,从最近 INITIAL_WINDOW 个 blocks 开始(重置窗口)
|
|
335
|
+
new_start = max(0, len(blocks) - INITIAL_WINDOW)
|
|
336
|
+
new_blocks = blocks[new_start:]
|
|
337
|
+
if not new_blocks and not status_line and not bottom_bar:
|
|
338
|
+
return
|
|
339
|
+
new_card_dict = build_stream_card(
|
|
340
|
+
new_blocks, status_line, bottom_bar,
|
|
341
|
+
agent_panel=agent_panel, option_block=option_block,
|
|
342
|
+
session_name=tracker.session_name,
|
|
343
|
+
)
|
|
344
|
+
new_card_id = await self._card_service.create_card(new_card_dict)
|
|
345
|
+
if new_card_id:
|
|
346
|
+
await self._card_service.send_card(tracker.chat_id, new_card_id)
|
|
347
|
+
tracker.cards.append(CardSlice(card_id=new_card_id, start_idx=new_start))
|
|
348
|
+
tracker.content_hash = self._compute_hash(new_blocks, status_line, bottom_bar, agent_panel, option_block)
|
|
349
|
+
_track_stats('card', 'create', session_name=tracker.session_name,
|
|
350
|
+
chat_id=tracker.chat_id)
|
|
351
|
+
logger.info(
|
|
352
|
+
f"[ELEMENT_LIMIT_SPLIT] session={tracker.session_name} "
|
|
353
|
+
f"new_start={new_start} blocks={len(new_blocks)} card_id={new_card_id}"
|
|
354
|
+
)
|
|
355
|
+
|
|
308
356
|
async def _freeze_and_split(
|
|
309
357
|
self, tracker: StreamTracker, blocks: List[dict],
|
|
310
358
|
status_line: Optional[dict], bottom_bar: Optional[dict],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remote-claude",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "双端共享 Claude CLI 工具",
|
|
5
5
|
"bin": {
|
|
6
6
|
"remote-claude": "bin/remote-claude",
|
|
@@ -26,13 +26,22 @@
|
|
|
26
26
|
"uv.lock",
|
|
27
27
|
".env.example"
|
|
28
28
|
],
|
|
29
|
-
"os": [
|
|
29
|
+
"os": [
|
|
30
|
+
"darwin",
|
|
31
|
+
"linux"
|
|
32
|
+
],
|
|
30
33
|
"license": "MIT",
|
|
31
34
|
"repository": {
|
|
32
35
|
"type": "git",
|
|
33
36
|
"url": "git+https://github.com/yuyangzi/remote_claude.git"
|
|
34
37
|
},
|
|
35
|
-
"keywords": [
|
|
38
|
+
"keywords": [
|
|
39
|
+
"claude",
|
|
40
|
+
"cli",
|
|
41
|
+
"terminal",
|
|
42
|
+
"pty",
|
|
43
|
+
"lark"
|
|
44
|
+
],
|
|
36
45
|
"engines": {
|
|
37
46
|
"node": ">=16"
|
|
38
47
|
},
|