@@ -1,5 +1,11 @@
|
|||||||
# Changelog
|
# 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
|
## beta 1.0.0.1016 - 2026-10-16
|
||||||
|
|
||||||
- Promote JWeBot from alpha to beta after the local, staging, privacy, and
|
- Promote JWeBot from alpha to beta after the local, staging, privacy, and
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ JWeBot 是运行于 Linux 的单群微信自动回复系统。它通过本地 OC
|
|||||||
观察选定的微信窗口,将消息写入持久化 Edge 账本,调用经过审核的命令插件,
|
观察选定的微信窗口,将消息写入持久化 Edge 账本,调用经过审核的命令插件,
|
||||||
并通过带视觉确认的 GUI 发送链路回复。
|
并通过带视觉确认的 GUI 发送链路回复。
|
||||||
|
|
||||||
当前版本:`beta 1.0.0.1016`,更新日期:2026-10-16。
|
当前版本:`beta 1.0.1.1016`,更新日期:2026-10-16。
|
||||||
|
|
||||||
1.0 Beta 收口了当前稳定功能:本地命令插件、人物与上下文索引、
|
1.0 Beta 收口了当前稳定功能:本地命令插件、人物与上下文索引、
|
||||||
Steam 状态与时长查询、虚拟金币游戏、多人彩票、21 点、CS Major
|
Steam 状态与时长查询、虚拟金币游戏、多人彩票、21 点、CS Major
|
||||||
@@ -263,7 +263,7 @@ visually confirmed GUI bridge.
|
|||||||
Current version:
|
Current version:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
beta 1.0.0.1016
|
beta 1.0.1.1016
|
||||||
```
|
```
|
||||||
|
|
||||||
Updated: 2026-10-16.
|
Updated: 2026-10-16.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# JWeBot Command Catalog
|
# 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
|
This catalog records the reviewed commands shipped with the built-in local
|
||||||
extensions. Runtime ownership is frozen at startup from the generated
|
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;
|
- Host timeout and fallback;
|
||||||
- GUI sender timeout.
|
- 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
|
### Debug
|
||||||
|
|
||||||
`components/debug.toml` generates:
|
`components/debug.toml` generates:
|
||||||
|
|||||||
+2
-2
@@ -14,9 +14,9 @@ Rules:
|
|||||||
- `X.Y.Z` is the project version.
|
- `X.Y.Z` is the project version.
|
||||||
- `MMDD` is the delivery date.
|
- `MMDD` is the delivery date.
|
||||||
- Python package versions use PEP 440 pre-release form. For beta builds,
|
- 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
|
- 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
|
- Minor, major, or stable-release changes require explicit user review and
|
||||||
milestone approval.
|
milestone approval.
|
||||||
- Local repository migration does not imply a production deployment.
|
- Local repository migration does not imply a production deployment.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-config"
|
name = "jwebot-config"
|
||||||
version = "1.0.0b1+1016"
|
version = "1.0.1b1+1016"
|
||||||
description = "Profile-based configuration compiler for JWeBot"
|
description = "Profile-based configuration compiler for JWeBot"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -36,4 +36,4 @@ __all__ = [
|
|||||||
"write_default_profile",
|
"write_default_profile",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.0.0b1+1016"
|
__version__ = "1.0.1b1+1016"
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-extension-host"
|
name = "jwebot-extension-host"
|
||||||
version = "1.0.0b1+1016"
|
version = "1.0.1b1+1016"
|
||||||
description = "Single-release, single-invocation process host for JWeBot extensions"
|
description = "Single-release, single-invocation process host for JWeBot extensions"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jwebot-sdk==1.0.0b1+1016",
|
"jwebot-sdk==1.0.1b1+1016",
|
||||||
"jwebot-kernel==1.0.0b1+1016",
|
"jwebot-kernel==1.0.1b1+1016",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ __all__ = [
|
|||||||
"process_request",
|
"process_request",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.0.0b1+1016"
|
__version__ = "1.0.1b1+1016"
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-kernel"
|
name = "jwebot-kernel"
|
||||||
version = "1.0.0b1+1016"
|
version = "1.0.1b1+1016"
|
||||||
description = "Stable routing and extension authority for JWeBot"
|
description = "Stable routing and extension authority for JWeBot"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jwebot-config==1.0.0b1+1016",
|
"jwebot-config==1.0.1b1+1016",
|
||||||
"jwebot-sdk==1.0.0b1+1016",
|
"jwebot-sdk==1.0.1b1+1016",
|
||||||
"jwebot-official-extensions==1.0.0b1+1016",
|
"jwebot-official-extensions==1.0.1b1+1016",
|
||||||
"tomli>=2; python_version < '3.11'",
|
"tomli>=2; python_version < '3.11'",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -107,4 +107,4 @@ __all__ = [
|
|||||||
"verify_installed_release_signature",
|
"verify_installed_release_signature",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.0.0b1+1016"
|
__version__ = "1.0.1b1+1016"
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-official-extensions"
|
name = "jwebot-official-extensions"
|
||||||
version = "1.0.0b1+1016"
|
version = "1.0.1b1+1016"
|
||||||
description = "Reviewed local command adapters and broker implementation for JWeBot"
|
description = "Reviewed local command adapters and broker implementation for JWeBot"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jwebot-sdk==1.0.0b1+1016",
|
"jwebot-sdk==1.0.1b1+1016",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
|
|||||||
@@ -90,4 +90,4 @@ __all__ = [
|
|||||||
"render_feature_list",
|
"render_feature_list",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.0.0b1+1016"
|
__version__ = "1.0.1b1+1016"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-sdk"
|
name = "jwebot-sdk"
|
||||||
version = "1.0.0b1+1016"
|
version = "1.0.1b1+1016"
|
||||||
description = "Typed, capability-limited extension API for JWeBot plugins"
|
description = "Typed, capability-limited extension API for JWeBot plugins"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|||||||
@@ -38,4 +38,4 @@ __all__ = [
|
|||||||
"SingleGroupContext",
|
"SingleGroupContext",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.0.0b1+1016"
|
__version__ = "1.0.1b1+1016"
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[tool.jwebot]
|
[tool.jwebot]
|
||||||
release = "beta 1.0.0.1016"
|
release = "beta 1.0.1.1016"
|
||||||
production_deployments_enabled_by_default = false
|
production_deployments_enabled_by_default = false
|
||||||
|
|
||||||
[tool.jwebot.workspace]
|
[tool.jwebot.workspace]
|
||||||
|
|||||||
@@ -867,9 +867,9 @@ class LedgerAndApplicationTests(unittest.TestCase):
|
|||||||
self.assertEqual(version["route"], "local")
|
self.assertEqual(version["route"], "local")
|
||||||
self.assertIn("JWeBot 版本", version["text"])
|
self.assertIn("JWeBot 版本", version["text"])
|
||||||
self.assertIn("kernel", 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("更新时间:2026-10-16", version["text"])
|
||||||
self.assertIn("更新摘要:Promote JWeBot", version["text"])
|
self.assertIn("更新摘要:Limit RapidOCR", version["text"])
|
||||||
self.assertEqual(debug["route"], "local")
|
self.assertEqual(debug["route"], "local")
|
||||||
self.assertIn("调试埋点", debug["text"])
|
self.assertIn("调试埋点", debug["text"])
|
||||||
self.assertEqual(balance["route"], "local")
|
self.assertEqual(balance["route"], "local")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import importlib.util
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import types
|
||||||
import unittest
|
import unittest
|
||||||
from contextlib import nullcontext
|
from contextlib import nullcontext
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
@@ -517,6 +518,56 @@ class GuiCoordinationTests(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class OcrFallbackTests(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):
|
def test_low_confidence_line_uses_detection_fallback(self):
|
||||||
engine = MODULE.OcrEngine.__new__(MODULE.OcrEngine)
|
engine = MODULE.OcrEngine.__new__(MODULE.OcrEngine)
|
||||||
bubble = MODULE.Bubble(50, 50, 132, 68, 5000)
|
bubble = MODULE.Bubble(50, 50, 132, 68, 5000)
|
||||||
|
|||||||
+25
-1
@@ -67,6 +67,8 @@ DAMAGE_HEARTBEAT_SECONDS = 2.0
|
|||||||
CAPTURE_BUFFER_FRAMES = 12
|
CAPTURE_BUFFER_FRAMES = 12
|
||||||
OCR_MESSAGE_CACHE_SIZE = 256
|
OCR_MESSAGE_CACHE_SIZE = 256
|
||||||
OCR_CACHE_REFRESH_SCANS = 120
|
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
|
WINDOW_LAYOUT_SETTLE_SECONDS = 0.25
|
||||||
FOLLOW_LATEST_SETTLE_SECONDS = 0.20
|
FOLLOW_LATEST_SETTLE_SECONDS = 0.20
|
||||||
FOLLOW_LATEST_MOUSE_X = 1432
|
FOLLOW_LATEST_MOUSE_X = 1432
|
||||||
@@ -224,6 +226,17 @@ def normalize_text(value: str) -> str:
|
|||||||
return re.sub(r"\s+", " ", value).strip()
|
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(
|
def bubble_sequence_signature(
|
||||||
sender_crop: Image.Image,
|
sender_crop: Image.Image,
|
||||||
body_crop: Image.Image,
|
body_crop: Image.Image,
|
||||||
@@ -1099,7 +1112,18 @@ class OcrEngine:
|
|||||||
from rapidocr import RapidOCR
|
from rapidocr import RapidOCR
|
||||||
|
|
||||||
self._numpy = np
|
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[
|
self._message_cache: OrderedDict[
|
||||||
str,
|
str,
|
||||||
tuple[str, str, float, float, float],
|
tuple[str, str, float, float, float],
|
||||||
|
|||||||
Reference in New Issue
Block a user