Release beta 1.0.3 Mora wallet updates

This commit is contained in:
JWeBot Contributors
2026-10-16 15:44:34 +08:00
parent 22edf6ff15
commit bf6b738baa
50 changed files with 551 additions and 213 deletions
+4 -4
View File
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
[project]
name = "jwebot-kernel"
version = "1.0.2b1+1016"
version = "1.0.3b1+1016"
description = "Stable routing and extension authority for JWeBot"
requires-python = ">=3.9"
dependencies = [
"jwebot-config==1.0.2b1+1016",
"jwebot-sdk==1.0.2b1+1016",
"jwebot-official-extensions==1.0.2b1+1016",
"jwebot-config==1.0.3b1+1016",
"jwebot-sdk==1.0.3b1+1016",
"jwebot-official-extensions==1.0.3b1+1016",
"tomli>=2; python_version < '3.11'",
]
@@ -107,4 +107,4 @@ __all__ = [
"verify_installed_release_signature",
]
__version__ = "1.0.2b1+1016"
__version__ = "1.0.3b1+1016"
@@ -71,6 +71,8 @@ from jwebot_official_extensions.steam import (
SteamUnavailable,
)
from jwebot_official_extensions.player_games import (
append_coin_event,
append_game_coin_event,
append_game_record,
hand_value,
next_game_index,
@@ -98,6 +100,7 @@ EXTENSION_SOCKET_ROOT = Path("/run/jwebot-extension-host")
MAX_EVENT_TEXT = 5_000
MAX_REPLY_CHARS = 900
MAX_CONTEXT_TURNS = 6
OCR_EVENT_SOURCES = frozenset({"ocr", "rapidocr-local"})
MAX_OUTBOX_ATTEMPTS = 3
DIRECT_CHAT_GENERATION_WORKERS = 2
DIRECT_API_SOCKET_TIMEOUT_SECONDS = 18.0
@@ -354,6 +357,11 @@ def event_question(event: dict[str, Any]) -> str:
return bounded_text(match.group(1) if match else text, limit=MAX_EVENT_TEXT)
def is_ocr_event(event: dict[str, Any]) -> bool:
source = str(event.get("source", "")).casefold()
return source in OCR_EVENT_SOURCES or source.startswith("rapidocr-")
def classify_question(question: str) -> tuple[str, str]:
decision = route_question(question)
return decision.route.value, decision.reason
@@ -1293,6 +1301,43 @@ class Ledger:
raise EdgeError("ledger write did not persist")
return result, cursor.rowcount == 1
def recent_semantic_duplicate(
self,
*,
group_fingerprint: str,
sender: str,
question: str,
route: str,
route_reason: str,
within_seconds: float = 8.0,
) -> dict[str, Any] | None:
"""Find OCR re-ingestion of the same visible message under a new event id."""
group = bounded_text(group_fingerprint, limit=256)
actor = bounded_text(sender, limit=256) or "unknown"
content = bounded_text(question, limit=2_000)
if not group or not actor or not content:
return None
cutoff = (
datetime.now(timezone.utc) - timedelta(seconds=within_seconds)
).isoformat(timespec="milliseconds")
with self.lock:
row = self.connection.execute(
"""
SELECT *
FROM events
WHERE group_fingerprint = ?
AND sender = ?
AND question = ?
AND route = ?
AND route_reason = ?
AND created_at >= ?
ORDER BY created_at DESC, event_id DESC
LIMIT 1
""",
(group, actor, content, route, route_reason, cutoff),
).fetchone()
return dict(row) if row else None
def set_event(
self,
event_id: str,
@@ -3140,7 +3185,7 @@ class Ledger:
def _player_identity(self, group: str, sender: str) -> dict[str, str]:
if not group or not sender or sender == "unknown":
raise LocalCommandError("无法确认本人身份,不能操作金币账户")
raise LocalCommandError("无法确认本人身份,不能操作摩拉账户")
person = self.resolve_person(group, sender)
person["display_name"] = person_display_label(person["display_name"])
return person
@@ -3178,13 +3223,15 @@ class Ledger:
(group, person_key),
).fetchone()
if stored is None:
return normalize_player_account({})
account = normalize_player_account({})
append_coin_event(account, int(account["balance"]), "首次初始化")
return account
try:
account = json.loads(str(stored["state_json"]))
except json.JSONDecodeError as error:
raise LocalCommandError("金币账户状态无效") from error
raise LocalCommandError("摩拉账户状态无效") from error
if not isinstance(account, dict):
raise LocalCommandError("金币账户状态无效")
raise LocalCommandError("摩拉账户状态无效")
return normalize_player_account(account)
def _save_player_account(
@@ -3199,7 +3246,7 @@ class Ledger:
type(account["balance"]) is not int
or not 0 <= account["balance"] <= 1_000_000_000
):
raise LocalCommandError("金币结算超出允许范围")
raise LocalCommandError("摩拉结算超出允许范围")
self.connection.execute(
"""
INSERT INTO player_accounts VALUES (?, ?, ?, ?)
@@ -3555,6 +3602,13 @@ class Ledger:
+ " ".join(map(str, drawn_numbers))
),
)
append_game_coin_event(
account,
kind="lottery",
outcome=f"命中{matches}个",
stake=stake,
payout=payout,
)
self._save_player_account(
group, key, account, timestamp
)
@@ -3661,6 +3715,13 @@ class Ledger:
payout=stake,
summary="主持人取消,下注已全额退回",
)
append_game_coin_event(
account,
kind="lottery",
outcome="本轮取消",
stake=stake,
payout=stake,
)
self._save_player_account(
group, key, account, timestamp
)
@@ -3827,6 +3888,13 @@ class Ledger:
f"玩家{player_value}点,Taffy{dealer_value}点"
),
)
append_game_coin_event(
account,
kind="blackjack",
outcome=outcome,
stake=bet,
payout=payout,
)
self._save_player_account(group, key, account, timestamp)
self.connection.execute(
"""
@@ -4830,9 +4898,9 @@ class Ledger:
try:
account = json.loads(str(row["state_json"]))
except json.JSONDecodeError as error:
raise LocalCommandError("金币账户状态无效") from error
raise LocalCommandError("摩拉账户状态无效") from error
if not isinstance(account, dict):
raise LocalCommandError("金币账户状态无效")
raise LocalCommandError("摩拉账户状态无效")
normalize_player_account(account)
loan = account.get("loan")
loan_principal = (
@@ -4939,9 +5007,9 @@ class Ledger:
try:
account = json.loads(str(row["state_json"]))
except json.JSONDecodeError as error:
raise LocalCommandError("金币账户状态无效") from error
raise LocalCommandError("摩拉账户状态无效") from error
if not isinstance(account, dict):
raise LocalCommandError("金币账户状态无效")
raise LocalCommandError("摩拉账户状态无效")
normalize_player_account(account)
stake = 0
payout = 0
@@ -5052,6 +5120,16 @@ class Ledger:
)
source_account["balance"] -= amount
target_account["balance"] += amount
append_coin_event(
source_account,
-amount,
f"转账给 {target_display_name}",
)
append_coin_event(
target_account,
amount,
f"转账 来自 {source['display_name']}",
)
timestamp = utc_now()
self._save_player_account(
group, source_key, source_account, timestamp
@@ -6502,6 +6580,34 @@ class EdgeApplication:
reason=reason,
execution_path=execution_path,
)
existing_row = self.ledger.row(event_id) if event_id else None
semantic_duplicate = None
if existing_row is None and is_ocr_event(event):
semantic_duplicate = self.ledger.recent_semantic_duplicate(
group_fingerprint=group_id,
sender=str(event.get("sender", "")),
question=question,
route=route,
route_reason=reason,
)
if semantic_duplicate is not None:
self.telemetry.elapsed(
"edge.event.accepted",
started,
status="semantic_duplicate",
event_id=event_id,
group_id=group_id,
route=route,
execution_path=execution_path,
duplicate=True,
)
return {
"status": "ignored",
"route": route,
"execution_path": execution_path,
"duplicate": True,
"duplicate_event_id": str(semantic_duplicate["event_id"]),
}
row, created = self.ledger.record_event(
event,
question=question,