@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## beta 1.0.1.1016 - 2026-10-16
|
||||
|
||||
- Limit RapidOCR ONNXRuntime intra/inter operation threads by default so the
|
||||
adaptive OCR recorder no longer saturates CPU after restart and slow recovery
|
||||
windows do not stretch local command detection into 10+ second scans.
|
||||
|
||||
## beta 1.0.0.1016 - 2026-10-16
|
||||
|
||||
- Promote JWeBot from alpha to beta after the local, staging, privacy, and
|
||||
|
||||
@@ -8,7 +8,7 @@ JWeBot 是运行于 Linux 的单群微信自动回复系统。它通过本地 OC
|
||||
观察选定的微信窗口,将消息写入持久化 Edge 账本,调用经过审核的命令插件,
|
||||
并通过带视觉确认的 GUI 发送链路回复。
|
||||
|
||||
当前版本:`beta 1.0.0.1016`,更新日期:2026-10-16。
|
||||
当前版本:`beta 1.0.1.1016`,更新日期:2026-10-16。
|
||||
|
||||
1.0 Beta 收口了当前稳定功能:本地命令插件、人物与上下文索引、
|
||||
Steam 状态与时长查询、虚拟金币游戏、多人彩票、21 点、CS Major
|
||||
@@ -263,7 +263,7 @@ visually confirmed GUI bridge.
|
||||
Current version:
|
||||
|
||||
```text
|
||||
beta 1.0.0.1016
|
||||
beta 1.0.1.1016
|
||||
```
|
||||
|
||||
Updated: 2026-10-16.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# JWeBot Command Catalog
|
||||
|
||||
Current baseline: `beta 1.0.0.1016`.
|
||||
Current baseline: `beta 1.0.1.1016`.
|
||||
|
||||
This catalog records the reviewed commands shipped with the built-in local
|
||||
extensions. Runtime ownership is frozen at startup from the generated
|
||||
|
||||
@@ -90,6 +90,15 @@ Steam uses `JWEBOT_EDGE_STEAM_ID64` and
|
||||
- Host timeout and fallback;
|
||||
- GUI sender timeout.
|
||||
|
||||
The OCR runtime also accepts optional environment overrides for constrained
|
||||
hosts:
|
||||
|
||||
- `WECHAT_OCR_ONNX_INTRA_OP_THREADS` defaults to `1`;
|
||||
- `WECHAT_OCR_ONNX_INTER_OP_THREADS` defaults to `1`.
|
||||
|
||||
These values limit RapidOCR's ONNXRuntime CPU thread pools so service restarts
|
||||
do not temporarily saturate the host while the OCR cache is cold.
|
||||
|
||||
### Debug
|
||||
|
||||
`components/debug.toml` generates:
|
||||
|
||||
+2
-2
@@ -14,9 +14,9 @@ Rules:
|
||||
- `X.Y.Z` is the project version.
|
||||
- `MMDD` is the delivery date.
|
||||
- Python package versions use PEP 440 pre-release form. For beta builds,
|
||||
`beta 1.0.0.1016` maps to `1.0.0b1+1016`.
|
||||
`beta 1.0.1.1016` maps to `1.0.1b1+1016`.
|
||||
- Normal small updates increment only the patch version, for example
|
||||
`beta 1.0.0.1016` to `beta 1.0.1.1016`.
|
||||
`beta 1.0.1.1016` to `beta 1.0.2.1016`.
|
||||
- Minor, major, or stable-release changes require explicit user review and
|
||||
milestone approval.
|
||||
- Local repository migration does not imply a production deployment.
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "jwebot-config"
|
||||
version = "1.0.0b1+1016"
|
||||
version = "1.0.1b1+1016"
|
||||
description = "Profile-based configuration compiler for JWeBot"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
|
||||
@@ -36,4 +36,4 @@ __all__ = [
|
||||
"write_default_profile",
|
||||
]
|
||||
|
||||
__version__ = "1.0.0b1+1016"
|
||||
__version__ = "1.0.1b1+1016"
|
||||
|
||||
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "jwebot-extension-host"
|
||||
version = "1.0.0b1+1016"
|
||||
version = "1.0.1b1+1016"
|
||||
description = "Single-release, single-invocation process host for JWeBot extensions"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"jwebot-sdk==1.0.0b1+1016",
|
||||
"jwebot-kernel==1.0.0b1+1016",
|
||||
"jwebot-sdk==1.0.1b1+1016",
|
||||
"jwebot-kernel==1.0.1b1+1016",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -16,4 +16,4 @@ __all__ = [
|
||||
"process_request",
|
||||
]
|
||||
|
||||
__version__ = "1.0.0b1+1016"
|
||||
__version__ = "1.0.1b1+1016"
|
||||
|
||||
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "jwebot-kernel"
|
||||
version = "1.0.0b1+1016"
|
||||
version = "1.0.1b1+1016"
|
||||
description = "Stable routing and extension authority for JWeBot"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"jwebot-config==1.0.0b1+1016",
|
||||
"jwebot-sdk==1.0.0b1+1016",
|
||||
"jwebot-official-extensions==1.0.0b1+1016",
|
||||
"jwebot-config==1.0.1b1+1016",
|
||||
"jwebot-sdk==1.0.1b1+1016",
|
||||
"jwebot-official-extensions==1.0.1b1+1016",
|
||||
"tomli>=2; python_version < '3.11'",
|
||||
]
|
||||
|
||||
|
||||
@@ -107,4 +107,4 @@ __all__ = [
|
||||
"verify_installed_release_signature",
|
||||
]
|
||||
|
||||
__version__ = "1.0.0b1+1016"
|
||||
__version__ = "1.0.1b1+1016"
|
||||
|
||||
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "jwebot-official-extensions"
|
||||
version = "1.0.0b1+1016"
|
||||
version = "1.0.1b1+1016"
|
||||
description = "Reviewed local command adapters and broker implementation for JWeBot"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"jwebot-sdk==1.0.0b1+1016",
|
||||
"jwebot-sdk==1.0.1b1+1016",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
|
||||
@@ -90,4 +90,4 @@ __all__ = [
|
||||
"render_feature_list",
|
||||
]
|
||||
|
||||
__version__ = "1.0.0b1+1016"
|
||||
__version__ = "1.0.1b1+1016"
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "jwebot-sdk"
|
||||
version = "1.0.0b1+1016"
|
||||
version = "1.0.1b1+1016"
|
||||
description = "Typed, capability-limited extension API for JWeBot plugins"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = []
|
||||
|
||||
@@ -38,4 +38,4 @@ __all__ = [
|
||||
"SingleGroupContext",
|
||||
]
|
||||
|
||||
__version__ = "1.0.0b1+1016"
|
||||
__version__ = "1.0.1b1+1016"
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
[tool.jwebot]
|
||||
release = "beta 1.0.0.1016"
|
||||
release = "beta 1.0.1.1016"
|
||||
production_deployments_enabled_by_default = false
|
||||
|
||||
[tool.jwebot.workspace]
|
||||
|
||||
@@ -867,9 +867,9 @@ class LedgerAndApplicationTests(unittest.TestCase):
|
||||
self.assertEqual(version["route"], "local")
|
||||
self.assertIn("JWeBot 版本", version["text"])
|
||||
self.assertIn("kernel", version["text"])
|
||||
self.assertIn("最新版本:beta 1.0.0.1016", version["text"])
|
||||
self.assertIn("最新版本:beta 1.0.1.1016", version["text"])
|
||||
self.assertIn("更新时间:2026-10-16", version["text"])
|
||||
self.assertIn("更新摘要:Promote JWeBot", version["text"])
|
||||
self.assertIn("更新摘要:Limit RapidOCR", version["text"])
|
||||
self.assertEqual(debug["route"], "local")
|
||||
self.assertIn("调试埋点", debug["text"])
|
||||
self.assertEqual(balance["route"], "local")
|
||||
|
||||
@@ -4,6 +4,7 @@ import importlib.util
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import types
|
||||
import unittest
|
||||
from contextlib import nullcontext
|
||||
from datetime import datetime, timezone
|
||||
@@ -517,6 +518,56 @@ class GuiCoordinationTests(unittest.TestCase):
|
||||
|
||||
|
||||
class OcrFallbackTests(unittest.TestCase):
|
||||
def test_ocr_engine_limits_onnxruntime_threads_by_default(self):
|
||||
captured = {}
|
||||
|
||||
class FakeRapidOCR:
|
||||
def __init__(self, **kwargs):
|
||||
captured.update(kwargs)
|
||||
|
||||
fake_module = types.SimpleNamespace(RapidOCR=FakeRapidOCR)
|
||||
with mock.patch.dict(sys.modules, {"rapidocr": fake_module}), mock.patch.dict(
|
||||
MODULE.os.environ,
|
||||
{
|
||||
"WECHAT_OCR_ONNX_INTRA_OP_THREADS": "",
|
||||
"WECHAT_OCR_ONNX_INTER_OP_THREADS": "",
|
||||
},
|
||||
):
|
||||
MODULE.OcrEngine()
|
||||
|
||||
self.assertEqual(
|
||||
captured["params"],
|
||||
{
|
||||
"EngineConfig.onnxruntime.intra_op_num_threads": 1,
|
||||
"EngineConfig.onnxruntime.inter_op_num_threads": 1,
|
||||
},
|
||||
)
|
||||
|
||||
def test_ocr_engine_allows_thread_limit_overrides(self):
|
||||
captured = {}
|
||||
|
||||
class FakeRapidOCR:
|
||||
def __init__(self, **kwargs):
|
||||
captured.update(kwargs)
|
||||
|
||||
fake_module = types.SimpleNamespace(RapidOCR=FakeRapidOCR)
|
||||
with mock.patch.dict(sys.modules, {"rapidocr": fake_module}), mock.patch.dict(
|
||||
MODULE.os.environ,
|
||||
{
|
||||
"WECHAT_OCR_ONNX_INTRA_OP_THREADS": "2",
|
||||
"WECHAT_OCR_ONNX_INTER_OP_THREADS": "3",
|
||||
},
|
||||
):
|
||||
MODULE.OcrEngine()
|
||||
|
||||
self.assertEqual(
|
||||
captured["params"],
|
||||
{
|
||||
"EngineConfig.onnxruntime.intra_op_num_threads": 2,
|
||||
"EngineConfig.onnxruntime.inter_op_num_threads": 3,
|
||||
},
|
||||
)
|
||||
|
||||
def test_low_confidence_line_uses_detection_fallback(self):
|
||||
engine = MODULE.OcrEngine.__new__(MODULE.OcrEngine)
|
||||
bubble = MODULE.Bubble(50, 50, 132, 68, 5000)
|
||||
|
||||
+25
-1
@@ -67,6 +67,8 @@ DAMAGE_HEARTBEAT_SECONDS = 2.0
|
||||
CAPTURE_BUFFER_FRAMES = 12
|
||||
OCR_MESSAGE_CACHE_SIZE = 256
|
||||
OCR_CACHE_REFRESH_SCANS = 120
|
||||
DEFAULT_OCR_ONNX_INTRA_OP_THREADS = 1
|
||||
DEFAULT_OCR_ONNX_INTER_OP_THREADS = 1
|
||||
WINDOW_LAYOUT_SETTLE_SECONDS = 0.25
|
||||
FOLLOW_LATEST_SETTLE_SECONDS = 0.20
|
||||
FOLLOW_LATEST_MOUSE_X = 1432
|
||||
@@ -224,6 +226,17 @@ def normalize_text(value: str) -> str:
|
||||
return re.sub(r"\s+", " ", value).strip()
|
||||
|
||||
|
||||
def env_int(name: str, default: int) -> int:
|
||||
raw = os.environ.get(name, "").strip()
|
||||
if not raw:
|
||||
return default
|
||||
try:
|
||||
return int(raw)
|
||||
except ValueError:
|
||||
logging.warning("invalid integer environment value name=%s", name)
|
||||
return default
|
||||
|
||||
|
||||
def bubble_sequence_signature(
|
||||
sender_crop: Image.Image,
|
||||
body_crop: Image.Image,
|
||||
@@ -1099,7 +1112,18 @@ class OcrEngine:
|
||||
from rapidocr import RapidOCR
|
||||
|
||||
self._numpy = np
|
||||
self._engine = RapidOCR()
|
||||
self._engine = RapidOCR(
|
||||
params={
|
||||
"EngineConfig.onnxruntime.intra_op_num_threads": env_int(
|
||||
"WECHAT_OCR_ONNX_INTRA_OP_THREADS",
|
||||
DEFAULT_OCR_ONNX_INTRA_OP_THREADS,
|
||||
),
|
||||
"EngineConfig.onnxruntime.inter_op_num_threads": env_int(
|
||||
"WECHAT_OCR_ONNX_INTER_OP_THREADS",
|
||||
DEFAULT_OCR_ONNX_INTER_OP_THREADS,
|
||||
),
|
||||
}
|
||||
)
|
||||
self._message_cache: OrderedDict[
|
||||
str,
|
||||
tuple[str, str, float, float, float],
|
||||
|
||||
Reference in New Issue
Block a user