@@ -1,5 +1,18 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## beta 1.2.2.1019 - 2026-10-19 17:31:56
|
||||||
|
|
||||||
|
- 修复本地命令后的闲聊断上下文,新增 `/知识问答` 强制开题,并让多条回复按 0.5~1 秒随机间隔逐条发送。
|
||||||
|
- Include non-tool local command results in the follow-up chat context so game
|
||||||
|
and account actions like `/原石祈愿` can be referenced naturally afterward.
|
||||||
|
- Exclude utility local commands such as `/version`, `/状态`, `/debug`, and
|
||||||
|
feature-list output from chat context to avoid noisy system details.
|
||||||
|
- Add randomized spacing between queued multi-part replies and immediate local
|
||||||
|
multi-reply sends.
|
||||||
|
- Add `/知识问答` as a Kernel-owned local command that starts a cold-knowledge
|
||||||
|
quiz immediately while reusing the active five-minute question when one is
|
||||||
|
already running.
|
||||||
|
|
||||||
## beta 1.2.1.1019 - 2026-10-19 17:23:58
|
## beta 1.2.1.1019 - 2026-10-19 17:23:58
|
||||||
|
|
||||||
- 修复 Taffy 短句拆分只换行不分条的问题:多句闲聊现在会按多条消息逐条发送。
|
- 修复 Taffy 短句拆分只换行不分条的问题:多句闲聊现在会按多条消息逐条发送。
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ JWeBot 是运行于 Linux 的单群微信自动回复系统。它通过本地 OC
|
|||||||
观察选定的微信窗口,将消息写入持久化 Edge 账本,调用经过审核的命令插件,
|
观察选定的微信窗口,将消息写入持久化 Edge 账本,调用经过审核的命令插件,
|
||||||
并通过带视觉确认的 GUI 发送链路回复。
|
并通过带视觉确认的 GUI 发送链路回复。
|
||||||
|
|
||||||
当前版本:`beta 1.2.1.1019`,更新时间:2026-10-19 17:23:58。
|
当前版本:`beta 1.2.2.1019`,更新时间:2026-10-19 17:31:56。
|
||||||
|
|
||||||
1.0 Beta 收口了当前稳定功能:本地命令插件、人物与上下文索引、
|
1.0 Beta 收口了当前稳定功能:本地命令插件、人物与上下文索引、
|
||||||
Steam 状态与时长查询、虚拟摩拉游戏、多人彩票、21 点、CS Major
|
Steam 状态与时长查询、虚拟摩拉游戏、多人彩票、21 点、CS Major
|
||||||
@@ -90,6 +90,7 @@ PATH="$PWD/.venv/bin:$PATH" ./tools/check-local.sh
|
|||||||
/21点 开始 [1~500];开局后 /21点 要牌|停牌|状态|结束
|
/21点 开始 [1~500];开局后 /21点 要牌|停牌|状态|结束
|
||||||
/21点 多人 启动;/21点 加入;/21点 多人 开始;/21点 放200
|
/21点 多人 启动;/21点 加入;/21点 多人 开始;/21点 放200
|
||||||
/摩拉(兼 /原石);/原石祈愿;/原石榜;/扫码加权 人名(24小时+5);/Taffy发话 内容
|
/摩拉(兼 /原石);/原石祈愿;/原石榜;/扫码加权 人名(24小时+5);/Taffy发话 内容
|
||||||
|
/知识问答;/回答 A
|
||||||
/游戏记录 [页码](/gamehistory)
|
/游戏记录 [页码](/gamehistory)
|
||||||
/游戏记录 [页码](/gamehistory)
|
/游戏记录 [页码](/gamehistory)
|
||||||
/游戏帮助(/games)
|
/游戏帮助(/games)
|
||||||
@@ -269,10 +270,10 @@ visually confirmed GUI bridge.
|
|||||||
Current version:
|
Current version:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
beta 1.2.1.1019
|
beta 1.2.2.1019
|
||||||
```
|
```
|
||||||
|
|
||||||
Updated: 2026-10-19 17:23:58.
|
Updated: 2026-10-19 17:31:56.
|
||||||
|
|
||||||
The 1.0 Beta baseline consolidates the stable local command set, person and
|
The 1.0 Beta baseline consolidates the stable local command set, person and
|
||||||
context indexes, Steam presence/playtime queries, virtual coin games,
|
context indexes, Steam presence/playtime queries, virtual coin games,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# JWeBot Command Catalog
|
# JWeBot Command Catalog
|
||||||
|
|
||||||
Current baseline: `beta 1.1.2.1019`.
|
Current baseline: `beta 1.2.2.1019`.
|
||||||
|
|
||||||
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
|
||||||
@@ -51,6 +51,7 @@ descriptor inventory; plugin code delegates to reviewed service profiles.
|
|||||||
| `/扫码加权 人名` | `/扫码概率 人名` | Spend a scan-boost ticket or 1 primogem to give that person +5 hourly scan weight for 24 hours. |
|
| `/扫码加权 人名` | `/扫码概率 人名` | Spend a scan-boost ticket or 1 primogem to give that person +5 hourly scan weight for 24 hours. |
|
||||||
| `/Taffy发话 内容` | `/taffysay content` | Spend Taffy speech credit; 1 primogem converts to 2 credits when needed. |
|
| `/Taffy发话 内容` | `/taffysay content` | Spend Taffy speech credit; 1 primogem converts to 2 credits when needed. |
|
||||||
| `/Taffy发话 定时 HH:MM 内容` | - | Schedule a Taffy speech through the guarded clock delivery queue. |
|
| `/Taffy发话 定时 HH:MM 内容` | - | Schedule a Taffy speech through the guarded clock delivery queue. |
|
||||||
|
| `/知识问答` | - | Start a five-minute cold-knowledge quiz immediately, unless one is already active. |
|
||||||
| `/回答 A` | `/answer A` | Answer the active five-minute cold-knowledge quiz for a rarity-based Mora reward. |
|
| `/回答 A` | `/answer A` | Answer the active five-minute cold-knowledge quiz for a rarity-based Mora reward. |
|
||||||
| `/抽奖 [金额]` | `/draw` | Play a single-player prize draw. |
|
| `/抽奖 [金额]` | `/draw` | Play a single-player prize draw. |
|
||||||
| `/彩票 开始` | `/lottery start` | Open a multiplayer lottery round. |
|
| `/彩票 开始` | `/lottery start` | Open a multiplayer lottery round. |
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-config"
|
name = "jwebot-config"
|
||||||
version = "1.2.1b1+1019"
|
version = "1.2.2b1+1019"
|
||||||
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.2.1b1+1019"
|
__version__ = "1.2.2b1+1019"
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-extension-host"
|
name = "jwebot-extension-host"
|
||||||
version = "1.2.1b1+1019"
|
version = "1.2.2b1+1019"
|
||||||
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.2.1b1+1019",
|
"jwebot-sdk==1.2.2b1+1019",
|
||||||
"jwebot-kernel==1.2.1b1+1019",
|
"jwebot-kernel==1.2.2b1+1019",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ __all__ = [
|
|||||||
"process_request",
|
"process_request",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.2.1b1+1019"
|
__version__ = "1.2.2b1+1019"
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-kernel"
|
name = "jwebot-kernel"
|
||||||
version = "1.2.1b1+1019"
|
version = "1.2.2b1+1019"
|
||||||
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.2.1b1+1019",
|
"jwebot-config==1.2.2b1+1019",
|
||||||
"jwebot-sdk==1.2.1b1+1019",
|
"jwebot-sdk==1.2.2b1+1019",
|
||||||
"jwebot-official-extensions==1.2.1b1+1019",
|
"jwebot-official-extensions==1.2.2b1+1019",
|
||||||
"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.2.1b1+1019"
|
__version__ = "1.2.2b1+1019"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import hashlib
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import signal
|
import signal
|
||||||
@@ -104,6 +105,8 @@ EXTENSION_SOCKET_ROOT = Path("/run/jwebot-extension-host")
|
|||||||
MAX_EVENT_TEXT = 5_000
|
MAX_EVENT_TEXT = 5_000
|
||||||
MAX_REPLY_CHARS = 900
|
MAX_REPLY_CHARS = 900
|
||||||
MAX_CONTEXT_TURNS = 6
|
MAX_CONTEXT_TURNS = 6
|
||||||
|
MULTI_REPLY_DELAY_MIN_SECONDS = 0.5
|
||||||
|
MULTI_REPLY_DELAY_MAX_SECONDS = 1.0
|
||||||
OCR_EVENT_SOURCES = frozenset({"ocr", "rapidocr-local"})
|
OCR_EVENT_SOURCES = frozenset({"ocr", "rapidocr-local"})
|
||||||
OCR_SEMANTIC_DUPLICATE_SECONDS = 8.0
|
OCR_SEMANTIC_DUPLICATE_SECONDS = 8.0
|
||||||
OCR_RESOURCE_MUTATION_DUPLICATE_SECONDS = 75.0
|
OCR_RESOURCE_MUTATION_DUPLICATE_SECONDS = 75.0
|
||||||
@@ -117,6 +120,24 @@ OCR_RESOURCE_MUTATION_REASONS = frozenset(
|
|||||||
"local_transfer",
|
"local_transfer",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
LOCAL_CONTEXT_EXCLUDED_REASONS = frozenset(
|
||||||
|
{
|
||||||
|
"local_feature_list",
|
||||||
|
"local_context_summary",
|
||||||
|
"local_person_directory",
|
||||||
|
"local_person_info",
|
||||||
|
"local_person_memory",
|
||||||
|
"local_my_memory",
|
||||||
|
"local_forget_memory",
|
||||||
|
"local_memory_help",
|
||||||
|
"local_local_status",
|
||||||
|
"local_server_status",
|
||||||
|
"local_jwebot_version",
|
||||||
|
"local_telemetry_debug",
|
||||||
|
"local_deepseek_balance",
|
||||||
|
"local_unknown",
|
||||||
|
}
|
||||||
|
)
|
||||||
MAX_OUTBOX_ATTEMPTS = 3
|
MAX_OUTBOX_ATTEMPTS = 3
|
||||||
DIRECT_CHAT_GENERATION_WORKERS = 2
|
DIRECT_CHAT_GENERATION_WORKERS = 2
|
||||||
DIRECT_API_SOCKET_TIMEOUT_SECONDS = 18.0
|
DIRECT_API_SOCKET_TIMEOUT_SECONDS = 18.0
|
||||||
@@ -1645,21 +1666,37 @@ class Ledger:
|
|||||||
with self.lock:
|
with self.lock:
|
||||||
rows = self.connection.execute(
|
rows = self.connection.execute(
|
||||||
"""
|
"""
|
||||||
SELECT question, response_text
|
SELECT question, response_text, route, route_reason
|
||||||
FROM events
|
FROM events
|
||||||
WHERE group_fingerprint = ?
|
WHERE group_fingerprint = ?
|
||||||
AND event_id != ?
|
AND event_id != ?
|
||||||
AND route = 'chat'
|
AND (
|
||||||
AND status IN ('chat_completed', 'chat_delivered')
|
(route = 'chat' AND status IN ('chat_completed', 'chat_delivered'))
|
||||||
|
OR (route = 'local' AND status = 'local_completed')
|
||||||
|
)
|
||||||
AND response_text != ''
|
AND response_text != ''
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
""",
|
""",
|
||||||
(group_fingerprint, exclude_event_id, limit),
|
(group_fingerprint, exclude_event_id, limit * 3),
|
||||||
).fetchall()
|
).fetchall()
|
||||||
result: list[tuple[str, str]] = []
|
result: list[tuple[str, str]] = []
|
||||||
for row in reversed(rows):
|
for row in reversed(rows):
|
||||||
result.append((str(row["question"]), str(row["response_text"])))
|
if (
|
||||||
|
row["route"] == "local"
|
||||||
|
and str(row["route_reason"]) in LOCAL_CONTEXT_EXCLUDED_REASONS
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
response_text = str(row["response_text"])
|
||||||
|
parts = unpack_reply_parts(response_text)
|
||||||
|
result.append(
|
||||||
|
(
|
||||||
|
str(row["question"]),
|
||||||
|
"\n".join(parts) if parts is not None else response_text,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if len(result) >= limit:
|
||||||
|
break
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def local_game(
|
def local_game(
|
||||||
@@ -6749,6 +6786,53 @@ class HourlyBroadcastEngine:
|
|||||||
lines.append("回答格式:/回答 A")
|
lines.append("回答格式:/回答 A")
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def start_trivia_question(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
group: str,
|
||||||
|
local_now: datetime,
|
||||||
|
) -> str:
|
||||||
|
question = self.select_trivia_question(group=group, local_now=local_now)
|
||||||
|
message = self.render_trivia_message(question)
|
||||||
|
expires_at = (
|
||||||
|
local_now + timedelta(minutes=TRIVIA_ANSWER_WINDOW_MINUTES)
|
||||||
|
).timestamp()
|
||||||
|
self.ledger.save_local_game(
|
||||||
|
group,
|
||||||
|
TRIVIA_GAME_KEY,
|
||||||
|
{
|
||||||
|
"question_id": str(question["id"]),
|
||||||
|
"base_id": str(question.get("base_id") or question["id"]),
|
||||||
|
"question": str(question["question"]),
|
||||||
|
"options": list(question["options"]),
|
||||||
|
"answer": str(question["answer"]),
|
||||||
|
"explanation": str(question["explanation"]),
|
||||||
|
"rarity": int(question.get("rarity", 3)),
|
||||||
|
"reward": int(question.get("reward", 50)),
|
||||||
|
"created_at": local_now.timestamp(),
|
||||||
|
"expires_at": expires_at,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.remember_trivia_question(group, question)
|
||||||
|
return message
|
||||||
|
|
||||||
|
def force_trivia_message(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
group: str,
|
||||||
|
now: datetime | None = None,
|
||||||
|
) -> str:
|
||||||
|
local_now = self.in_beijing_timezone(now)
|
||||||
|
active = self.ledger.local_game(group, TRIVIA_GAME_KEY)
|
||||||
|
if active:
|
||||||
|
try:
|
||||||
|
if float(active.get("expires_at", 0)) > local_now.timestamp():
|
||||||
|
return "已经有一题在进行中喵\n" + self.render_trivia_message(active)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return "冷门问答状态坏了喵"
|
||||||
|
self.ledger.clear_local_game(group, TRIVIA_GAME_KEY)
|
||||||
|
return self.start_trivia_question(group=group, local_now=local_now)
|
||||||
|
|
||||||
def due_trivia_broadcast(
|
def due_trivia_broadcast(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -6772,28 +6856,7 @@ class HourlyBroadcastEngine:
|
|||||||
seed = f"{group}|{local_date}|{local_now.hour:02d}|trivia-probability"
|
seed = f"{group}|{local_date}|{local_now.hour:02d}|trivia-probability"
|
||||||
if deterministic_int(seed, 0, 99) >= TRIVIA_QUIZ_PROBABILITY_PERCENT:
|
if deterministic_int(seed, 0, 99) >= TRIVIA_QUIZ_PROBABILITY_PERCENT:
|
||||||
return None
|
return None
|
||||||
question = self.select_trivia_question(group=group, local_now=local_now)
|
message = self.start_trivia_question(group=group, local_now=local_now)
|
||||||
message = self.render_trivia_message(question)
|
|
||||||
expires_at = (
|
|
||||||
local_now + timedelta(minutes=TRIVIA_ANSWER_WINDOW_MINUTES)
|
|
||||||
).timestamp()
|
|
||||||
self.ledger.save_local_game(
|
|
||||||
group,
|
|
||||||
TRIVIA_GAME_KEY,
|
|
||||||
{
|
|
||||||
"question_id": str(question["id"]),
|
|
||||||
"base_id": str(question.get("base_id") or question["id"]),
|
|
||||||
"question": str(question["question"]),
|
|
||||||
"options": list(question["options"]),
|
|
||||||
"answer": str(question["answer"]),
|
|
||||||
"explanation": str(question["explanation"]),
|
|
||||||
"rarity": int(question.get("rarity", 3)),
|
|
||||||
"reward": int(question.get("reward", 50)),
|
|
||||||
"created_at": local_now.timestamp(),
|
|
||||||
"expires_at": expires_at,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.remember_trivia_question(group, question)
|
|
||||||
return ClockBroadcast(
|
return ClockBroadcast(
|
||||||
group_fingerprint=group,
|
group_fingerprint=group,
|
||||||
slot_key=slot_key,
|
slot_key=slot_key,
|
||||||
@@ -8038,6 +8101,11 @@ class EdgeApplication:
|
|||||||
try:
|
try:
|
||||||
command_kind = local_command_kind(str(row["question"]))
|
command_kind = local_command_kind(str(row["question"]))
|
||||||
reply = (
|
reply = (
|
||||||
|
self.clock.force_trivia_message(
|
||||||
|
group=str(row["group_fingerprint"])
|
||||||
|
)
|
||||||
|
if command_kind == "trivia_start"
|
||||||
|
else
|
||||||
self._execute_local_extension(row, event)
|
self._execute_local_extension(row, event)
|
||||||
if (
|
if (
|
||||||
self.config.local_extensions_enabled
|
self.config.local_extensions_enabled
|
||||||
@@ -8705,6 +8773,13 @@ class EdgeApplication:
|
|||||||
if "missing expected group" in output:
|
if "missing expected group" in output:
|
||||||
raise EdgeError("missing expected group")
|
raise EdgeError("missing expected group")
|
||||||
raise EdgeError("delivery helper failed")
|
raise EdgeError("delivery helper failed")
|
||||||
|
if index < len(replies):
|
||||||
|
time.sleep(
|
||||||
|
random.uniform(
|
||||||
|
MULTI_REPLY_DELAY_MIN_SECONDS,
|
||||||
|
MULTI_REPLY_DELAY_MAX_SECONDS,
|
||||||
|
)
|
||||||
|
)
|
||||||
self.telemetry.elapsed(
|
self.telemetry.elapsed(
|
||||||
"edge.delivery.process",
|
"edge.delivery.process",
|
||||||
started,
|
started,
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-official-extensions"
|
name = "jwebot-official-extensions"
|
||||||
version = "1.2.1b1+1019"
|
version = "1.2.2b1+1019"
|
||||||
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.2.1b1+1019",
|
"jwebot-sdk==1.2.2b1+1019",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
|
|||||||
@@ -90,4 +90,4 @@ __all__ = [
|
|||||||
"render_feature_list",
|
"render_feature_list",
|
||||||
]
|
]
|
||||||
|
|
||||||
__version__ = "1.2.1b1+1019"
|
__version__ = "1.2.2b1+1019"
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ LOCAL_COMMAND_ALIASES = {
|
|||||||
"扫码概率": "scan_boost",
|
"扫码概率": "scan_boost",
|
||||||
"Taffy发话": "taffy_say",
|
"Taffy发话": "taffy_say",
|
||||||
"taffysay": "taffy_say",
|
"taffysay": "taffy_say",
|
||||||
|
"知识问答": "trivia_start",
|
||||||
"回答": "trivia_answer",
|
"回答": "trivia_answer",
|
||||||
"answer": "trivia_answer",
|
"answer": "trivia_answer",
|
||||||
"转账": "transfer",
|
"转账": "transfer",
|
||||||
@@ -151,6 +152,11 @@ DIRECT_KERNEL_COMMANDS = (
|
|||||||
help_entries=("/余额",),
|
help_entries=("/余额",),
|
||||||
command_kinds=("deepseek_balance",),
|
command_kinds=("deepseek_balance",),
|
||||||
),
|
),
|
||||||
|
KernelCommandDescriptor(
|
||||||
|
description="手动触发冷门知识问答",
|
||||||
|
help_entries=("/知识问答",),
|
||||||
|
command_kinds=("trivia_start",),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
DIRECT_KERNEL_COMMAND_KINDS = frozenset(
|
DIRECT_KERNEL_COMMAND_KINDS = frozenset(
|
||||||
kind
|
kind
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "jwebot-sdk"
|
name = "jwebot-sdk"
|
||||||
version = "1.2.1b1+1019"
|
version = "1.2.2b1+1019"
|
||||||
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.2.1b1+1019"
|
__version__ = "1.2.2b1+1019"
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[tool.jwebot]
|
[tool.jwebot]
|
||||||
release = "beta 1.2.1.1019"
|
release = "beta 1.2.2.1019"
|
||||||
production_deployments_enabled_by_default = false
|
production_deployments_enabled_by_default = false
|
||||||
|
|
||||||
[tool.jwebot.workspace]
|
[tool.jwebot.workspace]
|
||||||
|
|||||||
@@ -211,6 +211,10 @@ class RoutingTests(unittest.TestCase):
|
|||||||
MODULE.classify_question("/steam 在线"),
|
MODULE.classify_question("/steam 在线"),
|
||||||
("local", "local_steam_friends"),
|
("local", "local_steam_friends"),
|
||||||
)
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
MODULE.classify_question("/知识问答"),
|
||||||
|
("local", "local_trivia_start"),
|
||||||
|
)
|
||||||
|
|
||||||
def test_reply_has_one_suffix_within_delivery_limit(self):
|
def test_reply_has_one_suffix_within_delivery_limit(self):
|
||||||
self.assertEqual(MODULE.clean_reply("收到。"), "收到喵")
|
self.assertEqual(MODULE.clean_reply("收到。"), "收到喵")
|
||||||
@@ -308,6 +312,24 @@ class LedgerAndApplicationTests(unittest.TestCase):
|
|||||||
"第二条回复喵",
|
"第二条回复喵",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_local_game_command_result_is_available_to_followup_chat(self):
|
||||||
|
observed_context = []
|
||||||
|
|
||||||
|
def complete(**kwargs):
|
||||||
|
observed_context.extend(kwargs["context"])
|
||||||
|
return "我看到了喵"
|
||||||
|
|
||||||
|
self.application.deepseek.complete = complete
|
||||||
|
self.application.accept(event("version-context", "@JWeBot /version"))
|
||||||
|
local = self.application.accept(event("coin-context", "@JWeBot /翻硬币"))
|
||||||
|
self.application.accept(event("chat-context", "@JWeBot 刚才是什么结果"))
|
||||||
|
self.application.process_direct_chat_once()
|
||||||
|
|
||||||
|
self.assertIn(("/翻硬币", local["text"]), observed_context)
|
||||||
|
self.assertTrue(
|
||||||
|
all(question != "/version" for question, _reply in observed_context)
|
||||||
|
)
|
||||||
|
|
||||||
def test_route_and_ack_telemetry_is_privacy_safe(self):
|
def test_route_and_ack_telemetry_is_privacy_safe(self):
|
||||||
observed = []
|
observed = []
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
@@ -450,6 +472,12 @@ class LedgerAndApplicationTests(unittest.TestCase):
|
|||||||
os.environ,
|
os.environ,
|
||||||
{"JWEBOT_RUNTIME_PYTHON": str(runtime_python)},
|
{"JWEBOT_RUNTIME_PYTHON": str(runtime_python)},
|
||||||
), \
|
), \
|
||||||
|
mock.patch.object(
|
||||||
|
MODULE.random,
|
||||||
|
"uniform",
|
||||||
|
return_value=0.75,
|
||||||
|
) as uniform, \
|
||||||
|
mock.patch.object(MODULE.time, "sleep") as sleep, \
|
||||||
mock.patch.object(
|
mock.patch.object(
|
||||||
MODULE.subprocess,
|
MODULE.subprocess,
|
||||||
"run",
|
"run",
|
||||||
@@ -482,6 +510,11 @@ class LedgerAndApplicationTests(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
uniform.assert_called_once_with(
|
||||||
|
MODULE.MULTI_REPLY_DELAY_MIN_SECONDS,
|
||||||
|
MODULE.MULTI_REPLY_DELAY_MAX_SECONDS,
|
||||||
|
)
|
||||||
|
sleep.assert_called_once_with(0.75)
|
||||||
|
|
||||||
def test_two_direct_generations_run_in_parallel(self):
|
def test_two_direct_generations_run_in_parallel(self):
|
||||||
entered = []
|
entered = []
|
||||||
@@ -996,9 +1029,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.2.1.1019", version["text"])
|
self.assertIn("最新版本:beta 1.2.2.1019", version["text"])
|
||||||
self.assertIn("更新时间:2026-10-19 17:23:58", version["text"])
|
self.assertIn("更新时间:2026-10-19 17:31:56", version["text"])
|
||||||
self.assertIn("更新摘要:修复 Taffy 短句拆分只换行不分条", version["text"])
|
self.assertIn("更新摘要:修复本地命令后的闲聊断上下文", 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")
|
||||||
@@ -1342,6 +1375,33 @@ class LedgerAndApplicationTests(unittest.TestCase):
|
|||||||
account = ledger._load_player_account("group-a", str(person["person_key"]))
|
account = ledger._load_player_account("group-a", str(person["person_key"]))
|
||||||
self.assertEqual(account["balance"], 1000 + reward)
|
self.assertEqual(account["balance"], 1000 + reward)
|
||||||
|
|
||||||
|
def test_trivia_quiz_can_be_started_by_local_command(self):
|
||||||
|
self.application.deepseek.complete = mock.Mock(side_effect=AssertionError)
|
||||||
|
|
||||||
|
first = self.application.accept(
|
||||||
|
event("trivia-force", "@JWeBot /知识问答")
|
||||||
|
)
|
||||||
|
second = self.application.accept(
|
||||||
|
event("trivia-force-existing", "@JWeBot /知识问答")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(first["route"], "local")
|
||||||
|
self.assertEqual(first["execution_path"], "local-command")
|
||||||
|
self.assertIn("冷门知识问答", first["text"])
|
||||||
|
self.assertIn("回答格式:/回答 A", first["text"])
|
||||||
|
self.assertIn("已经有一题在进行中", second["text"])
|
||||||
|
self.assertEqual(
|
||||||
|
self.application.ledger.row("trivia-force")["status"],
|
||||||
|
"local_completed",
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(
|
||||||
|
self.application.ledger.local_game(
|
||||||
|
"group-a",
|
||||||
|
MODULE.TRIVIA_GAME_KEY,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.application.deepseek.complete.assert_not_called()
|
||||||
|
|
||||||
def test_ambient_chat_cooldown_keeps_next_hour_scan_broadcast(self):
|
def test_ambient_chat_cooldown_keeps_next_hour_scan_broadcast(self):
|
||||||
ledger = self.application.ledger
|
ledger = self.application.ledger
|
||||||
observed_at = MODULE.datetime(
|
observed_at = MODULE.datetime(
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ class LocalExtensionInventoryTests(unittest.TestCase):
|
|||||||
{
|
{
|
||||||
"feature_list",
|
"feature_list",
|
||||||
"deepseek_balance",
|
"deepseek_balance",
|
||||||
|
"trivia_start",
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ SPEC.loader.exec_module(MODULE)
|
|||||||
|
|
||||||
|
|
||||||
class ReleaseUpdateTests(unittest.TestCase):
|
class ReleaseUpdateTests(unittest.TestCase):
|
||||||
release = "beta 1.2.1.1019"
|
release = "beta 1.2.2.1019"
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.temporary = tempfile.TemporaryDirectory()
|
self.temporary = tempfile.TemporaryDirectory()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import hashlib
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import queue
|
import queue
|
||||||
|
import random
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
@@ -43,6 +44,8 @@ GUI_SENDER_TIMEOUT_SECONDS = float(
|
|||||||
os.environ.get("JWEBOT_GUI_SENDER_TIMEOUT_SECONDS", "55")
|
os.environ.get("JWEBOT_GUI_SENDER_TIMEOUT_SECONDS", "55")
|
||||||
)
|
)
|
||||||
GUI_SENDER_MAX_PAYLOAD_BYTES = 65536
|
GUI_SENDER_MAX_PAYLOAD_BYTES = 65536
|
||||||
|
MULTI_REPLY_DELAY_MIN_SECONDS = 0.5
|
||||||
|
MULTI_REPLY_DELAY_MAX_SECONDS = 1.0
|
||||||
|
|
||||||
BASE_WIDTH = 1440
|
BASE_WIDTH = 1440
|
||||||
BASE_HEIGHT = 873
|
BASE_HEIGHT = 873
|
||||||
@@ -1052,6 +1055,13 @@ def run() -> None:
|
|||||||
event_id=event_id,
|
event_id=event_id,
|
||||||
source=f"local-multi-{index}",
|
source=f"local-multi-{index}",
|
||||||
)
|
)
|
||||||
|
if index < len(texts):
|
||||||
|
time.sleep(
|
||||||
|
random.uniform(
|
||||||
|
MULTI_REPLY_DELAY_MIN_SECONDS,
|
||||||
|
MULTI_REPLY_DELAY_MAX_SECONDS,
|
||||||
|
)
|
||||||
|
)
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"multi reply GUI delivery failed event_hash=%s error_type=%s",
|
"multi reply GUI delivery failed event_hash=%s error_type=%s",
|
||||||
|
|||||||
Reference in New Issue
Block a user