remote-claude 0.2.11 → 0.2.13
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/.env.example +4 -0
- package/README.md +1 -0
- package/client/client.py +41 -2
- package/init.sh +18 -0
- package/lark_client/session_bridge.py +40 -1
- package/package.json +2 -1
- package/server/parsers/__init__.py +13 -0
- package/server/parsers/base_parser.py +44 -0
- package/server/parsers/claude_parser.py +1263 -0
- package/server/parsers/codex_parser.py +1496 -0
- package/server/server.py +50 -27
- package/utils/session.py +9 -1
package/server/server.py
CHANGED
|
@@ -42,6 +42,15 @@ from utils.session import (
|
|
|
42
42
|
|
|
43
43
|
logger = logging.getLogger('Server')
|
|
44
44
|
|
|
45
|
+
# Server 日志级别配置
|
|
46
|
+
_SERVER_LOG_LEVEL = os.getenv("SERVER_LOG_LEVEL", "INFO").upper()
|
|
47
|
+
SERVER_LOG_LEVEL_MAP = {
|
|
48
|
+
"DEBUG": logging.DEBUG,
|
|
49
|
+
"INFO": logging.INFO,
|
|
50
|
+
"WARNING": logging.WARNING,
|
|
51
|
+
"ERROR": logging.ERROR,
|
|
52
|
+
}.get(_SERVER_LOG_LEVEL, logging.INFO) # 默认 INFO
|
|
53
|
+
|
|
45
54
|
# 加载用户 .env 配置(支持 CLAUDE_COMMAND 等)
|
|
46
55
|
try:
|
|
47
56
|
from dotenv import load_dotenv
|
|
@@ -126,11 +135,11 @@ class OutputWatcher:
|
|
|
126
135
|
# 持久化解析器(跨帧保留 dot_row_cache);由调用方注入(可插拔架构)
|
|
127
136
|
import logging as _logging
|
|
128
137
|
_logging.getLogger('ComponentParser').setLevel(_logging.DEBUG)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
# 创建专用 logger 替换直接文件写入
|
|
139
|
+
self._blink_logger = _logging.getLogger(f'OutputWatcher.{self._session_name}.blink')
|
|
140
|
+
self._blink_logger.setLevel(_logging.DEBUG)
|
|
141
|
+
self._flush_logger = _logging.getLogger(f'OutputWatcher.{self._session_name}.flush')
|
|
142
|
+
self._flush_logger.setLevel(_logging.DEBUG)
|
|
134
143
|
if parser is None:
|
|
135
144
|
from parsers import ClaudeParser
|
|
136
145
|
parser = ClaudeParser()
|
|
@@ -258,11 +267,7 @@ class OutputWatcher:
|
|
|
258
267
|
pass
|
|
259
268
|
break
|
|
260
269
|
if last_ob_blink:
|
|
261
|
-
|
|
262
|
-
_blink_log.write(
|
|
263
|
-
f"[{time.strftime('%H:%M:%S')}] raw-blink last_ob={last_ob_content!r}\n"
|
|
264
|
-
)
|
|
265
|
-
_blink_log.close()
|
|
270
|
+
self._blink_logger.debug(f"raw-blink last_ob={last_ob_content!r}")
|
|
266
271
|
|
|
267
272
|
self._frame_window.append(_FrameObs(
|
|
268
273
|
ts=now,
|
|
@@ -308,23 +313,19 @@ class OutputWatcher:
|
|
|
308
313
|
if len(chars) > 1 or len(fgs) > 1 or len(bolds) > 1:
|
|
309
314
|
window_block_active = True
|
|
310
315
|
# 记录字符变化触发原因
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
f"
|
|
314
|
-
f" chars={chars} fgs={fgs} bolds={bolds}\n"
|
|
316
|
+
self._blink_logger.debug(
|
|
317
|
+
f"char-change row={last_ob_start_row}"
|
|
318
|
+
f" chars={chars} fgs={fgs} bolds={bolds}"
|
|
315
319
|
)
|
|
316
|
-
_blink_log.close()
|
|
317
320
|
|
|
318
321
|
if window_block_active:
|
|
319
322
|
for b in reversed(visible_blocks):
|
|
320
323
|
if isinstance(b, OutputBlock):
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
f"[{time.strftime('%H:%M:%S')}] win-smooth last_ob={b.content[:40]!r}"
|
|
324
|
+
self._blink_logger.debug(
|
|
325
|
+
f"win-smooth last_ob={b.content[:40]!r}"
|
|
324
326
|
f" window_frames={len(window_list)}"
|
|
325
|
-
f" blink_frames={sum(1 for o in window_list if o.block_blink)}
|
|
327
|
+
f" blink_frames={sum(1 for o in window_list if o.block_blink)}"
|
|
326
328
|
)
|
|
327
|
-
_blink_log.close()
|
|
328
329
|
b.is_streaming = True
|
|
329
330
|
break
|
|
330
331
|
|
|
@@ -373,13 +374,12 @@ class OutputWatcher:
|
|
|
373
374
|
self._on_snapshot(window)
|
|
374
375
|
t4 = time.time()
|
|
375
376
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
)
|
|
377
|
+
self._flush_logger.debug(
|
|
378
|
+
f"[flush] screen_log={1000*(t1-t0):.1f}ms parse={1000*(t2-t1):.1f}ms "
|
|
379
|
+
f"msg_log={1000*(t3-t2):.1f}ms snapshot={1000*(t4-t3):.1f}ms "
|
|
380
|
+
f"total={1000*(t4-t0):.1f}ms rows={self._rows}\n"
|
|
381
|
+
f" └─ {self._parser.last_parse_timing}"
|
|
382
|
+
)
|
|
383
383
|
|
|
384
384
|
except Exception as e:
|
|
385
385
|
print(f"[OutputWatcher] flush 失败: {e}")
|
|
@@ -727,6 +727,14 @@ class ProxyServer:
|
|
|
727
727
|
not hasattr(handler, '_debug_handler'):
|
|
728
728
|
root_logger.removeHandler(handler)
|
|
729
729
|
|
|
730
|
+
# 重定向 sys.stderr 到 ~/.remote-claude/server.error.log
|
|
731
|
+
# 注意:这不会影响外层的 2>> startup.log,但 Python 的 stderr 输出会走这里
|
|
732
|
+
# 适用于:print(..., file=sys.stderr)、logging 的 StreamHandler 等
|
|
733
|
+
# 不适用于:C 扩展模块直接写文件描述符 2、解释器崩溃等底层错误
|
|
734
|
+
error_log_path = os.path.expanduser('~/.remote-claude/server.error.log')
|
|
735
|
+
sys.stderr = open(error_log_path, 'w', encoding='utf-8')
|
|
736
|
+
logger.info(f"已重定向 stderr 到 {error_log_path}")
|
|
737
|
+
|
|
730
738
|
# 添加运行阶段日志文件
|
|
731
739
|
safe_name = _safe_filename(self.session_name)
|
|
732
740
|
runtime_handler = logging.FileHandler(
|
|
@@ -740,6 +748,21 @@ class ProxyServer:
|
|
|
740
748
|
runtime_handler._runtime_handler = True # 标记,方便后续清理
|
|
741
749
|
root_logger.addHandler(runtime_handler)
|
|
742
750
|
|
|
751
|
+
# DEBUG 级别时额外记录调试日志到独立文件
|
|
752
|
+
if SERVER_LOG_LEVEL_MAP == logging.DEBUG:
|
|
753
|
+
debug_handler = logging.FileHandler(
|
|
754
|
+
f"{SOCKET_DIR}/{safe_name}_debug.log",
|
|
755
|
+
encoding="utf-8"
|
|
756
|
+
)
|
|
757
|
+
debug_handler.setLevel(logging.DEBUG)
|
|
758
|
+
debug_handler.setFormatter(logging.Formatter(
|
|
759
|
+
"%(asctime)s.%(msecs)03d [%(name)s] %(levelname)s %(message)s",
|
|
760
|
+
datefmt="%Y-%m-%d %H:%M:%S"
|
|
761
|
+
))
|
|
762
|
+
debug_handler._debug_handler = True # 标记,方便后续清理
|
|
763
|
+
root_logger.addHandler(debug_handler)
|
|
764
|
+
logger.info(f"已启用 DEBUG 日志: {safe_name}_debug.log")
|
|
765
|
+
|
|
743
766
|
logger.info(f"日志已切换到运行阶段: {safe_name}_server.log")
|
|
744
767
|
|
|
745
768
|
def _get_effective_cmd(self) -> str:
|
package/utils/session.py
CHANGED
|
@@ -99,7 +99,15 @@ def tmux_create_session(session_name: str, command: str, detached: bool = True)
|
|
|
99
99
|
if detached:
|
|
100
100
|
args.append("-d")
|
|
101
101
|
args.extend(["-x", "200", "-y", "50"]) # 默认大小
|
|
102
|
-
|
|
102
|
+
|
|
103
|
+
# 将 stderr 重定向到 startup.log,捕获 Python 启动错误(如 ModuleNotFoundError)
|
|
104
|
+
# startup.log 位于 ~/.remote-claude/startup.log
|
|
105
|
+
startup_log = USER_DATA_DIR / "startup.log"
|
|
106
|
+
startup_log.parent.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
# 直接在命令末尾添加重定向,使用 str(startup_log) 确保路径正确展开
|
|
108
|
+
command_with_stderr = f"{command} 2>> {startup_log}"
|
|
109
|
+
|
|
110
|
+
args.append(command_with_stderr)
|
|
103
111
|
|
|
104
112
|
import logging as _logging
|
|
105
113
|
_logging.getLogger('Start').info(f"tmux_cmd: {' '.join(args)}")
|