Release beta 2.0.2.1021
check / local-gate (push) Failing after 1m56s

This commit is contained in:
JWeBot Contributors
2026-10-21 18:17:12 +08:00
parent 645a282b22
commit 22f5240fa7
17 changed files with 720 additions and 28 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "jwebot-config"
version = "2.0.1b1+1021"
version = "2.0.2b1+1021"
description = "Profile-based configuration compiler for JWeBot"
requires-python = ">=3.9"
dependencies = [
@@ -36,4 +36,4 @@ __all__ = [
"write_default_profile",
]
__version__ = "2.0.1b1+1021"
__version__ = "2.0.2b1+1021"
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
[project]
name = "jwebot-extension-host"
version = "2.0.1b1+1021"
version = "2.0.2b1+1021"
description = "Single-release, single-invocation process host for JWeBot extensions"
requires-python = ">=3.9"
dependencies = [
"jwebot-sdk==2.0.1b1+1021",
"jwebot-kernel==2.0.1b1+1021",
"jwebot-sdk==2.0.2b1+1021",
"jwebot-kernel==2.0.2b1+1021",
]
[project.scripts]
@@ -16,4 +16,4 @@ __all__ = [
"process_request",
]
__version__ = "2.0.1b1+1021"
__version__ = "2.0.2b1+1021"
+4 -4
View File
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
[project]
name = "jwebot-kernel"
version = "2.0.1b1+1021"
version = "2.0.2b1+1021"
description = "Stable routing and extension authority for JWeBot"
requires-python = ">=3.9"
dependencies = [
"jwebot-config==2.0.1b1+1021",
"jwebot-sdk==2.0.1b1+1021",
"jwebot-official-extensions==2.0.1b1+1021",
"jwebot-config==2.0.2b1+1021",
"jwebot-sdk==2.0.2b1+1021",
"jwebot-official-extensions==2.0.2b1+1021",
"tomli>=2; python_version < '3.11'",
]
@@ -107,4 +107,4 @@ __all__ = [
"verify_installed_release_signature",
]
__version__ = "2.0.1b1+1021"
__version__ = "2.0.2b1+1021"
@@ -77,6 +77,7 @@ from jwebot_official_extensions.player_games import (
append_game_coin_event,
append_game_record,
blackjack_achievement_lines,
card_label,
hand_value,
lottery_achievement_lines,
next_game_index,
@@ -108,6 +109,8 @@ MAX_CONTEXT_TURNS = 40
CONTEXT_CANDIDATE_LIMIT = 240
CONTEXT_RECENT_FALLBACK_TURNS = 10
CONTEXT_TRUSTED_SENDER_CONFIDENCE = 0.80
ACTIVE_THREAD_TTL_SECONDS = 10 * 60
ACTIVE_THREAD_SHORT_LIMIT = 24
MULTI_REPLY_DELAY_MIN_SECONDS = 0.5
MULTI_REPLY_DELAY_MAX_SECONDS = 1.0
OCR_EVENT_SOURCES = frozenset({"ocr", "rapidocr-local"})
@@ -494,6 +497,25 @@ CONTEXT_SELF_REFERENCE = re.compile(
r"我那(?:把|局|个)|我这(?:把|局|个))",
re.IGNORECASE,
)
ACTIVE_THREAD_REPLY_HINT = re.compile(
r"(?:还真|真是|确实|也是|那确实|这么|这也|也太|有点|太|胆子|敢|"
r"离谱|逆天|抽象|难评|牛|绷|笑|哈哈|可以|行吧|草|艹|绝了|夸张)",
re.IGNORECASE,
)
LOCAL_PLAY_STATUS_QUESTION = re.compile(
r"^\s*(?P<subject>[\w\u4e00-\u9fff.-]{1,32})\s*"
r"(?:(?:现在|当前|刚才|刚刚|还)?(?:在|正?在)?)?\s*"
r"(?P<ask>玩(?:什么|啥|哪(?:个|款)?(?:游戏)?)?|"
r"什么游戏|啥游戏|游戏状态|状态|咋样|怎么样|啥情况)"
r"(?:吗|呢|了|呀|啊|?|\?)?\s*$",
re.IGNORECASE,
)
LOCAL_PLAY_LIST_QUESTION = re.compile(
r"^\s*(?:谁|哪个|哪些人|有人)\s*(?:在|正?在)?\s*"
r"(?:玩|打|进行)\s*(?:什么|啥|游戏|21点|黑杰克|本地游戏)?"
r"(?:吗|呢|呀|啊|?|\?)?\s*$",
re.IGNORECASE,
)
def apply_private_context(value: Mapping[str, Any]) -> None:
@@ -812,6 +834,60 @@ def context_terms(value: object) -> set[str]:
return profile_fact_terms(text)
def short_thread_reply(value: object) -> bool:
text = normalize_text(value)
if not text or local_command_kind(text):
return False
compact = re.sub(r"[\s,,。.!!??~~…]+", "", text)
return (
1 <= len(compact) <= ACTIVE_THREAD_SHORT_LIMIT
and bool(ACTIVE_THREAD_REPLY_HINT.search(text))
)
def blackjack_hand_label(cards: object) -> str:
if not isinstance(cards, list):
return "未知手牌"
normalized: list[int] = []
for card in cards:
try:
normalized.append(int(card))
except (TypeError, ValueError):
return "未知手牌"
if not normalized:
return "未发牌"
labels = " ".join(card_label(card) for card in normalized)
return f"{labels}({hand_value(normalized)}点)"
def visible_reply_text(value: object) -> str:
text = str(value or "")
parts = unpack_reply_parts(text)
return "\n".join(parts) if parts is not None else text
def thread_topic_label(route: str, reason: str) -> str:
if "blackjack" in reason:
return "21点"
if "trivia" in reason:
return "冷门问答"
if "dice" in reason:
return "骰子"
if "coin" in reason:
return "硬币"
if "lottery" in reason:
return "彩票"
if "draw" in reason:
return "抽奖"
if "steam" in reason:
return "Steam 状态"
if route == "broadcast":
return "Taffy 主动发言"
if route == "chat":
return "群聊"
return reason.removeprefix("local_").replace("_", " ") or route
def automatic_facts_are_near_duplicates(
left: dict[str, Any],
right: dict[str, Any],
@@ -1335,6 +1411,20 @@ class Ledger:
updated_at TEXT NOT NULL,
PRIMARY KEY (group_fingerprint, game_key)
);
CREATE TABLE IF NOT EXISTS active_threads (
group_fingerprint TEXT NOT NULL,
thread_key TEXT NOT NULL,
topic TEXT NOT NULL,
actor_person_key TEXT NOT NULL DEFAULT '',
actor_display_name TEXT NOT NULL DEFAULT '',
source_event_id TEXT NOT NULL DEFAULT '',
source_kind TEXT NOT NULL DEFAULT '',
summary TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
PRIMARY KEY (group_fingerprint, thread_key)
);
CREATE TABLE IF NOT EXISTS person_observations (
event_id TEXT PRIMARY KEY,
group_fingerprint TEXT NOT NULL,
@@ -1599,6 +1689,8 @@ class Ledger:
ON outbox(state, next_attempt_at);
CREATE INDEX IF NOT EXISTS direct_chat_outbox_due
ON direct_chat_outbox(state, next_attempt_at);
CREATE INDEX IF NOT EXISTS active_threads_group_expiry
ON active_threads(group_fingerprint, expires_at);
CREATE INDEX IF NOT EXISTS observations_scope_recent
ON person_observations(group_fingerprint, person_key, observed_at);
CREATE INDEX IF NOT EXISTS aliases_scope_match
@@ -1842,8 +1934,7 @@ class Ledger:
return cursor.rowcount == 1
def _record_taffy_context(self, event_id: str, response_text: str) -> None:
parts = unpack_reply_parts(response_text)
visible_text = "\n".join(parts) if parts is not None else response_text
visible_text = visible_reply_text(response_text)
if not visible_text:
return
with self.lock:
@@ -1869,6 +1960,46 @@ class Ledger:
replace=True,
)
def _activate_thread_from_event(self, event_id: str, response_text: str) -> None:
summary = bounded_text(visible_reply_text(response_text), limit=700)
if not summary or summary == FAST_ACKNOWLEDGEMENT:
return
with self.lock:
row = self.connection.execute(
"""
SELECT group_fingerprint, sender, sender_confidence, route,
route_reason, status
FROM events
WHERE event_id = ?
""",
(event_id,),
).fetchone()
if row is None:
return
reason = str(row["route_reason"])
if str(row["route"]) == "local" and reason in LOCAL_CONTEXT_EXCLUDED_REASONS:
return
actor_person_key = ""
actor_display_name = ""
sender = str(row["sender"])
sender_confidence = confidence_value(row["sender_confidence"], default=1.0)
group = str(row["group_fingerprint"])
if trusted_sender_value(sender, sender_confidence):
person = self.resolve_person(group, sender)
actor_person_key = str(person["person_key"])
actor_display_name = person_display_label(person["display_name"])
elif sender and sender != "unknown":
actor_display_name = bounded_text(sender, limit=120)
self.save_active_thread(
group_fingerprint=group,
topic=thread_topic_label(str(row["route"]), reason),
summary=summary,
actor_person_key=actor_person_key,
actor_display_name=actor_display_name,
source_event_id=event_id,
source_kind=f"{row['route']}:{reason}",
)
def set_event(
self,
event_id: str,
@@ -1893,6 +2024,7 @@ class Ledger:
)
if response_text is not None and status in TRACKED_TAFFY_RESPONSE_STATUSES:
self._record_taffy_context(event_id, response_text)
self._activate_thread_from_event(event_id, response_text)
def mentioned_person_names(
self,
@@ -1947,7 +2079,9 @@ class Ledger:
current_question = bounded_text(question, limit=MAX_EVENT_TEXT)
question_terms = context_terms(current_question)
followup = bool(CONTEXT_FOLLOWUP_HINT.search(current_question))
thread_followup = followup or short_thread_reply(current_question)
self_reference = bool(CONTEXT_SELF_REFERENCE.search(current_question))
active_thread = self.active_thread(group) if thread_followup else None
current_sender_trusted = trusted_sender_value(sender, sender_confidence)
current_display = (
str(self.resolve_person(group, sender)["display_name"])
@@ -1997,8 +2131,22 @@ class Ledger:
""",
(group, min(80, CONTEXT_CANDIDATE_LIMIT // 3)),
).fetchall()
history_rows = self.connection.execute(
"""
SELECT event_id, display_name, text, observed_at,
sender_confidence, body_confidence, source
FROM context_history
WHERE group_fingerprint = ?
AND event_id != ?
AND source = 'taffy_response'
ORDER BY observed_at DESC, event_id DESC
LIMIT ?
""",
(group, exclude_event_id, min(80, CONTEXT_CANDIDATE_LIMIT // 3)),
).fetchall()
candidates: list[dict[str, Any]] = []
fallback_recent: list[dict[str, Any]] = []
event_row_ids = {str(row["event_id"]) for row in rows}
for index, row in enumerate(rows):
if (
row["route"] == "local"
@@ -2046,6 +2194,12 @@ class Ledger:
if overlap:
score += min(40, overlap * 8)
tags.append("term_overlap")
if (
active_thread is not None
and str(active_thread.get("source_event_id")) == str(row["event_id"])
):
score += 140
tags.append("active_thread")
if str(row["route"]) == "local":
score += 18
tags.append("local_result")
@@ -2086,6 +2240,13 @@ class Ledger:
if overlap:
score += min(30, overlap * 8)
tags.append("term_overlap")
if (
active_thread is not None
and str(active_thread.get("source_event_id"))
== f"clock:{row['slot_key']}"
):
score += 140
tags.append("active_thread")
if followup:
score += 8
candidates.append(
@@ -2105,6 +2266,96 @@ class Ledger:
"tags": tuple(tags),
}
)
if not self_reference:
for index, row in enumerate(history_rows):
history_event_id = str(row["event_id"])
origin_event_id = (
history_event_id.removeprefix("taffy:")
if history_event_id.startswith("taffy:")
else history_event_id
)
if origin_event_id in event_row_ids:
continue
text = str(row["text"])
overlap = len(question_terms & context_terms(text))
score = max(0, 20 - index)
tags: list[str] = ["context_history", "taffy_response"]
if overlap:
score += min(30, overlap * 8)
tags.append("term_overlap")
if (
active_thread is not None
and str(active_thread.get("source_event_id")) in {
history_event_id,
origin_event_id,
}
):
score += 140
tags.append("active_thread")
if followup:
score += 8
if score < 30 and "active_thread" not in tags:
continue
candidates.append(
{
"source_event_id": history_event_id,
"speaker": (
bounded_text(row["display_name"], limit=80) or "Taffy"
),
"speaker_trusted": True,
"speaker_confidence": confidence_value(
row["sender_confidence"],
default=1.0,
),
"user_text": "",
"taffy_text": text,
"route": "history",
"route_reason": str(row["source"]),
"status": "recorded",
"observed_at": str(row["observed_at"]),
"sort_key": str(row["observed_at"]),
"score": score,
"tags": tuple(tags),
}
)
active_source_event = (
str(active_thread.get("source_event_id", ""))
if active_thread is not None
else ""
)
if (
active_thread is not None
and active_source_event
and not self_reference
and all(
str(candidate["source_event_id"]) != active_source_event
for candidate in candidates
)
):
thread_tags = ["active_thread", "thread_summary"]
source_kind = bounded_text(active_thread.get("source_kind"), limit=120)
if source_kind:
thread_tags.append(source_kind.replace(":", "_"))
candidates.append(
{
"source_event_id": f"active:{active_source_event}",
"speaker": (
bounded_text(active_thread.get("actor_display_name"), limit=80)
or "Taffy"
),
"speaker_trusted": True,
"speaker_confidence": 1.0,
"user_text": "",
"taffy_text": str(active_thread.get("summary", "")),
"route": "thread",
"route_reason": str(active_thread.get("topic", "")),
"status": "active",
"observed_at": str(active_thread.get("updated_at", "")),
"sort_key": str(active_thread.get("updated_at", "")),
"score": 220,
"tags": tuple(thread_tags),
}
)
selected: list[dict[str, Any]] = []
seen: set[str] = set()
for entry in sorted(
@@ -3521,6 +3772,97 @@ class Ledger:
).fetchone()
return str(row["value"]) if row else ""
def save_active_thread(
self,
*,
group_fingerprint: str,
topic: str,
summary: str,
actor_person_key: str = "",
actor_display_name: str = "",
source_event_id: str = "",
source_kind: str = "",
thread_key: str = "latest",
ttl_seconds: int = ACTIVE_THREAD_TTL_SECONDS,
) -> None:
group = bounded_text(group_fingerprint, limit=256)
thread = bounded_text(thread_key, limit=80) or "latest"
text = bounded_text(summary, limit=700)
if not group or not text:
return
now = datetime.now(timezone.utc)
timestamp = now.isoformat(timespec="milliseconds")
expires_at = (now + timedelta(seconds=ttl_seconds)).isoformat(
timespec="milliseconds"
)
with self.lock, self.connection:
self.connection.execute(
"""
INSERT INTO active_threads (
group_fingerprint, thread_key, topic, actor_person_key,
actor_display_name, source_event_id, source_kind, summary,
created_at, updated_at, expires_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(group_fingerprint, thread_key) DO UPDATE
SET topic = excluded.topic,
actor_person_key = excluded.actor_person_key,
actor_display_name = excluded.actor_display_name,
source_event_id = excluded.source_event_id,
source_kind = excluded.source_kind,
summary = excluded.summary,
updated_at = excluded.updated_at,
expires_at = excluded.expires_at
""",
(
group,
thread,
bounded_text(topic, limit=120),
bounded_text(actor_person_key, limit=128),
bounded_text(actor_display_name, limit=120),
bounded_text(source_event_id, limit=256),
bounded_text(source_kind, limit=120),
text,
timestamp,
timestamp,
expires_at,
),
)
def active_thread(
self,
group_fingerprint: str,
*,
thread_key: str = "latest",
now: datetime | None = None,
) -> dict[str, Any] | None:
group = bounded_text(group_fingerprint, limit=256)
thread = bounded_text(thread_key, limit=80) or "latest"
if not group:
return None
current = now or datetime.now(timezone.utc)
timestamp = current.astimezone(timezone.utc).isoformat(
timespec="milliseconds"
)
with self.lock, self.connection:
self.connection.execute(
"""
DELETE FROM active_threads
WHERE group_fingerprint = ? AND expires_at <= ?
""",
(group, timestamp),
)
row = self.connection.execute(
"""
SELECT *
FROM active_threads
WHERE group_fingerprint = ? AND thread_key = ?
AND expires_at > ?
LIMIT 1
""",
(group, thread, timestamp),
).fetchone()
return dict(row) if row else None
def recent_clock_broadcast(
self,
group_fingerprint: str,
@@ -3775,6 +4117,14 @@ class Ledger:
source=f"clock_broadcast:{kind[:48]}",
replace=True,
)
self.save_active_thread(
group_fingerprint=group_fingerprint,
topic=thread_topic_label("broadcast", kind),
summary=message,
actor_display_name="Taffy",
source_event_id=f"clock:{slot_key}",
source_kind=f"clock_broadcast:{kind[:48]}",
)
return True
def fail_clock_broadcast(
@@ -4248,6 +4598,230 @@ class Ledger:
),
)
def _local_status_subject(
self,
group: str,
subject: str,
*,
sender: str = "",
sender_confidence: float = 1.0,
) -> dict[str, str] | None:
normalized = normalize_text(subject)
if not normalized:
return None
if normalized in {"我", "本人", "自己"}:
if not trusted_sender_value(sender, sender_confidence):
return None
person = self.resolve_person(group, sender)
return {
"person_key": str(person["person_key"]),
"display_name": person_display_label(person["display_name"]),
}
if normalized.casefold() in {"你", "taffy", "jwebot", BOT_MENTION.casefold()}:
return None
self.seed_person_aliases(group)
subject_key = person_alias_key(normalized)
with self.lock:
rows = self.connection.execute(
"""
SELECT alias_key, match_kind, canonical_name
FROM person_aliases
WHERE group_fingerprint = ?
ORDER BY length(alias_key) DESC, match_kind
""",
(group,),
).fetchall()
for row in rows:
if not alias_is_mentioned(
subject_key,
str(row["alias_key"]),
str(row["match_kind"]),
):
continue
canonical = str(row["canonical_name"])
return {
"person_key": person_scope_key(group, canonical),
"display_name": person_display_label(canonical),
}
return None
@staticmethod
def _active_single_blackjack_game(account: dict[str, Any]) -> dict[str, Any] | None:
game = account.get("game")
if not isinstance(game, dict):
return None
try:
updated_at = float(game.get("updated_at", 0))
except (TypeError, ValueError):
return None
if time.time() - updated_at >= LOCAL_GAME_TIMEOUT_SECONDS:
return None
return game
def _single_blackjack_status(
self,
group: str,
person_key: str,
display_name: str,
) -> str | None:
with self.lock:
account = self._load_player_account(group, person_key)
game = self._active_single_blackjack_game(account)
if game is None:
return None
dealer = game.get("dealer", [])
dealer_text = (
f"{card_label(int(dealer[0]))} + 暗牌"
if isinstance(dealer, list) and dealer
else "未知"
)
try:
index = int(game.get("index", 0))
bet = int(game.get("bet", 0))
except (TypeError, ValueError):
index = 0
bet = 0
suffix = f" #{index}" if index > 0 else ""
return (
f"{display_name} 在玩单人21点{suffix}:"
f"手牌 {blackjack_hand_label(game.get('player'))};"
f"庄家 {dealer_text};下注 {bet};"
"可 /21点 要牌 或 /21点 停牌"
)
def _multiplayer_blackjack_status(
self,
group: str,
person_key: str,
display_name: str,
) -> str | None:
with self.lock:
table = self._open_group_blackjack(group)
if table is None:
return None
players = self._group_blackjack_players(group, int(table["table_number"]))
player = self._blackjack_entry(players, person_key)
if player is None:
return None
state = str(table["state"])
table_number = int(table["table_number"])
prompt = self._blackjack_entry(players, str(table["turn_person_key"]))
prompt_text = f";当前轮到 {prompt['display_name']}" if prompt else ""
if state == "joining":
names = "、".join(str(item["display_name"]) for item in players)
return (
f"{display_name} 在多人21点第 {table_number} 桌,正在等人加入;"
f"当前玩家:{names}"
)
if state == "betting":
bet = int(player["bet"])
bet_text = f"已放 {bet}" if bet > 0 else "还没放筹码"
return (
f"{display_name} 在多人21点第 {table_number} 桌,下注阶段;"
f"{display_name} {bet_text}{prompt_text}"
)
if state == "playing":
return (
f"{display_name} 在多人21点第 {table_number} 桌,出牌阶段;"
f"手牌 {blackjack_hand_label(player.get('hand'))};"
f"下注 {int(player['bet'])};状态 {player['state']}{prompt_text}"
)
return None
def _active_local_game_lines(self, group: str) -> list[str]:
directory = {
str(person["person_key"]): str(person["display_name"])
for person in self.person_directory(group)
}
lines: list[str] = []
with self.lock:
account_rows = self.connection.execute(
"""
SELECT person_key, state_json
FROM player_accounts
WHERE group_fingerprint = ?
ORDER BY updated_at DESC
LIMIT 80
""",
(group,),
).fetchall()
table = self._open_group_blackjack(group)
players = (
self._group_blackjack_players(group, int(table["table_number"]))
if table is not None
else []
)
for row in account_rows:
try:
account = normalize_player_account(
json.loads(str(row["state_json"]))
)
except (json.JSONDecodeError, TypeError, ValueError):
continue
game = self._active_single_blackjack_game(account)
if game is None:
continue
person_key = str(row["person_key"])
display_name = directory.get(person_key, "有人")
index = int(game.get("index", 0) or 0)
index_text = f" #{index}" if index > 0 else ""
lines.append(
f"{display_name} 单人21点{index_text},"
f"手牌 {blackjack_hand_label(game.get('player'))}"
)
if table is not None and players:
state = str(table["state"])
state_text = {
"joining": "等人加入",
"betting": "下注阶段",
"playing": "出牌阶段",
}.get(state, state)
names = "、".join(str(player["display_name"]) for player in players)
lines.append(
f"多人21点第 {int(table['table_number'])} 桌{state_text},"
f"玩家:{names}"
)
return lines
def local_game_status_reply(
self,
group: str,
question: str,
*,
sender: str = "",
sender_confidence: float = 1.0,
) -> str | None:
current = bounded_text(question, limit=MAX_EVENT_TEXT)
if LOCAL_PLAY_LIST_QUESTION.fullmatch(current):
lines = self._active_local_game_lines(group)
if not lines:
return None
return clean_reply("现在本地游戏里:" + ";".join(lines))
match = LOCAL_PLAY_STATUS_QUESTION.fullmatch(current)
if match is None:
return None
person = self._local_status_subject(
group,
match.group("subject"),
sender=sender,
sender_confidence=sender_confidence,
)
if person is None:
return None
person_key = str(person["person_key"])
display_name = str(person["display_name"])
lines = [
line
for line in (
self._single_blackjack_status(group, person_key, display_name),
self._multiplayer_blackjack_status(group, person_key, display_name),
)
if line
]
if not lines:
return None
return clean_reply(";".join(lines))
def _store_player_action(
self,
group: str,
@@ -7714,7 +8288,10 @@ class DeepSeekClient:
"STRUCTURED_RECENT_GROUP_CONTEXT 是多人群聊记录,speaker 不同就不是"
"同一个人;遇到“我刚才、我的、我上一把”这类自指问题,只引用"
"CURRENT_GROUP_SENDER_LABEL 对应的上下文。点名别人时才引用被点名者。"
"上下文不足或指代不清时,短句追问“你说哪件?”"
"标记为 local_result 或 active_thread 的内容是本地账本或当前线程事实,"
"必须优先采用,不要再回答“不知道”“去问他”或给出相反结论。"
"用户只发短句评价时,优先接 active_thread 继续聊。"
"只有多个候选明显冲突且无法判断时,才短句追问“你说哪件?”"
),
}
]
@@ -8760,6 +9337,37 @@ class EdgeApplication:
if parts is not None:
response["texts"] = parts
return response
local_fact_reply = self.ledger.local_game_status_reply(
str(row["group_fingerprint"]),
str(row["question"]),
sender=str(row["sender"]),
sender_confidence=confidence_value(
row.get("sender_confidence"),
default=1.0,
),
)
if local_fact_reply:
self.ledger.set_event(
str(row["event_id"]),
status="chat_completed",
response_text=local_fact_reply,
)
self.telemetry.elapsed(
"edge.local_fact.complete",
started,
event_id=event_id,
group_id=group_id,
route=route,
reason="local_game_status",
execution_path=ExecutionPath.LOCAL_COMMAND.value,
chars=len(local_fact_reply),
)
return {
"status": "reply",
"route": route,
"execution_path": ExecutionPath.LOCAL_COMMAND.value,
"text": local_fact_reply,
}
alias_reply = self.ledger.local_alias_identity_reply(
str(row["group_fingerprint"]),
str(row["question"]),
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
[project]
name = "jwebot-official-extensions"
version = "2.0.1b1+1021"
version = "2.0.2b1+1021"
description = "Reviewed local command adapters and broker implementation for JWeBot"
requires-python = ">=3.9"
dependencies = [
"jwebot-sdk==2.0.1b1+1021",
"jwebot-sdk==2.0.2b1+1021",
]
[tool.setuptools.packages.find]
@@ -90,4 +90,4 @@ __all__ = [
"render_feature_list",
]
__version__ = "2.0.1b1+1021"
__version__ = "2.0.2b1+1021"
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "jwebot-sdk"
version = "2.0.1b1+1021"
version = "2.0.2b1+1021"
description = "Typed, capability-limited extension API for JWeBot plugins"
requires-python = ">=3.9"
dependencies = []
@@ -40,4 +40,4 @@ __all__ = [
"SingleGroupContext",
]
__version__ = "2.0.1b1+1021"
__version__ = "2.0.2b1+1021"