Release v2.0.25 force-release lease row action
All checks were successful
技能自动化发布 / release (push) Successful in 6s

This commit is contained in:
2026-09-12 16:18:36 +08:00
parent 3f1cde1f99
commit 3ce0cc40a1
12 changed files with 251 additions and 10 deletions

View File

@@ -7,7 +7,7 @@ Subcommand tree:
account add-web|copy|update|ensure-web|add-secret|get|get-credential|list|resolve|pick-web|mark-session|mark-error|mark-used|open|reveal-profile
init-master-key|export-credentials|import-credentials
credential pick|resolve
lease release|list|cleanup
lease release|force-release|list|cleanup
(legacy) add|list|get|list-json|pick-web|open|delete
Machine commands output single-line JSON where appropriate.
@@ -42,6 +42,7 @@ from service.account_service import (
cmd_get,
cmd_init_db,
cmd_lease_cleanup,
cmd_lease_force_release,
cmd_lease_list,
cmd_lease_release,
cmd_list,
@@ -121,6 +122,7 @@ _USAGE = """account-manager v2 — Credential & Browser Profile Manager
租约管理:
lease release <lease_token>
lease force-release --id <租约编号>
lease list [--active]
lease cleanup
@@ -369,7 +371,7 @@ def main() -> None:
# ---- lease subcommand ----
elif cmd == "lease":
if len(av) < 3:
print("ERROR:CLI_BAD_ARGS lease 需要子命令:release | list | cleanup")
print("ERROR:CLI_BAD_ARGS lease 需要子命令:release | force-release | list | cleanup")
sys.exit(1)
sub = av[2]
if sub == "release":
@@ -377,13 +379,25 @@ def main() -> None:
print("ERROR:CLI_BAD_ARGS lease release 需要 <lease_token>。")
sys.exit(1)
cmd_lease_release(av[3])
elif sub == "force-release":
lease_id = _get_opt(av, "--id")
if lease_id is None and len(av) >= 4 and not str(av[3]).startswith("-"):
lease_id = av[3]
if lease_id is None:
print("ERROR:CLI_BAD_ARGS lease force-release 需要 --id <租约编号>。")
sys.exit(1)
try:
cmd_lease_force_release(int(lease_id))
except ValueError:
print("ERROR:CLI_BAD_ARGS 租约编号必须是数字。")
sys.exit(1)
elif sub == "list":
active = _has_flag(av, "--active")
cmd_lease_list(active)
elif sub == "cleanup":
cmd_lease_cleanup()
else:
print("ERROR:CLI_BAD_ARGS lease 子命令必须是 release、list 或 cleanup。")
print("ERROR:CLI_BAD_ARGS lease 子命令必须是 release、force-release、list 或 cleanup。")
sys.exit(1)
# ---- Legacy aliases ----

View File

@@ -795,6 +795,59 @@ def release_lease(lease_token: str) -> bool:
conn.close()
def release_lease_by_id(lease_id: int) -> Optional[dict]:
"""Force-release an active lease by row id. Returns released row summary or None."""
log = get_skill_logger()
log.info("lease_force_release_attempt lease_id=%s", lease_id)
conn = get_conn()
try:
cur = conn.cursor()
now = _now_unix()
cur.execute(
"SELECT id, account_id, lease_token, holder, purpose, status "
"FROM account_leases WHERE id = ?",
(int(lease_id),),
)
row = cur.fetchone()
if not row:
log.warning("lease_force_release_not_found lease_id=%s", lease_id)
return None
status = str(row[5] or "")
if status != "active":
log.warning(
"lease_force_release_not_active lease_id=%s status=%s",
lease_id,
status,
)
return None
cur.execute(
"UPDATE account_leases SET status = 'released', released_at = ?, updated_at = ? "
"WHERE id = ? AND status = 'active'",
(now, now, int(lease_id)),
)
conn.commit()
if cur.rowcount == 0:
return None
token = str(row[2] or "")
log.info(
"lease_force_release_success lease_id=%s account_id=%s token_prefix=%s",
lease_id,
row[1],
token[:8] if token else "",
)
return {
"id": int(row[0]),
"account_id": int(row[1]),
"lease_token": token,
"holder": row[3] or "",
"purpose": row[4] or "",
"status": "released",
"released_at": _unix_to_iso_local(now),
}
finally:
conn.close()
def list_active_leases() -> list[dict]:
"""List all active (non-expired, non-released) leases."""
conn = get_conn()

View File

@@ -39,7 +39,12 @@ from service.legacy_commands import (
cmd_delete_by_platform_phone,
cmd_list,
)
from service.lease_commands import cmd_lease_cleanup, cmd_lease_list, cmd_lease_release
from service.lease_commands import (
cmd_lease_cleanup,
cmd_lease_force_release,
cmd_lease_list,
cmd_lease_release,
)
from service.platform_commands import (
cmd_init_db,
cmd_platform_ensure,
@@ -70,6 +75,7 @@ __all__ = [
"cmd_get",
"cmd_init_db",
"cmd_lease_cleanup",
"cmd_lease_force_release",
"cmd_lease_list",
"cmd_lease_release",
"cmd_list",

View File

@@ -1,6 +1,11 @@
# -*- coding: utf-8 -*-
"""Lease management CLI commands."""
from db.accounts_repo import cleanup_expired_leases, list_active_leases, release_lease
from db.accounts_repo import (
cleanup_expired_leases,
list_active_leases,
release_lease,
release_lease_by_id,
)
from service._svc_common import _err_json, _ok_json, _say
from util.logging_config import get_skill_logger
@@ -16,6 +21,41 @@ def cmd_lease_release(lease_token: str) -> None:
_say(_err_json("ERROR:LEASE_NOT_FOUND", "未找到活跃的租约,或租约已释放/过期。"))
def cmd_lease_force_release(lease_id: int) -> None:
"""lease force-release --id <lease_row_id> — data-management row action."""
log = get_skill_logger()
log.info("cli_start subcommand=lease_force_release lease_id=%s", lease_id)
try:
lid = int(lease_id)
except (TypeError, ValueError):
_say(_err_json("ERROR:CLI_BAD_ARGS", "租约编号必须是数字。"))
return
if lid <= 0:
_say(_err_json("ERROR:CLI_BAD_ARGS", "租约编号必须是正整数。"))
return
released = release_lease_by_id(lid)
if not released:
_say(
_err_json(
"ERROR:LEASE_NOT_FOUND",
"未找到可释放的活跃租约(可能已释放、已过期清理,或不存在)。",
)
)
return
_say(
_ok_json(
{
"id": released["id"],
"account_id": released["account_id"],
"holder": released.get("holder") or "",
"purpose": released.get("purpose") or "",
"status": "released",
"message": "已强制释放租约,该账号可被其它任务占用。",
}
)
)
def cmd_lease_list(active_only: bool = False) -> None:
"""lease list [--active]"""
log = get_skill_logger()