release: alpha 0.6.1 local player plugins and capture reconciliation
check / local-gate (push) Failing after 3m24s
check / local-gate (push) Failing after 3m24s
This commit is contained in:
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "jwebot-kernel"
|
||||
version = "0.6.0a0+1015"
|
||||
version = "0.6.1a0+1015"
|
||||
description = "Stable routing and extension authority for JWeBot"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
"jwebot-config==0.6.0a0+1015",
|
||||
"jwebot-sdk==0.6.0a0+1015",
|
||||
"jwebot-official-extensions==0.6.0a0+1015",
|
||||
"jwebot-config==0.6.1a0+1015",
|
||||
"jwebot-sdk==0.6.1a0+1015",
|
||||
"jwebot-official-extensions==0.6.1a0+1015",
|
||||
"tomli>=2; python_version < '3.11'",
|
||||
]
|
||||
|
||||
|
||||
@@ -107,4 +107,4 @@ __all__ = [
|
||||
"verify_installed_release_signature",
|
||||
]
|
||||
|
||||
__version__ = "0.6.0a0+1015"
|
||||
__version__ = "0.6.1a0+1015"
|
||||
|
||||
@@ -941,6 +941,34 @@ class Ledger:
|
||||
observed_at TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS context_history (
|
||||
event_id TEXT PRIMARY KEY,
|
||||
group_fingerprint TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
text TEXT NOT NULL,
|
||||
observed_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS context_history_group_time
|
||||
ON context_history(group_fingerprint, observed_at, event_id);
|
||||
INSERT OR IGNORE INTO context_history
|
||||
SELECT event_id, group_fingerprint, display_name, text, observed_at
|
||||
FROM repeat_history;
|
||||
CREATE TABLE IF NOT EXISTS player_accounts (
|
||||
group_fingerprint TEXT NOT NULL,
|
||||
person_key TEXT NOT NULL,
|
||||
state_json TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
PRIMARY KEY (group_fingerprint, person_key)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS player_actions (
|
||||
group_fingerprint TEXT NOT NULL,
|
||||
event_id TEXT NOT NULL,
|
||||
person_key TEXT NOT NULL,
|
||||
command TEXT NOT NULL,
|
||||
response_text TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
PRIMARY KEY (group_fingerprint, event_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS events_group_created
|
||||
ON events(group_fingerprint, created_at);
|
||||
CREATE INDEX IF NOT EXISTS outbox_due
|
||||
@@ -2713,14 +2741,20 @@ class Ledger:
|
||||
) -> bool:
|
||||
content = bounded_text(text, limit=1_500)
|
||||
repeat_key = repeat_text_key(content)
|
||||
if not event_id or not group_fingerprint or not content:
|
||||
return False
|
||||
person = self.resolve_person(group_fingerprint, sender)
|
||||
with self.lock:
|
||||
self.connection.execute(
|
||||
"INSERT OR IGNORE INTO context_history VALUES (?, ?, ?, ?, ?)",
|
||||
(event_id, group_fingerprint, person_display_label(person["display_name"]),
|
||||
content, observed_at),
|
||||
)
|
||||
if (
|
||||
not event_id
|
||||
or not group_fingerprint
|
||||
or not repeat_key
|
||||
not repeat_key
|
||||
or len(repeat_key) < 2
|
||||
):
|
||||
return False
|
||||
person = self.resolve_person(group_fingerprint, sender)
|
||||
with self.lock, self.connection:
|
||||
cursor = self.connection.execute(
|
||||
"""
|
||||
@@ -2846,6 +2880,102 @@ class Ledger:
|
||||
).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
def recent_context_messages(
|
||||
self, group: str, limit: int, *, exclude_event_id: str = "",
|
||||
) -> list[dict[str, Any]]:
|
||||
if limit not in {100, 200, 300}:
|
||||
raise LocalCommandError("上下文数量只支持 100、200、300")
|
||||
with self.lock:
|
||||
rows = self.connection.execute(
|
||||
"""
|
||||
SELECT event_id, display_name, text, observed_at
|
||||
FROM context_history
|
||||
WHERE group_fingerprint = ? AND event_id != ?
|
||||
ORDER BY observed_at DESC, event_id DESC LIMIT ?
|
||||
""", (group, exclude_event_id, limit),
|
||||
).fetchall()
|
||||
return [dict(row) for row in reversed(rows)]
|
||||
|
||||
def person_directory(self, group: str) -> list[dict[str, Any]]:
|
||||
self.seed_person_aliases(group)
|
||||
with self.lock:
|
||||
rows = self.connection.execute(
|
||||
"""
|
||||
SELECT canonical_name, alias_key, match_kind FROM person_aliases
|
||||
WHERE group_fingerprint = ?
|
||||
ORDER BY canonical_name, match_kind, alias_key
|
||||
""", (group,),
|
||||
).fetchall()
|
||||
people: dict[str, dict[str, Any]] = {}
|
||||
for row in rows:
|
||||
name = str(row["canonical_name"])
|
||||
person = people.setdefault(name, {
|
||||
"display_name": person_display_label(name),
|
||||
"person_key": person_scope_key(group, name),
|
||||
"aliases": [], "prefixes": [],
|
||||
})
|
||||
person["prefixes" if row["match_kind"] == "prefix" else "aliases"].append(
|
||||
str(row["alias_key"]))
|
||||
# Include profiles with a canonical name even when no alias was configured.
|
||||
for template in PERSON_ALIAS_TEMPLATES:
|
||||
name = str(template["canonical_name"])
|
||||
people.setdefault(name, {
|
||||
"display_name": person_display_label(name),
|
||||
"person_key": person_scope_key(group, name),
|
||||
"aliases": [], "prefixes": [],
|
||||
})
|
||||
return list(people.values())
|
||||
|
||||
def apply_player_action(
|
||||
self, group: str, sender: str, event_id: str, command: str,
|
||||
action: Callable[[dict[str, Any]], str],
|
||||
) -> str:
|
||||
"""Commit identity-scoped funds, game state and replay response together."""
|
||||
if not group or not event_id or not sender or sender == "unknown":
|
||||
raise LocalCommandError("无法确认本人身份,不能操作金币账户")
|
||||
person = self.resolve_person(group, sender)
|
||||
person_key = str(person["person_key"])
|
||||
with self.lock:
|
||||
self.connection.execute("BEGIN IMMEDIATE")
|
||||
try:
|
||||
previous = self.connection.execute(
|
||||
"""SELECT person_key, command, response_text FROM player_actions
|
||||
WHERE group_fingerprint = ? AND event_id = ?""",
|
||||
(group, event_id),
|
||||
).fetchone()
|
||||
if previous is not None:
|
||||
if previous["person_key"] != person_key or previous["command"] != command:
|
||||
raise LocalCommandError("同一事件不能变更账户或游戏指令")
|
||||
self.connection.execute("COMMIT")
|
||||
return str(previous["response_text"])
|
||||
stored = self.connection.execute(
|
||||
"""SELECT state_json FROM player_accounts
|
||||
WHERE group_fingerprint = ? AND person_key = ?""",
|
||||
(group, person_key),
|
||||
).fetchone()
|
||||
account = json.loads(stored["state_json"]) if stored else {
|
||||
"balance": 1000, "game": None, "wins": 0, "losses": 0, "ties": 0,
|
||||
}
|
||||
response = action(account)
|
||||
if type(account["balance"]) is not int or not 0 <= account["balance"] <= 1_000_000_000:
|
||||
raise LocalCommandError("金币结算超出允许范围")
|
||||
timestamp = utc_now()
|
||||
self.connection.execute(
|
||||
"""INSERT INTO player_accounts VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(group_fingerprint, person_key) DO UPDATE
|
||||
SET state_json = excluded.state_json, updated_at = excluded.updated_at""",
|
||||
(group, person_key, json.dumps(account, ensure_ascii=False), timestamp),
|
||||
)
|
||||
self.connection.execute(
|
||||
"INSERT INTO player_actions VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(group, event_id, person_key, command, response, timestamp),
|
||||
)
|
||||
self.connection.execute("COMMIT")
|
||||
return response
|
||||
except BaseException:
|
||||
self.connection.execute("ROLLBACK")
|
||||
raise
|
||||
|
||||
def status(self) -> dict[str, Any]:
|
||||
with self.lock:
|
||||
events = self.connection.execute("SELECT COUNT(*) FROM events").fetchone()[0]
|
||||
|
||||
Reference in New Issue
Block a user