From 42de20da68fee5f9b1e552a963d767158bcdd169 Mon Sep 17 00:00:00 2001 From: host Date: Sat, 31 Oct 2026 06:20:12 +0000 Subject: [PATCH] =?UTF-8?q?feat(chv):=20=E6=9E=B6=E6=9E=84=E4=B8=8E?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=B4=A8=E9=87=8F=209.5+=20=E6=8F=90?= =?UTF-8?q?=E5=8D=87=E2=80=94=E2=80=94=E9=95=BF=E5=87=BD=E6=95=B0=E6=B8=85?= =?UTF-8?q?=E9=9B=B6=E3=80=81WarmPool=20facade=E3=80=81#215=20=E5=AF=B9?= =?UTF-8?q?=E9=BD=90=E3=80=81=E5=AE=A1=E8=AE=A1=E8=AE=A1=E6=95=B0=E3=80=81?= =?UTF-8?q?=E5=8F=AA=E8=AF=BB=20VM=20=E5=AD=97=E6=AE=B5=E3=80=81ADR-8~11?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chv-guest/check_lockfile.py | 68 +++ cloud/chv/api_client.py | 165 ++++--- cloud/chv/compat215.py | 59 +++ cloud/chv/config.py | 75 ++-- cloud/chv/executor.py | 410 +++++++++--------- cloud/chv/hardening.py | 68 +-- cloud/chv/pool.py | 10 + cloud/console-react/src/api/types.ts | 4 + .../src/sections/TaskDetailPanel.tsx | 1 + cloud/console.html | 22 +- cloud/server.py | 10 + cloud/tests/test_chv_arch95plus.py | 179 ++++++++ cloud/worker.py | 11 + docs/chv-architecture.md | 70 ++- docs/chv-production-readiness.md | 33 ++ 15 files changed, 840 insertions(+), 345 deletions(-) create mode 100644 chv-guest/check_lockfile.py create mode 100644 cloud/chv/compat215.py create mode 100644 cloud/tests/test_chv_arch95plus.py diff --git a/chv-guest/check_lockfile.py b/chv-guest/check_lockfile.py new file mode 100644 index 0000000..8fa22a4 --- /dev/null +++ b/chv-guest/check_lockfile.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""校验已构建镜像的 BUILDINFO 供应链清单(事后 lockfile 检查)。 + +事前 apt/pip 精确 lockfile 无法在本轨生成:apt 版本在构建 +宿主上解析、pip 版本在客机里解析,没有构建宿主就编不出 +版本号——本脚本绝不编造。构建时 build.sh 已从客机读回 +dpkg + pip freeze 写进 BUILDINFO.versions(事后 lockfile), +本脚本校验它:versions 非空、tag 与预期一致、可选的冻结 +清单(--frozen JSON,形如 {"dpkg": ["pkg version", ...]}) +逐项比对,漂移即退出非 0。 + +用法: + python3 check_lockfile.py out/BUILDINFO.json [--tag TAG] + [--frozen frozen.json] +""" +import json +import sys + + +def main(argv): + if len(argv) < 2: + print("用法:check_lockfile.py BUILDINFO.json [--tag TAG] " + "[--frozen frozen.json]", file=sys.stderr) + return 2 + path = argv[1] + tag = None + frozen_path = None + i = 2 + while i < len(argv): + if argv[i] == "--tag" and i + 1 < len(argv): + tag = argv[i + 1] + i += 2 + elif argv[i] == "--frozen" and i + 1 < len(argv): + frozen_path = argv[i + 1] + i += 2 + else: + print("未知参数:%s" % argv[i], file=sys.stderr) + return 2 + with open(path) as fh: + data = json.load(fh) + if tag is not None and data.get("tag") != tag: + print("tag 不一致:%s != %s" % (data.get("tag"), tag), + file=sys.stderr) + return 1 + versions = data.get("versions") or {} + if not versions.get("dpkg") or not versions.get("pip"): + print("BUILDINFO.versions.dpkg/pip 为空,供应链清单缺失", + file=sys.stderr) + return 1 + if frozen_path: + with open(frozen_path) as fh: + frozen = json.load(fh) + for kind in ("dpkg", "pip"): + want = set(frozen.get(kind) or []) + got = set(versions.get(kind) or []) + missing = want - got + if missing: + print("%s 漂移:冻结清单里有、构建产物里无:%s" + % (kind, sorted(missing)), file=sys.stderr) + return 1 + print("lockfile 校验通过:tag=%s dpkg=%d pip=%d" + % (data.get("tag"), len(versions["dpkg"]), + len(versions["pip"]))) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) diff --git a/cloud/chv/api_client.py b/cloud/chv/api_client.py index 64b7c6e..6cae561 100644 --- a/cloud/chv/api_client.py +++ b/cloud/chv/api_client.py @@ -9,12 +9,100 @@ import os import socket +def _read_header(sock): + """读满响应头:返回 (header_text, 已收体部前缀)。 + + 超时或提前关流即 RuntimeError,绝不把半截头当结束 + (feat-chv-fix P1-6 的语义,纯移动)。""" + buf = b"" + header_end = None + timed_out = False + while header_end is None: + try: + data = sock.recv(65536) + except socket.timeout: + timed_out = True + break + if not data: + break + buf += data + header_end = buf.find(b"\r\n\r\n") + if timed_out or header_end is None: + raise RuntimeError("CHV API 响应头不完整(超时或提前关流)") + header = buf[:header_end].decode("utf-8", errors="replace") + return header, buf[header_end + 4:] + + +def _parse_content_length(header): + """从响应头取 Content-Length:chunked 或无长度即拒绝。 + + chunked 明确拒绝(CHV REST 不应发 chunked);无长度则 + 无法证完整性,同样拒绝。""" + content_length = None + is_chunked = False + for line in header.split("\r\n")[1:]: + if ":" not in line: + continue + name, value = line.split(":", 1) + name = name.strip().lower() + value = value.strip() + if name == "content-length": + try: + content_length = int(value) + except ValueError: + raise RuntimeError("CHV API Content-Length 非法") + elif name == "transfer-encoding" \ + and "chunked" in value.lower(): + is_chunked = True + if is_chunked: + raise RuntimeError("CHV API 响应为 chunked,拒绝解析") + if content_length is None: + raise RuntimeError("CHV API 响应无 Content-Length,拒绝解析") + return content_length + + +def _read_body(sock, body, content_length): + """按 Content-Length 读满体部:超时或提前关流即截断错误。""" + while len(body) < content_length: + try: + data = sock.recv(65536) + except socket.timeout: + raise RuntimeError( + "CHV API 响应体截断:已收 %d / 需 %d 字节" + % (len(body), content_length)) + if not data: + raise RuntimeError( + "CHV API 响应体截断:已收 %d / 需 %d 字节" + % (len(body), content_length)) + body += data + return body[:content_length] + + +def _parse_response(header, body): + """从已读满的头与体部解析 (status, body):能解析 JSON + 时为 dict,否则为原文本;状态行解析失败时 status 为 0。""" + text = header + "\r\n\r\n" + body.decode("utf-8", errors="replace") + status = 0 + if text.startswith("HTTP/"): + try: + status = int(text.split()[1]) + except (IndexError, ValueError): + status = 0 + body_text = body.decode("utf-8", errors="replace") + try: + parsed = json.loads(body_text) if body_text.strip() else {} + except ValueError: + parsed = body_text + return status, parsed + + def _unix_socket_api(socket_path, method, path, payload=None): """Cloud Hypervisor REST API 的默认传输:标准库 raw HTTP over Unix socket(无第三方依赖)。返回 (status, body), body 能解析 JSON 时为 dict,否则为原文本。socket 不存在 或连接失败时 raise RuntimeError(调用方据此判 api 超时 - /失败,不编造 200)。""" + /失败,不编造 200)。读满语义见 _read_header / + _parse_content_length / _read_body(P1-6,纯移动)。""" if not os.path.exists(socket_path): raise RuntimeError("api socket 不存在:%s" % socket_path) body = b"" @@ -32,79 +120,12 @@ def _unix_socket_api(socket_path, method, path, payload=None): try: sock.connect(socket_path) sock.sendall(raw) - chunks = [] - # feat-chv-fix(P1-6):读超时不再当“响应结束”。 - # 先读满响应头,再按 Content-Length 读满体部;超时、 - # 提前关流、体部短于 Content-Length 一律 RuntimeError - # (调用方判 api_timeout / 传输失败),绝不解析半截 - # 响应。chunked 与无 Content-Length 响应明确拒绝 - # (Cloud Hypervisor REST 不应发 chunked;无长度则 - # 无法证完整性)。 - buf = b"" - header_end = None - timed_out = False - while header_end is None: - try: - data = sock.recv(65536) - except socket.timeout: - timed_out = True - break - if not data: - break - buf += data - header_end = buf.find(b"\r\n\r\n") - if timed_out or header_end is None: - raise RuntimeError("CHV API 响应头不完整(超时或提前关流)") - header = buf[:header_end].decode("utf-8", errors="replace") - body = buf[header_end + 4:] - content_length = None - is_chunked = False - for line in header.split("\r\n")[1:]: - if ":" not in line: - continue - name, value = line.split(":", 1) - name = name.strip().lower() - value = value.strip() - if name == "content-length": - try: - content_length = int(value) - except ValueError: - raise RuntimeError("CHV API Content-Length 非法") - elif name == "transfer-encoding" \ - and "chunked" in value.lower(): - is_chunked = True - if is_chunked: - raise RuntimeError("CHV API 响应为 chunked,拒绝解析") - if content_length is None: - raise RuntimeError("CHV API 响应无 Content-Length,拒绝解析") - while len(body) < content_length: - try: - data = sock.recv(65536) - except socket.timeout: - raise RuntimeError( - "CHV API 响应体截断:已收 %d / 需 %d 字节" - % (len(body), content_length)) - if not data: - raise RuntimeError( - "CHV API 响应体截断:已收 %d / 需 %d 字节" - % (len(body), content_length)) - body += data - body = body[:content_length] + header, body = _read_header(sock) + content_length = _parse_content_length(header) + body = _read_body(sock, body, content_length) finally: sock.close() - text = header + "\r\n\r\n" + body.decode("utf-8", errors="replace") - status = 0 - if text.startswith("HTTP/"): - try: - status = int(text.split()[1]) - except (IndexError, ValueError): - status = 0 - body_text = body.decode("utf-8", errors="replace") - try: - parsed = json.loads(body_text) if body_text.strip() else {} - except ValueError: - parsed = body_text - return status, parsed + return _parse_response(header, body) def normalize_api_result(result) -> tuple: diff --git a/cloud/chv/compat215.py b/cloud/chv/compat215.py new file mode 100644 index 0000000..35c4adb --- /dev/null +++ b/cloud/chv/compat215.py @@ -0,0 +1,59 @@ +"""#215(feat-arch-converge)接口对齐层,CHV 本地实现。 + +背景:#215 未合并,本基线里没有 cloud/timeouts.py、 +cloud/gates.py、cloud/quota.py、cloud/sandbox_policy.py。 +本模块绝不复制 #215 的文件,只把 CHV 真正能映射的 +接口写成与 #215 相同的签名,#215 合并后替换为 import +即是机械的(见 docs/chv-architecture.md ADR-8)。 + +映射表(逐个核对过 #215 远程分支的真实函数,不是凭空编的): +- gates.resolve_inside(base, rel):目录门,CHV 快照/磁盘 + 路径白名单正好需要它——本模块实现同语义并已在 + hardening.validate_snapshot_url 里使用,#215 合并后 + 改为 from cloud.gates import resolve_inside 即可。 +- sandbox_policy.tmpfs_isolated(executor_kind, path):CHV + 客机 /tmp 落在客机磁盘上,不是宿主 tmpfs,故对 + cloud-hypervisor 一律返回 (False, ...)——fail-closed, + 绝不宣称隔离。未知执行器同样 fail-closed。 +- timeouts.resolve_module_timeout / quota.decide:无映射。 + 前者是审批/队列超时(None=不过期、0=不过期),CHV + 的 api/ready 超时是秒数且 0 无效,语义不同,硬套会 + 改行为;后者是按月美元配额,CHV 的配额是 VM 数/磁盘 + GB/快照数,单位不同。两者在本模块里 deliberately + 不提供,测试钉住“CHV 不 import 它们”。 + +本模块只依赖标准库,是叶子模块,不 import cloud.sandbox。 +""" + +import os + + +def resolve_inside(base, rel): + """目录门:base 内的相对路径安全解析,越界回 None。 + + 空串回 base 本身;绝对路径、.. 逃逸、符号链接逃逸、 + commonpath 跨盘符 ValueError 一律 None。签名与 #215 + 的 gates.resolve_inside 一致,合并后可直接替换为 import。""" + if not rel: + return base + if os.path.isabs(rel): + return None + real_base = os.path.realpath(base) + target = os.path.realpath(os.path.join(real_base, rel)) + try: + if os.path.commonpath([real_base, target]) != real_base: + return None + except ValueError: + return None + return target + + +def tmpfs_isolated(executor_kind, path): + """某执行器下某路径是否走 tmpfs 隔离:(isolated, reason)。 + + cloud-hypervisor:客机 /tmp 落在客机磁盘上,不走宿主 + tmpfs,一律 (False, ...);未知执行器 fail-closed 同样 + False。签名与 #215 的 sandbox_policy.tmpfs_isolated 一致。""" + if executor_kind == "cloud-hypervisor": + return False, "CHV 客机 /tmp 落在客机磁盘上,不走宿主 tmpfs" + return False, "未知执行器,按不隔离处理(fail-closed 口径)" diff --git a/cloud/chv/config.py b/cloud/chv/config.py index 78c947b..48ee740 100644 --- a/cloud/chv/config.py +++ b/cloud/chv/config.py @@ -170,45 +170,21 @@ def _timeout_fragment(params: dict, env: dict) -> dict: } -def build_chv_config(params: dict, env: dict): - """把显式构造参数(params,None=未给)与环境变量(env, - 默认为 os.environ 的调用方传入 dict)解析成 ChvConfig。 +def _assemble_chv_config(base: dict, ssh: dict, quota: dict, + timeouts: dict): + """把基础字段 + SSH/配额/超时三个片段组装成 ChvConfig。 - 显式参数只有 __init__ 本来就有的那些;max_vms、磁盘模式、 - 磁盘配额、egress、快照、预热池目标数本来就没有显式参数, - 仍只走环境变量(与 #216 一致,不悄悄加新参数面)。 - 片段化(feat-chv-arch95):SSH 面、配额面、超时面分别 - 由 _ssh_fragment/_quota_fragment/_timeout_fragment 解析, - 错误文案与校验顺序逐字一致;build_chv_config 只组装。""" - env = env if env is not None else {} - binary = params.get("binary") or env.get("CHV_BINARY") \ - or "cloud-hypervisor" - kernel = params.get("kernel") or env.get("CHV_KERNEL") - rootfs = params.get("rootfs") or params.get("image") \ - or env.get("CHV_ROOTFS") or env.get("CHV_IMAGE") - cpus = str(params.get("cpus") or env.get("CHV_CPU") or "2") - memory = params.get("memory") or env.get("CHV_MEM") or "2G" - state_dir = params.get("state_dir") or env.get("CHV_STATE_DIR") \ - or os.path.join(os.path.expanduser("~"), - ".local", "share", "codex-app-server", "chv-vms") - kvm_path = params.get("kvm_path") \ - if params.get("kvm_path") is not None else "/dev/kvm" - cpuinfo_path = params.get("cpuinfo_path") \ - if params.get("cpuinfo_path") is not None else "/proc/cpuinfo" - ch_remote = params.get("ch_remote") or env.get("CHV_REMOTE") \ - or "ch-remote" - model = params.get("model") - ssh = _ssh_fragment(params, env) - quota = _quota_fragment(env) - timeouts = _timeout_fragment(params, env) + 纯移动:字段名与取值逐字不变,build_chv_config 只解析。""" return ChvConfig( - binary=binary, kernel=kernel, rootfs=rootfs, cpus=cpus, - memory=memory, state_dir=state_dir, ssh_key=ssh["ssh_key"], - ssh_user=ssh["ssh_user"], ssh_port=ssh["ssh_port"], + binary=base["binary"], kernel=base["kernel"], + rootfs=base["rootfs"], cpus=base["cpus"], + memory=base["memory"], state_dir=base["state_dir"], + ssh_key=ssh["ssh_key"], ssh_user=ssh["ssh_user"], + ssh_port=ssh["ssh_port"], guest_host_py=ssh["guest_host_py"], cmdline=ssh["cmdline"], net=ssh["net"], net_json=ssh["net_json"], - kvm_path=kvm_path, cpuinfo_path=cpuinfo_path, - ch_remote=ch_remote, model=model, + kvm_path=base["kvm_path"], cpuinfo_path=base["cpuinfo_path"], + ch_remote=base["ch_remote"], model=base["model"], max_vms=quota["max_vms"], disk_mode=quota["disk_mode"], disk_gb=quota["disk_gb"], ssh_host=ssh["ssh_host"], ssh_forward=ssh["ssh_forward"], @@ -228,3 +204,32 @@ def build_chv_config(params: dict, env: dict): snapshot_retention_days=quota["snapshot_retention_days"], snapshot_dir=quota["snapshot_dir"], allow_remote_snapshot=quota["allow_remote_snapshot"]) + + +def build_chv_config(params: dict, env: dict): + """把显式构造参数与环境变量解析成 ChvConfig(见 + _assemble_chv_config 的组装段;片段化与校验顺序不变)。""" + env = env if env is not None else {} + base = { + "binary": params.get("binary") or env.get("CHV_BINARY") + or "cloud-hypervisor", + "kernel": params.get("kernel") or env.get("CHV_KERNEL"), + "rootfs": params.get("rootfs") or params.get("image") + or env.get("CHV_ROOTFS") or env.get("CHV_IMAGE"), + "cpus": str(params.get("cpus") or env.get("CHV_CPU") or "2"), + "memory": params.get("memory") or env.get("CHV_MEM") or "2G", + "state_dir": params.get("state_dir") + or env.get("CHV_STATE_DIR") + or os.path.join(os.path.expanduser("~"), + ".local", "share", "codex-app-server", "chv-vms"), + "kvm_path": params.get("kvm_path") + if params.get("kvm_path") is not None else "/dev/kvm", + "cpuinfo_path": params.get("cpuinfo_path") + if params.get("cpuinfo_path") is not None else "/proc/cpuinfo", + "ch_remote": params.get("ch_remote") or env.get("CHV_REMOTE") + or "ch-remote", + "model": params.get("model"), + } + return _assemble_chv_config( + base, _ssh_fragment(params, env), _quota_fragment(env), + _timeout_fragment(params, env)) diff --git a/cloud/chv/executor.py b/cloud/chv/executor.py index 21f3198..5c06379 100644 --- a/cloud/chv/executor.py +++ b/cloud/chv/executor.py @@ -289,6 +289,7 @@ class CloudHypervisorExecutor(Executor): else _unix_socket_api self.sleeper = sleeper if sleeper is not None else time.sleep self.audit_callback = None + self.audit_failure_count = 0 self._vms_lock = threading.Lock() if runner is not None: self.runner = runner @@ -602,9 +603,15 @@ class CloudHypervisorExecutor(Executor): feat-chv-secfix(M5):审计回调失败不再双重静默 pass——记宿主 error 日志(审计完整性本身没有保证, 但失败至少可见;任务仍不因此炸,审计失败不改变 - 任务状态是 worker 层的既定语义)。审计 detail 里 - 绝不含密钥值(调用方负责:本类所有 _emit 的 detail - 只含 VM 名、路径、URL、kind,不含任何 key 值)。""" + 任务状态是 worker 层的既定语义)。feat-chv-arch95plus + 取舍(见 ADR-9):不改成“审计失败即中断任务”—— + 审计是可观测性不是门禁,DB 短暂不可用时硬中断会把 + 一次审计失败放大成任务失败,且与 worker 全局 + best-effort 语义冲突;改为“失败记日志 + 计数 + 不 + 中断”,计数见 self.audit_failure_count(宿主可观测, + 非任务状态)。审计 detail 里绝不含密钥值(调用方 + 负责:本类所有 _emit 的 detail 只含 VM 名、路径、 + URL、kind,不含任何 key 值)。""" try: if self.audit_callback is not None: self.audit_callback(event, task, detail, kind) @@ -612,6 +619,8 @@ class CloudHypervisorExecutor(Executor): _LOGGER.info("chv_audit event=%s kind=%s %s", event, kind, detail) except Exception as exc: + self.audit_failure_count = getattr( + self, "audit_failure_count", 0) + 1 _LOGGER.error("chv_audit_failed event=%s kind=%s error=%s", event, kind, exc) @@ -981,21 +990,37 @@ class CloudHypervisorExecutor(Executor): "vmm.ping 超时:%d 次内 API 未通,socket=%s" % (attempts, socket_path)) - def wait_guest_ready(self, task): - """等 guest SSH 就绪(只选 SSH,见类 docstring): - 轮询 ssh … "true",returncode 0 即就绪;次数 = - ready_timeout/ready_interval 取整,至少 1 次。 - 每轮先尽力捕获 host key(known_hosts 文件不存在时; - sshd 未起时抓不到是常态,下轮再试),再发 ssh - "true"——钉死式 SSH 在文件缺失时会直接拒绝未知主机, - 不先捕获就永远等不到就绪。超时时先关 VM 再 raise - ChvFailure(kind=guest_not_ready),绝不在未就绪时 - 派任务。""" - # feat-chv-fix(P0-2):provisioned 模式下缺 known_hosts - # 时直接 fail-fast,绝不调用 capture_host_key 回落 TOFU。 - # (keyscan 模式才走下面的捕获分支。) + def _provisioned_key_guard(self, task): + """provisioned 下缺 known_hosts 即 fail-fast,绝不 keyscan。 + + 返回 True 表示已拦截(调用方须先关 VM 再抛 + host_key_not_pinned);False 表示可继续轮询。""" if getattr(self, "host_key_mode", "provisioned") == "provisioned" \ and not os.path.exists(self.known_hosts_path(task)): + return True + return False + + def _check_host_key_mismatch(self, task, result): + """SSH stderr 含钉死拒绝标记时审计并返回 True(须 fail-fast)。 + + MITM 尝试是独立安全事件,不轮询成普通超时;审计 + detail 只含 VM 名与标记,不含 key 值。""" + stderr = getattr(result, "stderr", "") or "" + if "Host key verification failed" in stderr \ + or "REMOTE HOST IDENTIFICATION" in stderr: + self._emit("host_key_mismatch", task, + "vm=%s ssh host key 被拒绝(钉死不匹配)" + % self.vm_name(task), "host_key_mismatch") + return True + return False + + def wait_guest_ready(self, task): + """等 guest SSH 就绪:轮询 ssh "true",returncode 0 即就绪; + 每轮先尽力捕获 host key(keyscan 模式),超时先关 VM + 再 raise guest_not_ready,绝不在未就绪时派任务。""" + # P0-2:provisioned 下缺 known_hosts 即 fail-fast,绝不 + # 调用 capture_host_key 回落 TOFU(keyscan 才走捕获分支)。 + if self._provisioned_key_guard(task): try: self.stop_vm(task) except Exception: @@ -1013,17 +1038,9 @@ class CloudHypervisorExecutor(Executor): self.build_ssh_command(task, ["true"])) if getattr(result, "returncode", 1) == 0: return True - # feat-chv-secfix(M5):host key 不匹配是独立 - # 安全事件,不是“sshd 没起”。SSH stderr 含 - # 钉死拒绝标记时立即审计 host_key_mismatch 并 - # fail-fast(不把 MITM 尝试轮询成普通超时), - # 审计 detail 只含 VM 名与标记,不含 key 值。 - stderr = getattr(result, "stderr", "") or "" - if "Host key verification failed" in stderr \ - or "REMOTE HOST IDENTIFICATION" in stderr: - self._emit("host_key_mismatch", task, - "vm=%s ssh host key 被拒绝(钉死不匹配)" - % self.vm_name(task), "host_key_mismatch") + # M5:host key 不匹配是独立安全事件,见 + # _check_host_key_mismatch(审计 + fail-fast)。 + if self._check_host_key_mismatch(task, result): try: self.stop_vm(task) except Exception: @@ -1394,58 +1411,60 @@ class CloudHypervisorExecutor(Executor): % (self.vm_name(task_or_name), snapshot_url)) return snapshot_url - def restore_vm(self, task, snapshot_url): - """快照恢复唤醒:preflight → 只起 VMM(--api-socket) - → 等 vmm.ping → ch-remote restore → 显式 resume → - 等 guest SSH 就绪。恢复后的 VM 默认是 paused,不 - resume 就不算醒——本方法绝不省略 resume 那一步。 - 任一步失败都关 VM 再抛。成功返回 {vm, api_socket, - state: "running", ready: True, mode: "restored"}。""" + def _validate_restore(self, task, snapshot_url): + """restore 前置校验:返回归一化后的 snapshot_url。 + + restore 不走 vm.create、没有 VmConfig 可挂 seed: + 快照内已固化原 VM host key,重预置反而破坏钉死, + 故 provisioned 下复用已钉死的 known_hosts、缺失 + fail-fast、绝不 keyscan、绝不重生成。restore 时不 + 重复验快照文件存在(远端/对象存储源合法,ch-remote + 本身会在源缺失时失败)。""" if not isinstance(task, dict) or task.get("id") is None: raise ValueError("任务缺少 id") if not snapshot_url: raise ValueError("snapshot_url 不能为空") if "://" not in snapshot_url: snapshot_url = "file://%s" % snapshot_url - # feat-chv-secfix(L2):恢复源同样过 URL 白名单 - # (file:// 须在白名单目录内,远端须显式放行), - # 在 preflight/起 VMM 之前就拒绝。 validate_snapshot_url( snapshot_url, self.state_dir, snapshot_dir=getattr(self, "snapshot_dir", None), allow_remote=getattr(self, "allow_remote_snapshot", False)) self.preflight() self.resolve_ssh_host() - # feat-chv-fix(P0-2 修正版):restore 不走 vm.create, - # 没有 VmConfig 可挂 seed——审计报告“restore 须把 seed - # 接入 VmConfig”的说法在此不适用,纠正为:快照内已 - # 固化原 VM 的 host key,重新预置新 key 反而会破坏 - # 钉死。正确不变量是:provisioned 下复用已钉死的 - # known_hosts(stop 时公钥保留、私钥已删但快照内仍有 - # 原私钥),缺失时 fail-fast、绝不 keyscan、绝不重 - # 生成。file:// 快照须先验存在且非空,否则不启动。 - # 注意:restore 时不重复验快照文件存在——快照落盘 - # 校验是 snapshot_vm/hibernate_vm 创建时的门(P1-7), - # restore 的 ch-remote 本身会在源缺失时失败;在这里 - # 再验一次会把“源在远端/对象存储”的合法路径也挡掉。 - if getattr(self, "host_key_mode", "provisioned") == "provisioned" \ - and not os.path.exists(self.known_hosts_path(task)): + if self._provisioned_key_guard(task): raise ChvFailure( "host_key_not_pinned", "provisioned 模式下 restore 缺 known_hosts,拒绝 " "keyscan 回落:VM %s" % self.vm_name(task)) + return snapshot_url + + def _restore_boot(self, task, snapshot_url): + """restore 的启动段:起 VMM → 等 ping → restore → + 显式 resume → 等 SSH 就绪(恢复后默认 paused,不 + resume 就不算醒)。""" + name = self.vm_name(task) + self._launch_api_only(task) + self.wait_api_ready(task) + self._check_api_socket_permissions(self.api_socket(task)) + self._ch_remote(task, "restore", + "source_url=%s" % snapshot_url) + self._emit("vm_restore", task, + "vm=%s snapshot=%s" % (name, snapshot_url)) + self.resume_vm(task) + self.wait_guest_ready(task) + + def restore_vm(self, task, snapshot_url): + """快照恢复唤醒:校验 → 只起 VMM → restore → 显式 + resume → 等 SSH 就绪。恢复后默认 paused,不 resume + 就不算醒。任一步失败都关 VM 再抛;成功返回 + {vm, api_socket, state: "running", ready: True, + mode: "restored"}。""" + snapshot_url = self._validate_restore(task, snapshot_url) name = self.vm_name(task) self._check_capacity(exclude=name) try: - self._launch_api_only(task) - self.wait_api_ready(task) - self._check_api_socket_permissions(self.api_socket(task)) - self._ch_remote(task, "restore", - "source_url=%s" % snapshot_url) - self._emit("vm_restore", task, - "vm=%s snapshot=%s" % (name, snapshot_url)) - self.resume_vm(task) - self.wait_guest_ready(task) + self._restore_boot(task, snapshot_url) except Exception: try: self.stop_vm(task) @@ -1476,58 +1495,26 @@ class CloudHypervisorExecutor(Executor): 说明来源是休眠快照。""" return self.restore_vm(task, snapshot_url) - def stop_vm(self, task_or_name): - """停止 VM(尽力而为,不抛异常;三种情况): - - 已死进程:登记进程 poll() 非 None,跳过 terminate - 等待,直接删 socket、清登记,返回 True。 - - 已关 VM:无登记进程、无 socket,ch-remote 仍尽力 - 调过一次,返回 False(本来就没有什么可清)。 - - socket 残留:有 socket、无登记进程,删 socket 并 - 返回 True。 - ch-remote shutdown 失败不炸(VM 可能已死),但 - ch-remote 命令本身仍会发出——测试据此断言关闭路径 - 确实用了 ch-remote shutdown,而不是只杀进程。""" - name = self.vm_name(task_or_name) - socket_path = os.path.join(self.state_dir, name + ".sock") + def _stop_registered_proc(self, name, proc): + """清登记进程:已死直接清,活着先杀再清(纯移动)。""" + if proc is None: + return with self._vms_lock: - had_proc = name in self._vms - proc = self._vms.get(name) - had = had_proc or os.path.exists(socket_path) + self._vms.pop(name, None) try: - self.runner([self.ch_remote, - "--api-socket=%s" % socket_path, "shutdown"]) + alive = proc.poll() is None except Exception: - pass - if proc is not None: - with self._vms_lock: - self._vms.pop(name, None) - try: - alive = proc.poll() is None - except Exception: - alive = False - if alive: - self._kill_proc(proc) - try: - if os.path.exists(socket_path): - os.remove(socket_path) - except Exception: - pass - # 补缺口轨:per-VM seed 目录与 host 私钥随 VM - # 关闭而删,绝不留给下一个 VM 复用;known_hosts - # 公钥文件保留供事后审计,删失败不炸关闭。下次 - # 同名 VM 预置时若私钥已删即重新生成并覆盖 - # known_hosts,不会用旧钉死去校验新 VM。 - # feat-chv-secfix(H3/M2):清理分三块独立记账—— - # seed/私钥块、任务磁盘块、net 登记块,任一块失败 - # 都记对应审计 + 写脏标记(清理失败即拒起),绝不 - # 再以“一块失败、整段吞掉”收场;任务磁盘块是 H3 - # 的主修复:stop 即删 disk.raw(保留期 0;需取证 - # 须在 shutdown 前经 SSH 拷出,见 recycle_vm 文档)。 - # net.json 是 live 预约(非审计 artifact),随 VM - # 关闭而删,tap/mac/ip 预约随即释放。 - _task = task_or_name if isinstance(task_or_name, dict) \ - else {"id": task_or_name} - _cleanup_failed = False + alive = False + if alive: + self._kill_proc(proc) + + def _cleanup_vm_files(self, name, task): + """stop 的三块文件清理:seed/私钥、任务磁盘、net 登记。 + + 任一块失败记对应审计并返回 True(脏标记由调用方写); + seed/私钥与磁盘删除的审计名与文案逐字不变。""" + from cloud.chv.hostkeys import dirty_marker_path # noqa: F401 + failed = False try: seed_dir = os.path.join(self.state_dir, name, "seed") if os.path.isdir(seed_dir): @@ -1539,45 +1526,69 @@ class CloudHypervisorExecutor(Executor): if os.path.exists(_p): os.remove(_p) except Exception as _cleanup_exc: - _cleanup_failed = True - self._emit("vm_key_cleanup_failed", _task, + failed = True + self._emit("vm_key_cleanup_failed", task, "vm=%s error=%s" % (name, _cleanup_exc)) try: if delete_task_disk(self.state_dir, name): - self._emit("vm_disk_deleted", _task, + self._emit("vm_disk_deleted", task, "vm=%s disk deleted on stop" % name) except Exception as _disk_exc: - _cleanup_failed = True - self._emit("vm_disk_delete_failed", _task, + failed = True + self._emit("vm_disk_delete_failed", task, "vm=%s error=%s" % (name, _disk_exc)) try: _net_path = os.path.join(self.state_dir, name, "net.json") if os.path.exists(_net_path): os.remove(_net_path) except Exception: - _cleanup_failed = True + failed = True self._guest_env_delivered.pop(name, None) - if _cleanup_failed: - # M2 根治:脏标记。标记本身写失败也不炸关闭, - # 但下次 provision_host_key 仍会因残留文件复用 - # 旧 key——该残留情形由 cleanup_stale_secrets 的 - # 扫描兜底(见 cleanup_stale_vms),文档明写。 - try: - from cloud.chv.hostkeys import dirty_marker_path - _marker = dirty_marker_path(self.state_dir, name) + return failed + + def _write_dirty_marker(self, name, failed): + """M2 脏标记:清理失败写标记、成功清标记;写失败不炸关闭。""" + from cloud.chv.hostkeys import dirty_marker_path + try: + _marker = dirty_marker_path(self.state_dir, name) + if failed: os.makedirs(os.path.dirname(_marker), exist_ok=True) with open(_marker, "w") as fh: fh.write("cleanup failed\n") - except Exception: - pass - else: - try: - from cloud.chv.hostkeys import dirty_marker_path - _marker = dirty_marker_path(self.state_dir, name) - if os.path.exists(_marker): - os.remove(_marker) - except Exception: - pass + elif os.path.exists(_marker): + os.remove(_marker) + except Exception: + pass + + def stop_vm(self, task_or_name): + """停止 VM(尽力而为,不抛异常):已死进程直接清登记, + 已关 VM 仍尽力调一次 ch-remote(返回 False),socket + 残留删 socket(返回 True)。ch-remote shutdown 失败 + 不炸,但命令本身仍会发出(测试据此断言关闭路径)。""" + name = self.vm_name(task_or_name) + socket_path = os.path.join(self.state_dir, name + ".sock") + with self._vms_lock: + had_proc = name in self._vms + proc = self._vms.get(name) + had = had_proc or os.path.exists(socket_path) + try: + self.runner([self.ch_remote, + "--api-socket=%s" % socket_path, "shutdown"]) + except Exception: + pass + self._stop_registered_proc(name, proc) + try: + if os.path.exists(socket_path): + os.remove(socket_path) + except Exception: + pass + # per-VM seed 目录与 host 私钥随 VM 关闭而删,绝不留给 + # 下一个 VM 复用;known_hosts 公钥保留供事后审计。 + # 三块清理独立记账 + 脏标记(H3/M2,见 _cleanup_vm_files)。 + _task = task_or_name if isinstance(task_or_name, dict) \ + else {"id": task_or_name} + _cleanup_failed = self._cleanup_vm_files(name, _task) + self._write_dirty_marker(name, _cleanup_failed) task = task_or_name if isinstance(task_or_name, dict) \ else {"id": task_or_name} if had: @@ -1624,12 +1635,49 @@ class CloudHypervisorExecutor(Executor): "vm=%s gc error=%s" % (entry, exc)) return {"deleted": deleted, "failed": failed} + def _is_live_vm(self, entry): + """VM 是否活着:socket 存在或登记进程未死即活。""" + if os.path.exists(os.path.join(self.state_dir, entry + ".sock")): + return True + with self._vms_lock: + proc = self._vms.get(entry) + if proc is not None: + try: + return proc.poll() is None + except Exception: + pass + return False + + def _clean_one_vm_secrets(self, entry, vm_dir): + """单 VM 残留清理:私钥一律删(live VM 私钥在 seed 盘 + 建好后即删,残留只可能是崩溃遗留);非 live VM 的 + seed.iso/seed 目录同理删。返回 (删私钥, 删 seed)。""" + removed_priv = False + priv = os.path.join(vm_dir, "ssh_host_ed25519_key") + if os.path.exists(priv): + try: + os.remove(priv) + removed_priv = True + except Exception: + pass + removed_seed = False + if not self._is_live_vm(entry): + for _suffix in ("seed.iso", "seed"): + _p = os.path.join(vm_dir, _suffix) + try: + if os.path.isdir(_p): + shutil.rmtree(_p) + removed_seed = True + elif os.path.exists(_p): + os.remove(_p) + removed_seed = True + except Exception: + pass + return removed_priv, removed_seed + def cleanup_stale_secrets(self): - """崩溃残留扫描(H5):state_dir 下的 host 私钥文件 - 在本轨不变量下只可能是残留——live VM 的私钥在 seed - 盘建好后即删(见 wake_vm),所以任何残留私钥文件 - 一律删除并审计;非 live VM 的 seed.iso/seed 目录同 - 理。返回已清理的 VM 名列表。worker 启动与 + """崩溃残留扫描(H5):私钥一律删、非 live VM 的 seed + 同理删并审计;返回已清理 VM 名。worker 启动与 cleanup_stale_vms 时调它,不依赖 socket 是否存在。""" cleaned = [] try: @@ -1642,36 +1690,8 @@ class CloudHypervisorExecutor(Executor): vm_dir = os.path.join(self.state_dir, entry) if not os.path.isdir(vm_dir): continue - priv = os.path.join(vm_dir, "ssh_host_ed25519_key") - removed_priv = False - if os.path.exists(priv): - try: - os.remove(priv) - removed_priv = True - except Exception: - pass - live = os.path.exists( - os.path.join(self.state_dir, entry + ".sock")) - with self._vms_lock: - proc = self._vms.get(entry) - if proc is not None: - try: - live = live or proc.poll() is None - except Exception: - pass - removed_seed = False - if not live: - for _suffix in ("seed.iso", "seed"): - _p = os.path.join(vm_dir, _suffix) - try: - if os.path.isdir(_p): - shutil.rmtree(_p) - removed_seed = True - elif os.path.exists(_p): - os.remove(_p) - removed_seed = True - except Exception: - pass + removed_priv, removed_seed = \ + self._clean_one_vm_secrets(entry, vm_dir) if removed_priv or removed_seed: cleaned.append(entry) self._emit("host_secret_residue_cleaned", @@ -1712,32 +1732,11 @@ class CloudHypervisorExecutor(Executor): "lost": ["guest /tmp", "guest worktree temp files"], } - def cleanup_stale_vms(self): - """清理残留 VM:只清 socket 残留与已死进程对应的 VM。 + def _stop_dead_socket_vms(self, entries): + """socket 扫描:登记进程仍活的跳过,其余走 stop_vm。 - feat-chv-secfix(H3/H5):先跑残留盘 GC 与残留 - 密钥扫描(不依赖 socket 是否存在,覆盖 worker 崩溃 - 后 socket 已删、文件仍在的情形),再走原 socket 扫描。 - 扫描 state_dir 下 *.sock:登记进程仍活(poll() 为 None) - 的跳过;其余一律走 stop_vm。返回已清理的 VM 名列表; - state_dir 不存在时返回 [],绝不抛异常。基础设施或 - 其它执行器的 socket 不在 state_dir 里,不会被误删; - 同目录下非 chv- 前缀的 socket 也跳过(只认本执行器 - 的 chv- 命名)。 - """ + 返回已清理 VM 名;非 chv- 前缀 socket 一律跳过。""" cleaned = [] - try: - self.cleanup_stale_secrets() - except Exception: - pass - try: - self.gc_stale_disks() - except Exception: - pass - try: - entries = os.listdir(self.state_dir) - except Exception: - return [] for entry in entries: if not entry.endswith(".sock"): continue @@ -1754,9 +1753,11 @@ class CloudHypervisorExecutor(Executor): continue if self.stop_vm(name): cleaned.append(name) - # 登记进程已死但 socket 文件已被删的情况:登记项本身 - # 也是残留,顺手清掉登记,不计入 cleaned(没有 socket - # 可删,不算一次清理)。 + return cleaned + + def _purge_dead_registry(self): + """登记进程已死但 socket 已被删的登记项顺手清掉 + (没有 socket 可删,不计入清理数)。""" for name in list(self._vms.keys()): proc = self._vms.get(name) try: @@ -1765,6 +1766,25 @@ class CloudHypervisorExecutor(Executor): dead = True if dead: self._vms.pop(name, None) + + def cleanup_stale_vms(self): + """清理残留 VM:先跑残留盘 GC 与残留密钥扫描,再走 + socket 扫描与登记清理。只清 socket 残留与已死进程 + 对应的 VM;state_dir 不存在返回 [],绝不抛异常。""" + try: + self.cleanup_stale_secrets() + except Exception: + pass + try: + self.gc_stale_disks() + except Exception: + pass + try: + entries = os.listdir(self.state_dir) + except Exception: + return [] + cleaned = self._stop_dead_socket_vms(entries) + self._purge_dead_registry() return cleaned # -- 客内执行 ------------------------------------------------------ diff --git a/cloud/chv/hardening.py b/cloud/chv/hardening.py index 015fc10..21a60a8 100644 --- a/cloud/chv/hardening.py +++ b/cloud/chv/hardening.py @@ -99,33 +99,47 @@ def validate_net_entries(entries: list, existing_entries: list) -> list: vm})的 tap/mac/ip 任一重复即 net_tap_conflict, 拒绝启动并由调用方审计。 返回归一化后的条目 list。""" - seen_taps, seen_macs, seen_ips = set(), set(), set() - normalized = [] - for entry in entries: - unknown = set(entry) - _NET_FIELDS - if unknown: - raise ChvFailure( - "net_config_invalid", - "CHV_NET_JSON 条目含未知字段:%s(白名单:%s)" - % (",".join(sorted(unknown)), - ",".join(sorted(_NET_FIELDS)))) - tap = str(entry.get("tap") or "").strip() - mac = str(entry.get("mac") or "").strip().lower() - ip = str(entry.get("ip") or "").strip() - for kind, value, seen in (("tap", tap, seen_taps), - ("mac", mac, seen_macs), - ("ip", ip, seen_ips)): + normalized = [_normalize_net_entry(e) for e in entries] + _check_net_duplicates(normalized) + _check_net_conflicts(normalized, existing_entries) + return normalized + + +def _normalize_net_entry(entry: dict) -> dict: + """单条 net 条目归一化:字段白名单 + tap/mac/ip 归一化。 + + 空 tap 视为未指定,不参与 tap 冲突比较(见 + validate_net_entries 的说明,纯移动)。""" + unknown = set(entry) - _NET_FIELDS + if unknown: + raise ChvFailure( + "net_config_invalid", + "CHV_NET_JSON 条目含未知字段:%s(白名单:%s)" + % (",".join(sorted(unknown)), + ",".join(sorted(_NET_FIELDS)))) + return {"tap": str(entry.get("tap") or "").strip(), + "mac": str(entry.get("mac") or "").strip().lower(), + "ip": str(entry.get("ip") or "").strip(), + "mask": str(entry.get("mask") or "")} + + +def _check_net_duplicates(normalized: list) -> None: + """本次条目内 tap/mac/ip 重复即 net_config_invalid。""" + seen = {"tap": set(), "mac": set(), "ip": set()} + for entry in normalized: + for kind in ("tap", "mac", "ip"): + value = entry.get(kind) if not value: continue - if kind == "tap" and not tap: - continue - if value in seen: + if value in seen[kind]: raise ChvFailure( "net_config_invalid", "CHV_NET_JSON 内 %s 重复:%s" % (kind, value)) - seen.add(value) - normalized.append({"tap": tap, "mac": mac, "ip": ip, - "mask": str(entry.get("mask") or "")}) + seen[kind].add(value) + + +def _check_net_conflicts(normalized: list, existing_entries: list) -> None: + """与已登记 VM 的 tap/mac/ip 任一重复即 net_tap_conflict。""" for entry in normalized: for other in existing_entries: for kind in ("tap", "mac", "ip"): @@ -136,7 +150,6 @@ def validate_net_entries(entries: list, existing_entries: list) -> list: "net %s %s 已被 VM %s 使用,拒绝共享" " tap/mac/ip:每 VM 必须独立" % (kind, value, other.get("vm"))) - return normalized def validate_egress_proxy(egress_proxy: str) -> None: @@ -162,9 +175,12 @@ def validate_egress_proxy(egress_proxy: str) -> None: def _is_within(directory, path): - directory = os.path.realpath(directory) - path = os.path.realpath(path) - return path == directory or path.startswith(directory + os.sep) + """目录内判定:经 compat215.resolve_inside(#215 目录门 + 同签名),越界回 None 即不在内。绝对路径先转相对路径 + 再过门,.. 逃逸与符号链接逃逸一律挡。""" + from cloud.chv.compat215 import resolve_inside + rel = os.path.relpath(path, directory) + return resolve_inside(directory, rel) is not None def validate_snapshot_url(snapshot_url: str, state_dir: str, snapshot_dir=None, diff --git a/cloud/chv/pool.py b/cloud/chv/pool.py index 6630e64..0c4963f 100644 --- a/cloud/chv/pool.py +++ b/cloud/chv/pool.py @@ -304,3 +304,13 @@ class WarmPool: "warm_pool_release", task, "vm=%s destroyed=%s" % (pool_vm, destroyed)) return destroyed + + +def reconcile_orphans_for_executor(executor) -> list: + """生产路径唯一的 WarmPool 入口:只做孤儿清理。 + + worker 启动/关闭调它,不构造 WarmPool 类、不调 + prewarm/acquire/release——实验接口与生产路径彻底分离: + 生产代码 import 本函数即可,acquire/prewarm 只存在于 + 实验用的 WarmPool 类与测试里。""" + return WarmPool(executor).reconcile_orphans() diff --git a/cloud/console-react/src/api/types.ts b/cloud/console-react/src/api/types.ts index 44228fc..6406adb 100644 --- a/cloud/console-react/src/api/types.ts +++ b/cloud/console-react/src/api/types.ts @@ -21,6 +21,10 @@ export interface Task { coordinator_part?: string; goal_mode?: number | boolean; verify_command?: string; no_time_limit?: number | boolean; environment_id?: string; attachments?: string[]; model?: string; effort?: string; + // feat-chv-arch95plus 只读字段:仅 CHV 任务且 worker 已把 + // VM 死亡写进 interrupted_reason 时 server 才返回 true, + // 其它情形字段不存在(绝不把“未报告死亡”当成“VM 活着”)。 + chv_vm_dead?: boolean; child_model?: string; child_effort?: string; approval?: string; result_text?: string; error_text?: string; } diff --git a/cloud/console-react/src/sections/TaskDetailPanel.tsx b/cloud/console-react/src/sections/TaskDetailPanel.tsx index 9962d6c..5a054b3 100644 --- a/cloud/console-react/src/sections/TaskDetailPanel.tsx +++ b/cloud/console-react/src/sections/TaskDetailPanel.tsx @@ -131,6 +131,7 @@ export default function TaskDetailPanel() {
turn
{shortId(t.turn_id)}
root turn
{shortId(t.root_turn_id)}
executor
{t.executor || ""}{t.executor === "cloud-hypervisor" ? "(实验执行器,默认关闭;仅展示,无 VM 控制按钮)" : ""}
+ {t.chv_vm_dead ?
VM 状态
VM 中途死亡,任务未完成、断点可续跑(后端只读字段 chv_vm_dead;无该字段时不代表 VM 活着)
: null}
主机
{t.worktree_host || ""}
模型
{t.model || "默认"}
强度
{t.effort || "默认"}
diff --git a/cloud/console.html b/cloud/console.html index 1e354fe..a1fd577 100644 --- a/cloud/console.html +++ b/cloud/console.html @@ -20,7 +20,7 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var Hh;function Gy(){if(Hh)return pt;Hh=1;var i=Symbol.for("react.transitional.element"),r=Symbol.for("react.portal"),h=Symbol.for("react.fragment"),f=Symbol.for("react.strict_mode"),p=Symbol.for("react.profiler"),N=Symbol.for("react.consumer"),C=Symbol.for("react.context"),j=Symbol.for("react.forward_ref"),z=Symbol.for("react.suspense"),R=Symbol.for("react.memo"),q=Symbol.for("react.lazy"),g=Symbol.for("react.activity"),U=Symbol.for("react.view_transition"),F=Symbol.iterator;function X(y){return y===null||typeof y!="object"?null:(y=F&&y[F]||y["@@iterator"],typeof y=="function"?y:null)}var J={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},V=Object.assign,vt={};function St(y,D,ut){this.props=y,this.context=D,this.refs=vt,this.updater=ut||J}St.prototype.isReactComponent={},St.prototype.setState=function(y,D){if(typeof y!="object"&&typeof y!="function"&&y!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,y,D,"setState")},St.prototype.forceUpdate=function(y){this.updater.enqueueForceUpdate(this,y,"forceUpdate")};function jt(){}jt.prototype=St.prototype;function K(y,D,ut){this.props=y,this.context=D,this.refs=vt,this.updater=ut||J}var Nt=K.prototype=new jt;Nt.constructor=K,V(Nt,St.prototype),Nt.isPureReactComponent=!0;var w=Array.isArray;function Q(){}var nt={H:null,A:null,T:null,S:null},Dt=Object.prototype.hasOwnProperty;function qt(y,D,ut){var et=ut.ref;return{$$typeof:i,type:y,key:D,ref:et!==void 0?et:null,props:ut}}function dt(y,D){return qt(y.type,D,y.props)}function kt(y){return typeof y=="object"&&y!==null&&y.$$typeof===i}function H(y){var D={"=":"=0",":":"=2"};return"$"+y.replace(/[=:]/g,function(ut){return D[ut]})}var ot=/\/+/g;function yt(y,D){return typeof y=="object"&&y!==null&&y.key!=null?H(""+y.key):D.toString(36)}function G(y){switch(y.status){case"fulfilled":return y.value;case"rejected":throw y.reason;default:switch(typeof y.status=="string"?y.then(Q,Q):(y.status="pending",y.then(function(D){y.status==="pending"&&(y.status="fulfilled",y.value=D)},function(D){y.status==="pending"&&(y.status="rejected",y.reason=D)})),y.status){case"fulfilled":return y.value;case"rejected":throw y.reason}}throw y}function Y(y,D,ut,et,Ct){var bt=typeof y;(bt==="undefined"||bt==="boolean")&&(y=null);var Tt=!1;if(y===null)Tt=!0;else switch(bt){case"bigint":case"string":case"number":Tt=!0;break;case"object":switch(y.$$typeof){case i:case r:Tt=!0;break;case q:return Tt=y._init,Y(Tt(y._payload),D,ut,et,Ct)}}if(Tt)return Ct=Ct(y),Tt=et===""?"."+yt(y,0):et,w(Ct)?(ut="",Tt!=null&&(ut=Tt.replace(ot,"$&/")+"/"),Y(Ct,D,ut,"",function(re){return re})):Ct!=null&&(kt(Ct)&&(Ct=dt(Ct,ut+(Ct.key==null||y&&y.key===Ct.key?"":(""+Ct.key).replace(ot,"$&/")+"/")+Tt)),D.push(Ct)),1;Tt=0;var lt=et===""?".":et+":";if(w(y))for(var mt=0;mt>>1,xt=G[gt];if(0>>1;gtp(se,tt))yp(D,se)?(G[gt]=D,G[y]=tt,gt=y):(G[gt]=se,G[L]=tt,gt=L);else if(yp(D,tt))G[gt]=D,G[y]=tt,gt=y;else break t}}return Y}function p(G,Y){var tt=G.sortIndex-Y.sortIndex;return tt!==0?tt:G.id-Y.id}if(i.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var N=performance;i.unstable_now=function(){return N.now()}}else{var C=Date,j=C.now();i.unstable_now=function(){return C.now()-j}}var z=[],R=[],q=1,g=null,U=3,F=!1,X=!1,J=!1,V=!1,vt=typeof setTimeout=="function"?setTimeout:null,St=typeof clearTimeout=="function"?clearTimeout:null,jt=typeof setImmediate<"u"?setImmediate:null;function K(G){for(var Y=h(R);Y!==null;){if(Y.callback===null)f(R);else if(Y.startTime<=G)f(R),Y.sortIndex=Y.expirationTime,r(z,Y);else break;Y=h(R)}}function Nt(G){if(J=!1,K(G),!X)if(h(z)!==null)X=!0,w||(w=!0,kt());else{var Y=h(R);Y!==null&&yt(Nt,Y.startTime-G)}}var w=!1,Q=-1,nt=5,Dt=-1;function qt(){return V?!0:!(i.unstable_now()-DtG&&qt());){var gt=g.callback;if(typeof gt=="function"){g.callback=null,U=g.priorityLevel;var xt=gt(g.expirationTime<=G);if(G=i.unstable_now(),typeof xt=="function"){g.callback=xt,K(G),Y=!0;break e}g===h(z)&&f(z),K(G)}else f(z);g=h(z)}if(g!==null)Y=!0;else{var Se=h(R);Se!==null&&yt(Nt,Se.startTime-G),Y=!1}}break t}finally{g=null,U=tt,F=!1}Y=void 0}}finally{Y?kt():w=!1}}}var kt;if(typeof jt=="function")kt=function(){jt(dt)};else if(typeof MessageChannel<"u"){var H=new MessageChannel,ot=H.port2;H.port1.onmessage=dt,kt=function(){ot.postMessage(null)}}else kt=function(){vt(dt,0)};function yt(G,Y){Q=vt(function(){G(i.unstable_now())},Y)}i.unstable_IdlePriority=5,i.unstable_ImmediatePriority=1,i.unstable_LowPriority=4,i.unstable_NormalPriority=3,i.unstable_Profiling=null,i.unstable_UserBlockingPriority=2,i.unstable_cancelCallback=function(G){G.callback=null},i.unstable_forceFrameRate=function(G){0>G||125gt?(G.sortIndex=tt,r(R,G),h(z)===null&&G===h(R)&&(J?(St(Q),Q=-1):J=!0,yt(Nt,tt-gt))):(G.sortIndex=xt,r(z,G),X||F||(X=!0,w||(w=!0,kt()))),G},i.unstable_shouldYield=qt,i.unstable_wrapCallback=function(G){var Y=U;return function(){var tt=U;U=Y;try{return G.apply(this,arguments)}finally{U=tt}}}})(oo)),oo}var Yh;function Xy(){return Yh||(Yh=1,ro.exports=Vy()),ro.exports}var fo={exports:{}},Ue={};/** + */var qh;function Qy(){return qh||(qh=1,(function(i){function r(G,Y){var tt=G.length;G.push(Y);t:for(;0>>1,xt=G[gt];if(0>>1;gtp(se,tt))yp(D,se)?(G[gt]=D,G[y]=tt,gt=y):(G[gt]=se,G[L]=tt,gt=L);else if(yp(D,tt))G[gt]=D,G[y]=tt,gt=y;else break t}}return Y}function p(G,Y){var tt=G.sortIndex-Y.sortIndex;return tt!==0?tt:G.id-Y.id}if(i.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var N=performance;i.unstable_now=function(){return N.now()}}else{var C=Date,j=C.now();i.unstable_now=function(){return C.now()-j}}var z=[],R=[],q=1,g=null,U=3,F=!1,X=!1,J=!1,Q=!1,vt=typeof setTimeout=="function"?setTimeout:null,St=typeof clearTimeout=="function"?clearTimeout:null,jt=typeof setImmediate<"u"?setImmediate:null;function K(G){for(var Y=h(R);Y!==null;){if(Y.callback===null)f(R);else if(Y.startTime<=G)f(R),Y.sortIndex=Y.expirationTime,r(z,Y);else break;Y=h(R)}}function Nt(G){if(J=!1,K(G),!X)if(h(z)!==null)X=!0,w||(w=!0,kt());else{var Y=h(R);Y!==null&&yt(Nt,Y.startTime-G)}}var w=!1,V=-1,nt=5,Dt=-1;function qt(){return Q?!0:!(i.unstable_now()-DtG&&qt());){var gt=g.callback;if(typeof gt=="function"){g.callback=null,U=g.priorityLevel;var xt=gt(g.expirationTime<=G);if(G=i.unstable_now(),typeof xt=="function"){g.callback=xt,K(G),Y=!0;break e}g===h(z)&&f(z),K(G)}else f(z);g=h(z)}if(g!==null)Y=!0;else{var Se=h(R);Se!==null&&yt(Nt,Se.startTime-G),Y=!1}}break t}finally{g=null,U=tt,F=!1}Y=void 0}}finally{Y?kt():w=!1}}}var kt;if(typeof jt=="function")kt=function(){jt(dt)};else if(typeof MessageChannel<"u"){var H=new MessageChannel,ot=H.port2;H.port1.onmessage=dt,kt=function(){ot.postMessage(null)}}else kt=function(){vt(dt,0)};function yt(G,Y){V=vt(function(){G(i.unstable_now())},Y)}i.unstable_IdlePriority=5,i.unstable_ImmediatePriority=1,i.unstable_LowPriority=4,i.unstable_NormalPriority=3,i.unstable_Profiling=null,i.unstable_UserBlockingPriority=2,i.unstable_cancelCallback=function(G){G.callback=null},i.unstable_forceFrameRate=function(G){0>G||125gt?(G.sortIndex=tt,r(R,G),h(z)===null&&G===h(R)&&(J?(St(V),V=-1):J=!0,yt(Nt,tt-gt))):(G.sortIndex=xt,r(z,G),X||F||(X=!0,w||(w=!0,kt()))),G},i.unstable_shouldYield=qt,i.unstable_wrapCallback=function(G){var Y=U;return function(){var tt=U;U=Y;try{return G.apply(this,arguments)}finally{U=tt}}}})(oo)),oo}var Yh;function Xy(){return Yh||(Yh=1,ro.exports=Qy()),ro.exports}var fo={exports:{}},Ue={};/** * @license React * react-dom.production.js * @@ -36,7 +36,7 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var Gh;function Ly(){if(Gh)return Ue;Gh=1;var i=po();function r(q){var g="https://react.dev/errors/"+q;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(r){console.error(r)}}return i(),fo.exports=Ly(),fo.exports}/** + */var Gh;function Ly(){if(Gh)return Ue;Gh=1;var i=po();function r(q){var g="https://react.dev/errors/"+q;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(r){console.error(r)}}return i(),fo.exports=Ly(),fo.exports}/** * @license React * react-dom-client.production.js * @@ -44,19 +44,19 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var Vh;function Ky(){if(Vh)return ii;Vh=1;var i=Xy(),r=po(),h=Zy();function f(t){var e="https://react.dev/errors/"+t;if(1jl||(t.current=Ga[jl],Ga[jl]=null,jl--)}function Qt(t,e){jl++,Ga[jl]=t.current,t.current=e}var ye=Xe(null),Jl=Xe(null),xe=Xe(null),da=Xe(null);function Le(t,e){switch(Qt(xe,e),Qt(Jl,t),Qt(ye,null),e.nodeType){case 9:case 11:t=(t=e.documentElement)&&(t=t.namespaceURI)?Xm(t):0;break;default:if(t=e.tagName,e=e.namespaceURI)e=Xm(e),t=Lm(e,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}ae(ye),Qt(ye,t)}function zl(){ae(ye),ae(Jl),ae(xe)}function Rl(t){var e=t.memoizedState;e!==null&&(eu._currentValue=e.memoizedState,Qt(da,t)),e=ye.current;var l=Lm(e,t.type);e!==l&&(Qt(Jl,t),Qt(ye,l))}function Tl(t){Jl.current===t&&(ae(ye),ae(Jl)),da.current===t&&(ae(da),eu._currentValue=re)}var Be,oe;function fe(t){if(Be===void 0)try{throw Error()}catch(l){var e=l.stack.trim().match(/\n( *(at )?)/);Be=e&&e[1]||"",oe=-1jl||(t.current=Ga[jl],Ga[jl]=null,jl--)}function Vt(t,e){jl++,Ga[jl]=t.current,t.current=e}var ye=Xe(null),Jl=Xe(null),xe=Xe(null),da=Xe(null);function Le(t,e){switch(Vt(xe,e),Vt(Jl,t),Vt(ye,null),e.nodeType){case 9:case 11:t=(t=e.documentElement)&&(t=t.namespaceURI)?Xm(t):0;break;default:if(t=e.tagName,e=e.namespaceURI)e=Xm(e),t=Lm(e,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}ae(ye),Vt(ye,t)}function zl(){ae(ye),ae(Jl),ae(xe)}function Rl(t){var e=t.memoizedState;e!==null&&(eu._currentValue=e.memoizedState,Vt(da,t)),e=ye.current;var l=Lm(e,t.type);e!==l&&(Vt(Jl,t),Vt(ye,l))}function Tl(t){Jl.current===t&&(ae(ye),ae(Jl)),da.current===t&&(ae(da),eu._currentValue=re)}var Be,oe;function fe(t){if(Be===void 0)try{throw Error()}catch(l){var e=l.stack.trim().match(/\n( *(at )?)/);Be=e&&e[1]||"",oe=-1)":-1n||m[a]!==b[n]){var E=` `+m[a].replace(" at new "," at ");return t.displayName&&E.includes("")&&(E=E.replace("",t.displayName)),E}while(1<=a&&0<=n);break}}}finally{rl=!1,Error.prepareStackTrace=l}return(l=t?t.displayName||t.name:"")?fe(l):""}function ma(t,e){switch(t.tag){case 26:case 27:case 5:return fe(t.type);case 16:return fe("Lazy");case 13:return t.child!==e&&e!==null?fe("Suspense Fallback"):fe("Suspense");case 19:return fe("SuspenseList");case 0:case 15:return Fl(t.type,!1);case 11:return Fl(t.type.render,!1);case 1:return Fl(t.type,!0);case 31:return fe("Activity");case 30:return fe("ViewTransition");default:return""}}function Dl(t){try{var e="",l=null;do e+=ma(t,l),l=t,t=t.return;while(t);return e}catch(a){return` Error generating stack: `+a.message+` -`+a.stack}}var ee=Object.prototype.hasOwnProperty,Pe=i.unstable_scheduleCallback,qe=i.unstable_cancelCallback,_l=i.unstable_shouldYield,ol=i.unstable_requestPaint,$t=i.unstable_now,kl=i.unstable_getCurrentPriorityLevel,mn=i.unstable_ImmediatePriority,Qa=i.unstable_UserBlockingPriority,be=i.unstable_NormalPriority,Va=i.unstable_LowPriority,ha=i.unstable_IdlePriority,su=i.log,ru=i.unstable_setDisableYieldValue,Wl=null,Me=null;function fl(t){if(typeof su=="function"&&ru(t),Me&&typeof Me.setStrictMode=="function")try{Me.setStrictMode(Wl,t)}catch{}}var Oe=Math.clz32?Math.clz32:du,ou=Math.log,fu=Math.LN2;function du(t){return t>>>=0,t===0?32:31-(ou(t)/fu|0)|0}var B=256,k=262144,P=4194304;function ct(t){var e=t&42;if(e!==0)return e;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&-t;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function A(t,e,l){var a=t.pendingLanes;if(a===0)return 0;var n=0,u=t.suspendedLanes,s=t.pingedLanes;t=t.warmLanes;var o=a&134217727;return o!==0?(a=o&~u,a!==0?n=ct(a):(s&=o,s!==0?n=ct(s):l||(l=o&~t,l!==0&&(n=ct(l))))):(o=a&~u,o!==0?n=ct(o):s!==0?n=ct(s):l||(l=a&~t,l!==0&&(n=ct(l)))),n===0?0:e!==0&&e!==n&&(e&u)===0&&(u=n&-n,l=e&-e,u>=l||u===32&&(l&4194048)!==0)?e:n}function $(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function st(t,e){(e&8)!==0&&(e|=e&32);var l=t.entangledLanes;if(l!==0)for(t=t.entanglements,l&=e;0l;l++)e.push(t);return e}function Te(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function wc(t,e,l,a,n,u){var s=t.pendingLanes;t.pendingLanes=l,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=l,t.entangledLanes&=l,t.errorRecoveryDisabledLanes&=l,t.shellSuspendCounter=0;var o=t.entanglements,m=t.expirationTimes,b=t.hiddenUpdates;for(l=s&~l;0"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Vc=!1;if(Pl)try{var pu={};Object.defineProperty(pu,"passive",{get:function(){Vc=!0}}),window.addEventListener("test",pu,pu),window.removeEventListener("test",pu,pu)}catch{Vc=!1}var va=null,Xc=null,hi=null;function qo(){if(hi)return hi;var t,e=Xc,l=e.length,a,n="value"in va?va.value:va.textContent,u=n.length;for(t=0;t=bu),Lo=" ",Zo=!1;function Ko(t,e){switch(t){case"keyup":return Uv.indexOf(e.keyCode)!==-1;case"keydown":return e.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Jo(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var Tn=!1;function Bv(t,e){switch(t){case"compositionend":return Jo(e);case"keypress":return e.which!==32?null:(Zo=!0,Lo);case"textInput":return t=e.data,t===Lo&&Zo?null:t;default:return null}}function qv(t,e){if(Tn)return t==="compositionend"||!Fc&&Ko(t,e)?(t=qo(),hi=Xc=va=null,Tn=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(e.ctrlKey||e.altKey||e.metaKey)||e.ctrlKey&&e.altKey){if(e.char&&1"u")return null;try{return t.activeElement||t.body}catch{return t.body}}function lf(t){for(;t&&t.firstChild;)t=t.firstChild;return t}function af(t,e){var l=lf(t);t=0;for(var a;l;){if(l.nodeType===3){if(a=t+l.textContent.length,t<=e&&a>=e)return{node:l,offset:e-t};t=a}t:{for(;l;){if(l.nextSibling){l=l.nextSibling;break t}l=l.parentNode}l=void 0}l=lf(l)}}function nf(t,e){return t&&e?t===e?!0:t&&t.nodeType===3?!1:e&&e.nodeType===3?nf(t,e.parentNode):"contains"in t?t.contains(e):t.compareDocumentPosition?!!(t.compareDocumentPosition(e)&16):!1:!1}function uf(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var e=Ic(t.document);e instanceof t.HTMLIFrameElement;){try{var l=typeof e.contentWindow.location.href=="string"}catch{l=!1}if(l)t=e.contentWindow;else break;e=Ic(t.document)}return e}function Pc(t){var e=t&&t.nodeName&&t.nodeName.toLowerCase();return e&&(e==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||e==="textarea"||t.contentEditable==="true")}var Kv=Pl&&"documentMode"in document&&11>=document.documentMode,_n=null,ts=null,Eu=null,es=!1;function cf(t,e,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;es||_n==null||_n!==Ic(a)||(a=_n,"selectionStart"in a&&Pc(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),Eu&&_u(Eu,a)||(Eu=a,a=Sc(ts,"onSelect"),0>=s,n-=s,Hl=1<<32-Oe(e)+n|l<ht?(ve=rt,rt=null):ve=rt.sibling;var Ut=S(x,rt,T[ht],M);if(Ut===null){rt===null&&(rt=ve);break}t&&rt&&Ut.alternate===null&&e(x,rt),v=u(Ut,v,ht),Rt===null?I=Ut:Rt.sibling=Ut,Rt=Ut,rt=ve}if(ht===T.length)return l(x,rt),At&&aa(x,ht),I;if(rt===null){for(;htht?(ve=rt,rt=null):ve=rt.sibling;var Ya=S(x,rt,Ut.value,M);if(Ya===null){rt===null&&(rt=ve);break}t&&rt&&Ya.alternate===null&&e(x,rt),v=u(Ya,v,ht),Rt===null?I=Ya:Rt.sibling=Ya,Rt=Ya,rt=ve}if(Ut.done)return l(x,rt),At&&aa(x,ht),I;if(rt===null){for(;!Ut.done;ht++,Ut=T.next())Ut=O(x,Ut.value,M),Ut!==null&&(v=u(Ut,v,ht),Rt===null?I=Ut:Rt.sibling=Ut,Rt=Ut);return At&&aa(x,ht),I}for(rt=a(rt);!Ut.done;ht++,Ut=T.next())Ut=_(rt,x,ht,Ut.value,M),Ut!==null&&(t&&(ve=Ut.alternate,ve!==null&&rt.delete(ve.key===null?ht:ve.key)),v=u(Ut,v,ht),Rt===null?I=Ut:Rt.sibling=Ut,Rt=Ut);return t&&rt.forEach(function(By){return e(x,By)}),At&&aa(x,ht),I}function Et(x,v,T,M){if(typeof T=="object"&&T!==null&&T.type===dt&&T.key===null&&T.props.ref===void 0&&(T=T.props.children),typeof T=="object"&&T!==null){switch(T.$$typeof){case Dt:t:{for(var I=T.key;v!==null;){if(v.key===I){if(I=T.type,I===dt){if(v.tag===7){l(x,v.sibling),M=n(v,T.props.children),ba(M,T),M.return=x,x=M;break t}}else if(v.elementType===I||typeof I=="object"&&I!==null&&I.$$typeof===xt&&ln(I)===v.type){l(x,v.sibling),M=n(v,T.props),ba(M,T),M.return=x,x=M;break t}l(x,v);break}else e(x,v);v=v.sibling}T.type===dt?(M=Fa(T.props.children,x.mode,M,T.key),ba(M,T),M.return=x,x=M):(M=_i(T.type,T.key,T.props,null,x.mode,M),ba(M,T),M.return=x,x=M)}return s(x);case qt:t:{for(I=T.key;v!==null;){if(v.key===I)if(v.tag===4&&v.stateNode.containerInfo===T.containerInfo&&v.stateNode.implementation===T.implementation){l(x,v.sibling),M=n(v,T.children||[]),M.return=x,x=M;break t}else{l(x,v);break}else e(x,v);v=v.sibling}M=ss(T,x.mode,M),M.return=x,x=M}return s(x);case xt:return T=ln(T),Et(x,v,T,M)}if(Tt(T))return Z(x,v,T,M);if(et(T)){if(I=et(T),typeof I!="function")throw Error(f(150));return T=I.call(T),it(x,v,T,M)}if(typeof T.then=="function")return Et(x,v,ki(T),M);if(T.$$typeof===yt)return Et(x,v,Oi(x,T),M);wi(x,T)}return typeof T=="string"&&T!==""||typeof T=="number"||typeof T=="bigint"?(T=""+T,v!==null&&v.tag===6?(l(x,v.sibling),M=n(v,T),M.return=x,x=M):(l(x,v),M=cs(T,x.mode,M),M.return=x,x=M),s(x)):l(x,v)}return function(x,v,T,M){try{zu=0;var I=Et(x,v,T,M);return Dn=null,I}catch(rt){if(rt===Rn||rt===Ri)throw rt;var Rt=Je(29,rt,null,x.mode);return Rt.lanes=M,Rt.return=x,Rt}finally{}}}var nn=zf(!0),Rf=zf(!1),ja=!1;function ps(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ss(t,e){t=t.updateQueue,e.updateQueue===t&&(e.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function Ta(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function _a(t,e,l){var a=t.updateQueue;if(a===null)return null;if(a=a.shared,(Gt&2)!==0){var n=a.pending;return n===null?e.next=e:(e.next=n.next,n.next=e),a.pending=e,e=Ti(t),vf(t,null,l),e}return ji(t,a,e,l),Ti(t)}function Ru(t,e,l){if(e=e.updateQueue,e!==null&&(e=e.shared,(l&4194048)!==0)){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,hn(t,l)}}function xs(t,e){var l=t.updateQueue,a=t.alternate;if(a!==null&&(a=a.updateQueue,l===a)){var n=null,u=null;if(l=l.firstBaseUpdate,l!==null){do{var s={lane:l.lane,tag:l.tag,payload:l.payload,callback:null,next:null};u===null?n=u=s:u=u.next=s,l=l.next}while(l!==null);u===null?n=u=e:u=u.next=e}else n=u=e;l={baseState:a.baseState,firstBaseUpdate:n,lastBaseUpdate:u,shared:a.shared,callbacks:a.callbacks},t.updateQueue=l;return}t=l.lastBaseUpdate,t===null?l.firstBaseUpdate=e:t.next=e,l.lastBaseUpdate=e}var bs=!1;function Du(){if(bs){var t=zn;if(t!==null)throw t}}function ku(t,e,l,a){bs=!1;var n=t.updateQueue;ja=!1;var u=n.firstBaseUpdate,s=n.lastBaseUpdate,o=n.shared.pending;if(o!==null){n.shared.pending=null;var m=o,b=m.next;m.next=null,s===null?u=b:s.next=b,s=m;var E=t.alternate;E!==null&&(E=E.updateQueue,o=E.lastBaseUpdate,o!==s&&(o===null?E.firstBaseUpdate=b:o.next=b,E.lastBaseUpdate=m))}if(u!==null){var O=n.baseState;s=0,E=b=m=null,o=u;do{var S=o.lane&-536870913,_=S!==o.lane;if(_?(zt&S)===S:(a&S)===S){S!==0&&S===tn&&(bs=!0),E!==null&&(E=E.next={lane:0,tag:o.tag,payload:o.payload,callback:null,next:null});t:{var Z=t,it=o;S=e;var Et=l;switch(it.tag){case 1:if(Z=it.payload,typeof Z=="function"){O=Z.call(Et,O,S);break t}O=Z;break t;case 3:Z.flags=Z.flags&-65537|128;case 0:if(Z=it.payload,S=typeof Z=="function"?Z.call(Et,O,S):Z,S==null)break t;O=Q({},O,S);break t;case 2:ja=!0}}S=o.callback,S!==null&&(t.flags|=64,_&&(t.flags|=8192),_=n.callbacks,_===null?n.callbacks=[S]:_.push(S))}else _={lane:S,tag:o.tag,payload:o.payload,callback:o.callback,next:null},E===null?(b=E=_,m=O):E=E.next=_,s|=S;if(o=o.next,o===null){if(o=n.shared.pending,o===null)break;_=o,o=_.next,_.next=null,n.lastBaseUpdate=_,n.shared.pending=null}}while(!0);E===null&&(m=O),n.baseState=m,n.firstBaseUpdate=b,n.lastBaseUpdate=E,u===null&&(n.shared.lanes=0),za|=s,t.lanes=s,t.memoizedState=O}}function Df(t,e){if(typeof t!="function")throw Error(f(191,t));t.call(e)}function kf(t,e){var l=t.callbacks;if(l!==null)for(t.callbacks=null,t=0;tu?u:8;var s=lt.T,o={};o.types=s!==null?s.types:null,lt.T=o,Gs(t,!1,e,l);try{var m=n(),b=lt.S;if(b!==null&&b(o,m),m!==null&&typeof m=="object"&&typeof m.then=="function"){var E=n0(m,a);Bu(t,e,E,il(t))}else Bu(t,e,a,il(t))}catch(O){Bu(t,e,{then:function(){},status:"rejected",reason:O},il())}finally{mt.p=u,s!==null&&o.types!==null&&(s.types=o.types),lt.T=s}}function o0(){}function qs(t,e,l,a){if(t.tag!==5)throw Error(f(476));var n=dd(t).queue;fd(t,n,e,re,l===null?o0:function(){return md(t),l(a)})}function dd(t){var e=t.memoizedState;if(e!==null)return e;e={memoizedState:re,baseState:re,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ca,lastRenderedState:re},next:null};var l={};return e.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ca,lastRenderedState:l},next:null},t.memoizedState=e,t=t.alternate,t!==null&&(t.memoizedState=e),e}function md(t){var e=dd(t);e.next===null&&(e=t.alternate.memoizedState),Bu(t,e.next.queue,{},il())}function Ys(){return ze(eu)}function hd(){return ce().memoizedState}function vd(){return ce().memoizedState}function f0(t){for(var e=t.return;e!==null;){switch(e.tag){case 24:case 3:var l=il();t=Ta(l);var a=_a(e,t,l);a!==null&&(Ie(a,e,l),Ru(a,e,l)),e={cache:hs()},t.payload=e;return}e=e.return}}function d0(t,e,l){var a=il();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},Li(t)?gd(e,l):(l=us(t,e,l,a),l!==null&&(Ie(l,t,a),pd(l,e,a)))}function yd(t,e,l){var a=il();Bu(t,e,l,a)}function Bu(t,e,l,a){var n={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null};if(Li(t))gd(e,n);else{var u=t.alternate;if(t.lanes===0&&(u===null||u.lanes===0)&&(u=e.lastRenderedReducer,u!==null))try{var s=e.lastRenderedState,o=u(s,l);if(n.hasEagerState=!0,n.eagerState=o,el(o,s))return ji(t,e,n,0),Wt===null&&bi(),!1}catch{}finally{}if(l=us(t,e,n,a),l!==null)return Ie(l,t,a),pd(l,e,a),!0}return!1}function Gs(t,e,l,a){if(a={lane:2,revertLane:zr(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Li(t)){if(e)throw Error(f(479))}else e=us(t,l,a,2),e!==null&&Ie(e,t,2)}function Li(t){var e=t.alternate;return t===_t||e!==null&&e===_t}function gd(t,e){kn=Bi=!0;var l=t.pending;l===null?e.next=e:(e.next=l.next,l.next=e),t.pending=e}function pd(t,e,l){if((l&4194048)!==0){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,hn(t,l)}}var Zi={readContext:ze,use:Gi,useCallback:ue,useContext:ue,useEffect:ue,useImperativeHandle:ue,useLayoutEffect:ue,useInsertionEffect:ue,useMemo:ue,useReducer:ue,useRef:ue,useState:ue,useDebugValue:ue,useDeferredValue:ue,useTransition:ue,useSyncExternalStore:ue,useId:ue,useHostTransitionStatus:ue,useFormState:ue,useActionState:ue,useOptimistic:ue,useMemoCache:ue,useCacheRefresh:ue,useEffectEvent:ue},Sd={readContext:ze,use:Gi,useCallback:function(t,e){return Ge().memoizedState=[t,e===void 0?null:e],t},useContext:ze,useEffect:ld,useImperativeHandle:function(t,e,l){l=l!=null?l.concat([t]):null,Vi(4194308,4,id.bind(null,e,t),l)},useLayoutEffect:function(t,e){return Vi(4194308,4,t,e)},useInsertionEffect:function(t,e){Vi(4,2,t,e)},useMemo:function(t,e){var l=Ge();e=e===void 0?null:e;var a=t();if(un){fl(!0);try{t()}finally{fl(!1)}}return l.memoizedState=[a,e],a},useReducer:function(t,e,l){var a=Ge();if(l!==void 0){var n=l(e);if(un){fl(!0);try{l(e)}finally{fl(!1)}}}else n=e;return a.memoizedState=a.baseState=n,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:n},a.queue=t,t=t.dispatch=d0.bind(null,_t,t),[a.memoizedState,t]},useRef:function(t){var e=Ge();return t={current:t},e.memoizedState=t},useState:function(t){t=ks(t);var e=t.queue,l=yd.bind(null,_t,e);return e.dispatch=l,[t.memoizedState,l]},useDebugValue:Hs,useDeferredValue:function(t,e){var l=Ge();return Bs(l,t,e)},useTransition:function(){var t=ks(!1);return t=fd.bind(null,_t,t.queue,!0,!1),Ge().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,e,l){var a=_t,n=Ge();if(At){if(l===void 0)throw Error(f(407));l=l()}else{if(l=e(),Wt===null)throw Error(f(349));(zt&127)!==0||Yf(a,e,l)}n.memoizedState=l;var u={value:l,getSnapshot:e};return n.queue=u,ld(Qf.bind(null,a,u,t),[t]),a.flags|=2048,Un(9,{destroy:void 0},Gf.bind(null,a,u,l,e),null),l},useId:function(){var t=Ge(),e=Wt.identifierPrefix;if(At){var l=Bl,a=Hl;l=(a&~(1<<32-Oe(a)-1)).toString(32)+l,e="_"+e+"R_"+l,l=qi++,0<\/script>",u=u.removeChild(u.firstChild);break;case"select":u=typeof a.is=="string"?s.createElement("select",{is:a.is}):s.createElement("select"),a.multiple?u.multiple=!0:a.size&&(u.size=a.size);break;default:u=typeof a.is=="string"?s.createElement(n,{is:a.is}):s.createElement(n)}}u[ge]=e,u[Ke]=a;t:for(s=e.child;s!==null;){if(s.tag===5||s.tag===6)u.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===e)break t;for(;s.sibling===null;){if(s.return===null||s.return===e)break t;s=s.return}s.sibling.return=s.return,s=s.sibling}e.stateNode=u;t:switch(we(u,n,a),n){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break t;case"img":a=!0;break t;default:a=!1}a&&ra(e)}}return Pt(e),e.subtreeFlags&=-33554433,tr(e,e.type,t===null?null:t.memoizedProps,e.pendingProps,l),null;case 6:if(t&&e.stateNode!=null)t.memoizedProps!==a&&ra(e);else{if(typeof a!="string"&&e.stateNode===null)throw Error(f(166));if(t=xe.current,On(e)){if(t=e.stateNode,l=e.memoizedProps,a=null,n=Ee,n!==null)switch(n.tag){case 27:case 5:a=n.memoizedProps}t[ge]=e,t=!!(t.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||Qm(t.nodeValue,l)),t||Sa(e,!0)}else t=$u(t).createTextNode(a),t[ge]=e,e.stateNode=t}return Pt(e),null;case 31:if(l=e.memoizedState,t===null||t.memoizedState!==null){if(a=On(e),l!==null){if(t===null){if(!a)throw Error(f(318));if(t=e.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(f(557));t[ge]=e}else Wa(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;Pt(e),t=!1}else l=fs(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=l),t=!0;if(!t)return e.flags&256?(ll(e),e):(ll(e),null);if((e.flags&128)!==0)throw Error(f(558))}return Pt(e),null;case 13:if(a=e.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(n=On(e),a!==null&&a.dehydrated!==null){if(t===null){if(!n)throw Error(f(318));if(n=e.memoizedState,n=n!==null?n.dehydrated:null,!n)throw Error(f(317));n[ge]=e}else Wa(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;Pt(e),n=!1}else n=fs(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=n),n=!0;if(!n)return e.flags&256?(ll(e),e):(ll(e),null)}return ll(e),(e.flags&128)!==0?(e.lanes=l,e):(l=a!==null,t=t!==null&&t.memoizedState!==null,l&&(a=e.child,n=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(n=a.alternate.memoizedState.cachePool.pool),u=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(u=a.memoizedState.cachePool.pool),u!==n&&(a.flags|=2048)),l!==t&&l&&(e.child.flags|=8192),Ii(e,e.updateQueue),Pt(e),null);case 4:return zl(),t===null&&wr(e.stateNode.containerInfo),e.flags|=67108864,Pt(e),null;case 10:return ua(e.type),Pt(e),null;case 19:if(Es(e),a=e.memoizedState,a===null)return Pt(e),null;if(n=(e.flags&128)!==0,u=a.rendering,u===null)if(n)Yu(a,!1);else{if(ie!==0||t!==null&&(t.flags&128)!==0)for(t=e.child;t!==null;){if(u=Hi(t),u!==null){for(e.flags|=128,Yu(a,!1),t=u.updateQueue,e.updateQueue=t,Ii(e,t),e.subtreeFlags=0,t=l,l=e.child;l!==null;)yf(l,t),l=l.sibling;return wu(e,De.current&1|2),At&&aa(e,a.treeForkCount),e.child}t=t.sibling}a.tail!==null&&$t()>oc&&(e.flags|=128,n=!0,Yu(a,!1),e.lanes=4194304)}else{if(!n)if(t=Hi(u),t!==null){if(e.flags|=128,n=!0,t=t.updateQueue,e.updateQueue=t,Ii(e,t),Yu(a,!0),a.tail===null&&a.tailMode!=="collapsed"&&a.tailMode!=="visible"&&!u.alternate&&!At)return Pt(e),null}else 2*$t()-a.renderingStartTime>oc&&l!==536870912&&(e.flags|=128,n=!0,Yu(a,!1),e.lanes=4194304);a.isBackwards?(u.sibling=e.child,e.child=u):(t=a.last,t!==null?t.sibling=u:e.child=u,a.last=u)}if(a.tail!==null){t=a.tail;t:{for(l=t;l!==null;){if(l.alternate!==null){l=!1;break t}l=l.sibling}l=!0}return a.rendering=t,a.tail=t.sibling,a.renderingStartTime=$t(),t.sibling=null,u=De.current,u=n?u&1|2:u&1,a.tailMode==="visible"||a.tailMode==="collapsed"||!l||At?wu(e,u):(l=u,Qt(Re,e),Qt(De,l),He===null&&(He=e)),At&&aa(e,a.treeForkCount),t}return Pt(e),null;case 22:case 23:return ll(e),Ts(),a=e.memoizedState!==null,t!==null?t.memoizedState!==null!==a&&(e.flags|=8192):a&&(e.flags|=8192),a?(l&536870912)!==0&&(e.flags&128)===0&&(Pt(e),e.subtreeFlags&6&&(e.flags|=8192)):Pt(e),l=e.updateQueue,l!==null&&Ii(e,l.retryQueue),l=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(l=t.memoizedState.cachePool.pool),a=null,e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),a!==l&&(e.flags|=2048),t!==null&&ae(en),null;case 24:return l=null,t!==null&&(l=t.memoizedState.cache),e.memoizedState.cache!==l&&(e.flags|=2048),ua(de),Pt(e),null;case 25:return null;case 30:return e.flags|=33554432,Pt(e),null}throw Error(f(156,e.tag))}function S0(t,e){switch(rs(e),e.tag){case 1:return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 3:return ua(de),zl(),t=e.flags,(t&65536)!==0&&(t&128)===0?(e.flags=t&-65537|128,e):null;case 26:case 27:case 5:return Tl(e),null;case 31:if(e.memoizedState!==null){if(ll(e),e.alternate===null)throw Error(f(340));Wa()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 13:if(ll(e),t=e.memoizedState,t!==null&&t.dehydrated!==null){if(e.alternate===null)throw Error(f(340));Wa()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 19:return Es(e),t=e.flags,t&65536?(e.flags=t&-65537|128,t=e.memoizedState,t!==null&&(t.rendering=null,t.tail=null),e.flags|=4,e):null;case 4:return zl(),null;case 10:return ua(e.type),null;case 22:case 23:return ll(e),Ts(),t!==null&&ae(en),t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 24:return ua(de),null;case 25:return null;default:return null}}function Xd(t,e){switch(rs(e),e.tag){case 3:ua(de),zl();break;case 26:case 27:case 5:Tl(e);break;case 4:zl();break;case 31:e.memoizedState!==null&&ll(e);break;case 13:ll(e);break;case 19:Es(e);break;case 10:ua(e.type);break;case 22:case 23:ll(e),Ts(),t!==null&&ae(en);break;case 24:ua(de)}}function Gu(t,e){try{var l=e.updateQueue,a=l!==null?l.lastEffect:null;if(a!==null){var n=a.next;l=n;do{if((l.tag&t)===t){a=void 0;var u=l.create,s=l.inst;a=u(),s.destroy=a}l=l.next}while(l!==n)}}catch(o){Lt(e,e.return,o)}}function Aa(t,e,l){try{var a=e.updateQueue,n=a!==null?a.lastEffect:null;if(n!==null){var u=n.next;a=u;do{if((a.tag&t)===t){var s=a.inst,o=s.destroy;if(o!==void 0){s.destroy=void 0,n=e;var m=l,b=o;try{b()}catch(E){Lt(n,m,E)}}}a=a.next}while(a!==u)}}catch(E){Lt(e,e.return,E)}}function Ld(t){var e=t.updateQueue;if(e!==null){var l=t.stateNode;try{kf(e,l)}catch(a){Lt(t,t.return,a)}}}function Zd(t,e,l){l.props=cn(t.type,t.memoizedProps),l.state=t.memoizedState;try{l.componentWillUnmount()}catch(a){Lt(t,e,a)}}function ql(t,e){try{var l=t.ref;if(l!==null){switch(t.tag){case 26:case 27:case 5:var a=t.stateNode;break;case 30:var n=t.stateNode,u=ta(t.memoizedProps,n);(n.ref===null||n.ref.name!==u)&&(n.ref=Pm(u)),a=n.ref;break;case 7:if(t.stateNode===null){var s=new cl(t);g(t.child,!1,ry,s,void 0,void 0),t.stateNode=s}a=t.stateNode;break;default:a=t.stateNode}typeof l=="function"?t.refCleanup=l(a):l.current=a}}catch(o){Lt(t,e,o)}}function ke(t,e){var l=t.ref,a=t.refCleanup;if(l!==null)if(typeof a=="function")try{a()}catch(n){Lt(t,e,n)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof l=="function")try{l(null)}catch(n){Lt(t,e,n)}else l.current=null}function Pi(t,e){if((t.tag===5||t.tag===27||t.tag===6)&&t.alternate===null&&e!==null)for(var l=0;l title"))),we(a,e,l),a[ge]=t,_e(a),e=a;break t;case"link":if(u=yh("link","href",n).get(e+(l.href||""))){for(s=0;sEt&&(s=Et,Et=it,it=s);var x=af(o,it),v=af(o,Et);if(x&&v&&(_.rangeCount!==1||_.anchorNode!==x.node||_.anchorOffset!==x.offset||_.focusNode!==v.node||_.focusOffset!==v.offset)){var T=O.createRange();T.setStart(x.node,x.offset),_.removeAllRanges(),it>Et?(_.addRange(T),_.extend(v.node,v.offset)):(T.setEnd(v.node,v.offset),_.addRange(T))}}}}for(O=[],_=o;_=_.parentNode;)_.nodeType===1&&O.push({element:_,left:_.scrollLeft,top:_.scrollTop});for(typeof o.focus=="function"&&o.focus(),o=0;ol?32:l,lt.T=null,l=br,br=null;var u=Ml,s=Ll;if(le=0,fn=Ml=null,Ll=0,(Gt&6)!==0)throw Error(f(331));var o=Gt;if(Gt|=4,vm(u.current),dm(u,u.current,s,l),Gt=o,Ju(0,!1),Me&&typeof Me.onPostCommitFiberRoot=="function")try{Me.onPostCommitFiberRoot(Wl,u)}catch{}return!0}finally{mt.p=n,lt.T=a,Om(t,e)}}function zm(t,e,l){e=hl(l,e),e=Xs(t.stateNode,e,2),t=_a(t,e,2),t!==null&&(Te(t,2),Zl(t))}function Lt(t,e,l){if(t.tag===3)zm(t,t,l);else for(;e!==null;){if(e.tag===3){zm(e,t,l);break}else if(e.tag===1){var a=e.stateNode;if(typeof e.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(Ra===null||!Ra.has(a))){t=hl(l,t),l=Cd(2),a=_a(e,l,2),a!==null&&(Ad(l,a,e,t),Te(a,2),Zl(a));break}}e=e.return}}function Ar(t,e,l){var a=t.pingCache;if(a===null){a=t.pingCache=new T0;var n=new Set;a.set(e,n)}else n=a.get(e),n===void 0&&(n=new Set,a.set(e,n));n.has(l)||(pr=!0,n.add(l),t=z0.bind(null,t,e,l),e.then(t,t))}function z0(t,e,l){var a=t.pingCache;a!==null&&a.delete(e),t.pingedLanes|=t.suspendedLanes&l,t.warmLanes&=~l,Wt===t&&(zt&l)===l&&((ie===4||ie===3&&(zt&62914560)===zt&&300>$t()-rc)&&(Gt&2)===0?Kn(t,0):sc|=l,Qn===zt&&(Qn=0)),Zl(t)}function Rm(t,e){e===0&&(e=wt()),t=Ja(t,e),t!==null&&(Te(t,e),Zl(t))}function R0(t){var e=t.memoizedState,l=0;e!==null&&(l=e.retryLane),Rm(t,l)}function D0(t,e){var l=0;switch(t.tag){case 31:case 13:var a=t.stateNode,n=t.memoizedState;n!==null&&(l=n.retryLane);break;case 19:a=t.stateNode;break;case 22:a=t.stateNode._retryCache;break;default:throw Error(f(314))}a!==null&&a.delete(e),Rm(t,l)}function k0(t,e){return Pe(t,e)}var Fn=null,Wn=null,Mr=!1,gc=!1,Or=!1,ka=0;function Zl(t){t!==Wn&&t.next===null&&(Wn===null?Fn=Wn=t:Wn=Wn.next=t),gc=!0,Mr||(Mr=!0,U0())}function Ju(t,e){if(!Or&&gc){Or=!0;do for(var l=!1,a=Fn;a!==null;){if(t!==0){var n=a.pendingLanes;if(n===0)var u=0;else{var s=a.suspendedLanes,o=a.pingedLanes;u=(1<<31-Oe(42|t)+1)-1,u&=n&~(s&~o),u=u&201326741?u&201326741|1:u?u|2:0}u!==0&&(l=!0,Um(a,u))}else u=zt,u=A(a,a===Wt?u:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(u&3)===0||$(a,u)||(l=!0,Um(a,u));a=a.next}while(l);Or=!1}}function w0(){Dm()}function Dm(){gc=Mr=!1;var t=0;ka!==0&&K0()&&(t=ka);for(var e=$t(),l=null,a=Fn;a!==null;){var n=a.next,u=km(a,e);u===0?(a.next=null,l===null?Fn=n:l.next=n,n===null&&(Wn=l)):(l=a,(t!==0||(u&3)!==0)&&(gc=!0)),a=n}le!==0&&le!==5||Ju(t),ka!==0&&(ka=0)}function km(t,e){for(var l=t.suspendedLanes,a=t.pingedLanes,n=t.expirationTimes,u=t.pendingLanes&-62914561;0o)break;var E=m.transferSize,O=m.initiatorType;E&&Vm(O)&&(m=m.responseEnd,s+=E*(mjc){it.length=Z;break}T=new Promise(P0.bind(T)),it.push(T)}}}if(0"u"?null:document;function dh(t,e,l){var a=In;if(a&&typeof e=="string"&&e){var n=dl(e);n='link[rel="'+t+'"][href="'+n+'"]',typeof l=="string"&&(n+='[crossorigin="'+l+'"]'),fh.has(n)||(fh.add(n),t={rel:t,crossOrigin:l,href:e},a.querySelector(n)===null&&(e=a.createElement("link"),we(e,"link",t),_e(e),a.head.appendChild(e)))}}function py(t){fa.D(t),dh("dns-prefetch",t,null)}function Sy(t,e){fa.C(t,e),dh("preconnect",t,e)}function xy(t,e,l){fa.L(t,e,l);var a=In;if(a&&t&&e){var n='link[rel="preload"][as="'+dl(e)+'"]';e==="image"&&l&&l.imageSrcSet?(n+='[imagesrcset="'+dl(l.imageSrcSet)+'"]',typeof l.imageSizes=="string"&&(n+='[imagesizes="'+dl(l.imageSizes)+'"]')):n+='[href="'+dl(t)+'"]';var u=n;switch(e){case"style":u=Pn(t);break;case"script":u=tu(t)}if(!(xl.has(u)||(t=Q({rel:"preload",href:e==="image"&&l&&l.imageSrcSet?void 0:t,as:e},l),xl.set(u,t),a.querySelector(n)!==null||e==="style"&&a.querySelector(Pu(u))||e==="script"&&a.querySelector(ti(u))))){var s=a.createElement("link");we(s,"link",t),e==="style"&&(s[ri]=!0,s.onload=s.onerror=function(){_o(s)}),_e(s),a.head.appendChild(s)}}}function by(t,e){fa.m(t,e);var l=In;if(l&&t){var a=e&&typeof e.as=="string"?e.as:"script",n='link[rel="modulepreload"][as="'+dl(a)+'"][href="'+dl(t)+'"]',u=n;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":u=tu(t)}if(!xl.has(u)&&(t=Q({rel:"modulepreload",href:t},e),xl.set(u,t),l.querySelector(n)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(l.querySelector(ti(u)))return}a=l.createElement("link"),we(a,"link",t),_e(a),l.head.appendChild(a)}}}function jy(t,e,l){fa.S(t,e,l);var a=In;if(a&&t){var n=gn(a).hoistableStyles,u=Pn(t);e=e||"default";var s=n.get(u);if(!s){var o={loading:0,preload:null};if(s=a.querySelector(Pu(u)))o.loading=5;else{t=Q({rel:"stylesheet",href:t,"data-precedence":e},l),(l=xl.get(u))&&$r(t,l);var m=s=a.createElement("link");_e(m),we(m,"link",t),m._p=new Promise(function(b,E){m.onload=b,m.onerror=E}),m.addEventListener("load",function(){o.loading|=1}),m.addEventListener("error",function(){o.loading|=2}),o.loading|=4,xc(s,e,a)}s={type:"stylesheet",instance:s,count:1,state:o},n.set(u,s)}}}function Ty(t,e){fa.X(t,e);var l=In;if(l&&t){var a=gn(l).hoistableScripts,n=tu(t),u=a.get(n);u||(u=l.querySelector(ti(n)),u||(t=Q({src:t,async:!0},e),(e=xl.get(n))&&Ir(t,e),u=l.createElement("script"),_e(u),we(u,"link",t),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function _y(t,e){fa.M(t,e);var l=In;if(l&&t){var a=gn(l).hoistableScripts,n=tu(t),u=a.get(n);u||(u=l.querySelector(ti(n)),u||(t=Q({src:t,async:!0,type:"module"},e),(e=xl.get(n))&&Ir(t,e),u=l.createElement("script"),_e(u),we(u,"link",t),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function mh(t,e,l,a){var n=(n=xe.current)?Iu(n):null;if(!n)throw Error(f(446));switch(t){case"meta":case"title":return null;case"style":return typeof l.precedence=="string"&&typeof l.href=="string"?(l=Pn(l.href),e=gn(n).hoistableStyles,a=e.get(l),a||(a={type:"style",instance:null,count:0,state:null},e.set(l,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(l.rel==="stylesheet"&&typeof l.href=="string"&&typeof l.precedence=="string"){t=Pn(l.href);var u=gn(n).hoistableStyles,s=u.get(t);if(s||(n=n.ownerDocument||n,s={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},u.set(t,s),(u=n.querySelector(Pu(t)))?u._p||(s.instance=u,s.state.loading=5):(u=xl.get(t),u||(u={rel:"preload",as:"style",href:l.href,crossOrigin:l.crossOrigin,integrity:l.integrity,media:l.media,hrefLang:l.hrefLang,referrerPolicy:l.referrerPolicy},xl.set(t,u)),Ey(n,t,u,s.state))),e&&a===null)throw Error(f(528,""));return s}if(e&&a!==null)throw Error(f(529,""));return null;case"script":return e=l.async,l=l.src,typeof l=="string"&&e&&typeof e!="function"&&typeof e!="symbol"?(l=tu(l),e=gn(n).hoistableScripts,a=e.get(l),a||(a={type:"script",instance:null,count:0,state:null},e.set(l,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(f(444,t))}}function Pn(t){return'href="'+dl(t)+'"'}function Pu(t){return'link[rel="stylesheet"]['+t+"]"}function hh(t){return Q({},t,{"data-precedence":t.precedence,precedence:null})}function Ey(t,e,l,a){if(e=t.querySelector('link[rel="preload"][as="style"]['+e+"]")){if(e[ri]!==!0){a.loading=1;return}}else e=t.createElement("link"),e[ri]=!0,e.onload=e.onerror=_o.bind(null,e),we(e,"link",l),_e(e),t.head.appendChild(e);a.preload=e,e.addEventListener("load",function(){return a.loading|=1}),e.addEventListener("error",function(){return a.loading|=2})}function tu(t){return'[src="'+dl(t)+'"]'}function ti(t){return"script[async]"+t}function vh(t,e,l){if(e.count++,e.instance===null)switch(e.type){case"style":var a=t.querySelector('style[data-href~="'+dl(l.href)+'"]');if(a)return e.instance=a,_e(a),a;var n=Q({},l,{"data-href":l.href,"data-precedence":l.precedence,href:null,precedence:null});return a=(t.ownerDocument||t).createElement("style"),_e(a),we(a,"style",n),xc(a,l.precedence,t),e.instance=a;case"stylesheet":n=Pn(l.href);var u=t.querySelector(Pu(n));if(u)return e.state.loading|=4,e.instance=u,_e(u),u;a=hh(l),(n=xl.get(n))&&$r(a,n),u=(t.ownerDocument||t).createElement("link"),_e(u);var s=u;return s._p=new Promise(function(o,m){s.onload=o,s.onerror=m}),we(u,"link",a),e.state.loading|=4,xc(u,l.precedence,t),e.instance=u;case"script":return u=tu(l.src),(n=t.querySelector(ti(u)))?(e.instance=n,_e(n),n):(a=l,(n=xl.get(u))&&(a=Q({},l),Ir(a,n)),t=t.ownerDocument||t,n=t.createElement("script"),_e(n),we(n,"link",a),t.head.appendChild(n),e.instance=n);case"void":return null;default:throw Error(f(443,e.type))}else e.type==="stylesheet"&&(e.state.loading&4)===0&&(a=e.instance,e.state.loading|=4,xc(a,l.precedence,t));return e.instance}function xc(t,e,l){for(var a=l.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),n=a.length?a[a.length-1]:null,u=n,s=0;s title"):null)}function Ny(t,e,l){if(l===1||e.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof e.precedence!="string"||typeof e.href!="string"||e.href==="")break;return!0;case"link":if(typeof e.rel!="string"||typeof e.href!="string"||e.href===""||e.onLoad||e.onError)break;switch(e.rel){case"stylesheet":return t=e.disabled,typeof e.precedence=="string"&&t==null;default:return!0}case"script":if(e.async&&typeof e.async!="function"&&typeof e.async!="symbol"&&!e.onLoad&&!e.onError&&e.src&&typeof e.src=="string")return!0}return!1}function gh(t,e){return t==="img"&&e.src!=null&&e.src!==""&&e.onLoad==null&&e.loading!=="lazy"}function ph(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function Sh(t){return(t.width||100)*(t.height||100)*(typeof devicePixelRatio=="number"?devicePixelRatio:1)*.25}function xh(t,e){typeof e.decode=="function"&&(t.imgCount++,e.complete||(t.imgBytes+=Sh(e),t.suspenseyImages.push(e)),t=My.bind(t),e.decode().then(t,t))}function Cy(t,e,l,a){if(l.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(l.state.loading&4)===0){if(l.instance===null){var n=Pn(a.href),u=e.querySelector(Pu(n));if(u){e=u._p,e!==null&&typeof e=="object"&&typeof e.then=="function"&&(t.count++,t=ei.bind(t),e.then(t,t)),l.state.loading|=4,l.instance=u,_e(u);return}u=e.ownerDocument||e,a=hh(a),(n=xl.get(n))&&$r(a,n),u=u.createElement("link"),_e(u);var s=u;s._p=new Promise(function(o,m){s.onload=o,s.onerror=m}),we(u,"link",a),l.instance=u}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(l,e),(e=l.state.preload)&&(l.state.loading&3)===0&&(t.count++,l=ei.bind(t),e.addEventListener("load",l),e.addEventListener("error",l))}}var jc=0;function Ay(t,e){return t.stylesheets&&t.count===0&&_c(t,t.stylesheets),0jc?50:800)+e);return t.unsuspend=l,function(){t.unsuspend=null,clearTimeout(a),clearTimeout(n)}}:null}function bh(t){if(t.count===0&&(t.imgCount===0||!t.waitingForImages)){if(t.stylesheets)_c(t,t.stylesheets);else if(t.unsuspend){var e=t.unsuspend;t.unsuspend=null,e()}}}function ei(){this.count--,bh(this)}function My(){this.imgCount--,bh(this)}var Tc=null;function _c(t,e){t.stylesheets=null,t.unsuspend!==null&&(t.count++,Tc=new Map,e.forEach(Oy,t),Tc=null,ei.call(t))}function Oy(t,e){if(!(e.state.loading&4)){var l=Tc.get(t);if(l)var a=l.get(null);else{l=new Map,Tc.set(t,l);for(var n=t.querySelectorAll("link[data-precedence],style[data-precedence]"),u=0;u"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(r){console.error(r)}}return i(),so.exports=Ky(),so.exports}var Fy=Jy();const Wy=Wh(Fy),$h=d.createContext(null);function Kt(){const i=d.useContext($h);if(!i)throw new Error("ConsoleContext missing");return i}const ft=encodeURIComponent,W={health:"/health",relayModels:"/relay/models",quotaMe:"/users/me/quota",userKeys:"/users/me/keys",userKey:i=>"/users/me/keys/"+ft(i),githubRepos:"/github/repos",projects:"/projects",project:i=>"/projects/"+ft(i),projectMembers:i=>"/projects/"+ft(i)+"/members",projectMember:(i,r)=>"/projects/"+ft(i)+"/members/"+ft(r),projectSkills:i=>"/projects/"+ft(i)+"/skills",projectApps:i=>"/projects/"+ft(i)+"/apps",projectMcp:i=>"/projects/"+ft(i)+"/mcp-servers",projectMcpReload:i=>"/projects/"+ft(i)+"/mcp-servers/reload",projectMcpOauth:(i,r)=>"/projects/"+ft(i)+"/mcp-servers/"+ft(r)+"/oauth-login",projectAutomations:i=>"/projects/"+ft(i)+"/automations",projectAutomation:(i,r)=>"/projects/"+ft(i)+"/automations/"+ft(r),projectAutomationCancel:(i,r)=>"/projects/"+ft(i)+"/automations/"+ft(r)+"/cancel",projectTriage:i=>"/projects/"+ft(i)+"/triage",projectTodos:i=>"/projects/"+ft(i)+"/todos",projectTodo:(i,r)=>"/projects/"+ft(i)+"/todos/"+ft(r),workers:"/workers",environments:i=>"/environments?project_id="+ft(i),environmentsCreate:"/environments",environmentArchive:i=>"/environments/"+ft(i)+"/archive",tasks:i=>"/tasks?project_id="+ft(i),tasksPage:(i,r,h,f,p,N)=>{let C="/tasks?project_id="+ft(i)+"&limit="+r+"&offset="+h+"&order=desc";return f&&(C+="&status="+ft(f)),p&&(C+="&executor="+ft(p)),N&&N.trim()&&(C+="&q="+ft(N.trim())),C},tasksByParent:(i,r)=>"/tasks?project_id="+ft(i)+"&parent_task_id="+ft(r),tasksCreate:"/tasks",task:i=>"/tasks/"+ft(i),taskAction:(i,r)=>"/tasks/"+ft(i)+"/"+r,taskFork:i=>"/tasks/"+ft(i)+"/fork",taskContinue:i=>"/tasks/"+ft(i)+"/continue",taskTrace:i=>"/tasks/"+ft(i)+"/trace",taskTraceStream:i=>"/tasks/"+ft(i)+"/trace/stream",taskAgents:i=>"/tasks/"+ft(i)+"/agents",taskReviews:i=>"/tasks/"+ft(i)+"/reviews",taskReviewFindings:(i,r)=>"/tasks/"+ft(i)+"/reviews/"+ft(r)+"/findings",taskApprovals:i=>"/tasks/"+ft(i)+"/approvals",taskApprovalDecision:(i,r,h)=>"/tasks/"+ft(i)+"/approvals/"+ft(r)+"/"+h,taskFollowups:i=>"/tasks/"+ft(i)+"/followups",taskChatHistory:i=>"/tasks/"+ft(i)+"/chat-history",taskMessages:i=>"/tasks/"+ft(i)+"/messages",taskMessagesTo:i=>"/tasks/"+ft(i)+"/messages",taskThread:i=>"/tasks/"+ft(i)+"/thread",taskThreads:i=>"/tasks/"+ft(i)+"/threads",taskThreadAction:(i,r)=>"/tasks/"+ft(i)+"/thread/"+r,taskCost:i=>"/tasks/"+ft(i)+"/cost",taskBrowserState:i=>"/tasks/"+ft(i)+"/browser/state",taskBrowserStream:i=>"/tasks/"+ft(i)+"/browser/stream",taskBrowserInput:i=>"/tasks/"+ft(i)+"/browser/input",taskBrowserControl:i=>"/tasks/"+ft(i)+"/browser/control",uploads:"/uploads",adminQuotas:"/admin/quotas",adminQuotaUser:i=>"/admin/quotas?user_id="+ft(i),adminSweep:"/admin/sweep",adminBackup:"/admin/backup"},$y={exec:"命令",file:"文件",call:"调用",tool:"工具",message:"消息",reasoning:"思考",plan:"计划","agent-start":"子代理","agent-done":"子代理"},uu=["completed","failed","interrupted","merged","merge-failed"],Iy={queued:"排队中,尚未被 worker 认领",running:"运行中,模型回合进行中",completed:"模型回合结束、改动在 worktree 里尚未合并",failed:"模型回合失败,可重试",interrupted:"已被取消/中断,可重试",merged:"控制面已标记 merged,不代表 git 主分支已合入","merge-failed":"合并落盘失败,可重试"},Lh=["low","medium","high","xhigh","max"],Py=["viewer","contributor","maintainer","owner"],So="codex-console-key";function sl(){try{return localStorage.getItem(So)||""}catch{return""}}function tg(i){localStorage.setItem(So,i)}function eg(){localStorage.removeItem(So)}function Ae(i){return i?i.slice(0,8):""}function Kl(i){return i?new Date(i*1e3).toLocaleString():""}async function at(i,r="GET",h){const f={"Content-Type":"application/json"},p=sl();p&&(f["X-API-Key"]=p);const N={method:r,headers:f};h!==void 0&&(N.body=JSON.stringify(h));const C=await fetch(i,N);let j=null;try{j=await C.json()}catch{j=null}if(!C.ok){const z=j;let R=z&&z.error?z.error:"HTTP "+C.status;throw C.status===401&&(R+="(key 未填或无效)"),C.status===429&&(R+="(触发限流,稍后再试)"),C.status===402&&(z&&z.unpriced_token_cap!==void 0?R+="(无价 token 已满,联系管理员调整)":z&&z.error==="coordinator_quota_reserve_exceeded"?R="协调员分工预留不足:剩余美元配额不够覆盖全部分工,子任务费用记父任务创建人账,整个父任务已被挡下(未建任何子任务)":R+="(美元配额已满,联系管理员调整)"),new Error(R)}return j}async function lg(i){const r=new FormData;r.append("paths",JSON.stringify(i.map(p=>p.path))),i.forEach((p,N)=>r.append("file"+N,p.file));const h=await fetch(W.uploads,{method:"POST",headers:{"X-API-Key":sl()},body:r}),f=await h.json().catch(()=>({}));if(!h.ok)throw new Error(f&&f.error||"HTTP "+h.status);return f.upload_id}function ag(i,r,h){const f=[],p={},N=(C,j)=>{if(C)for(let z=0;zC.name),N(r,C=>C.webkitRelativePath||C.name),N(h,C=>C.name),f}function xo(i){const r=Date.now()/1e3;let h=null;if(typeof i.last_seen=="number"?h=r-i.last_seen:typeof i.last_seen_seconds_ago=="number"&&(h=i.last_seen_seconds_ago),(h===null||h<0)&&(h=null),h===null)return{text:"未知",age:null};const f=Math.floor(h);return f<60?{text:f+" 秒前",age:h}:f<3600?{text:Math.floor(f/60)+" 分钟前",age:h}:{text:Math.floor(f/3600)+" 小时前",age:h}}function zc(i){const r=i.message||"";return r.includes("forbidden")?"没有权限:只有项目 owner 或全局管理员可以管理成员":r.includes("already_member")?"该用户已是成员,改角色请用成员行的下拉框":r.includes("cannot demote the only owner")?"不能把唯一 owner 降为非 owner:请先把另一位成员升为 owner":r.includes("user not found")?"找不到该用户:请核对 user_id 或用户名是否正确":r.includes("member not found")?"该用户不是本项目成员":r.includes("unknown role")?"角色无效:只能是 viewer / contributor / maintainer / owner":r.includes("project not found")?"项目不存在(可能已被删除)":"成员操作失败:"+r}function mo(i){const r=i.message||"";return r.includes("forbidden")?"无全局管理员权限:服务端返回 403(CODEX_GLOBAL_ADMINS 未包含当前用户),请联系管理员":r.includes("user not found")?"找不到该用户:请核对用户 id 是否正确":"管理操作失败:"+r}function ho(i){return i==null?"不限额":"$"+(i/1e6).toFixed(2)}function go(i){const r=$y[i.kind||""]||i.kind||"?";let h="";return i.kind==="exec"?h=(i.command||"")+"(exit="+(i.exitCode??"")+","+(i.durationMs??"")+"ms,cwd="+(i.cwd||"")+")":i.kind==="file"?h=(i.path||"")+" "+(i.change||"")+" "+(i.status||""):i.kind==="message"?h=(i.phase?"["+i.phase+"] ":"")+(i.text||"")+(i.truncated?"(链路摘要已截断至 300 字,完整回复见右侧任务结果)":""):i.kind==="reasoning"?h=i.summary||"":i.kind==="tool"?h=(i.server||"")+"/"+(i.tool||"")+" "+(i.status||""):i.kind==="call"?h=i.name||"":i.kind==="plan"?h=i.text||"":i.kind==="agent-start"?h="开始:"+(i.name||"")+(i.prompt?"("+i.prompt+")":""):i.kind==="agent-done"?h="结束:"+(i.name||"")+" "+(i.status||""):h=JSON.stringify(i).slice(0,300),"["+r+"] "+h}function ng(){const[i,r]=d.useState(!!sl()),[h,f]=d.useState(sl()?"已保存到本浏览器(只用于调本站 API)":"未填 key,数据接口会返回 401"),[p,N]=d.useState("未加载"),[C,j]=d.useState("未加载"),[z,R]=d.useState("填 key 后判断权限…"),[q,g]=d.useState(!1),[U,F]=d.useState(""),[X,J]=d.useState(""),[V,vt]=d.useState([]),[St,jt]=d.useState(""),[K,Nt]=d.useState(""),[w,Q]=d.useState([]),[nt,Dt]=d.useState(null),[qt,dt]=d.useState("未加载"),[kt,H]=d.useState(""),[ot,yt]=d.useState([]),[G,Y]=d.useState(""),[tt,gt]=d.useState(""),[xt,Se]=d.useState([]),[L,se]=d.useState([]),[y,D]=d.useState([]),[ut,et]=d.useState("先选项目"),[Ct,bt]=d.useState(""),[Tt,lt]=d.useState(""),[mt,re]=d.useState(""),[Ga,jl]=d.useState(""),[Xe,ae]=d.useState([]),[Qt,ye]=d.useState(""),[Jl,xe]=d.useState(""),[da,Le]=d.useState(""),[zl,Rl]=d.useState(!1),[Tl,Be]=d.useState(""),[oe,fe]=d.useState(""),[rl,Fl]=d.useState([]),[ma,Dl]=d.useState(!1),ee=d.useRef("");ee.current=St;const Pe=d.useRef("");Pe.current=oe;const qe=d.useCallback(async()=>{try{const A=await at(W.health);j("backend="+A.backend+" db_ok="+A.db_ok+" worker_ok="+A.worker_ok+" commit="+String(A.commit||"").slice(0,7))}catch(A){j("加载失败:"+A.message)}},[]),_l=d.useCallback(async()=>{if(!sl()){N("未填 key,配额不显示");return}try{const A=await at(W.quotaMe),$=((A.used_usd_micros||0)/1e6).toFixed(2),st=A.quota_usd_micros===null||A.quota_usd_micros===void 0?"美元:本月用量 $"+$+"(不限额)":"美元:本月用量 $"+$+" / $"+((A.quota_usd_micros||0)/1e6).toFixed(2)+"(剩余 $"+((A.remaining_usd_micros||0)/1e6).toFixed(2)+")",Yt=A.unpriced_token_cap===void 0||A.unpriced_tokens_used===void 0?"无价 token:未返回(旧服务端)":"无价 token:"+A.unpriced_tokens_used+" / "+A.unpriced_token_cap+" tokens";N(st+" | "+Yt)}catch(A){N("配额加载失败:"+A.message)}},[]),ol=d.useCallback(async()=>{if(!sl()){R("未填 key,管理区不可用"),g(!1);return}try{const A=await fetch(W.adminQuotas,{headers:{"X-API-Key":sl()}});A.status===400?(R("已确认为全局管理员,管理操作可用"),g(!0)):A.status===403?(R("管理区仅全局管理员可用:当前 key 无全局管理员权限(服务端返回 403)"),g(!1)):A.status===401?(R("key 无效,管理区不可用(服务端返回 401)"),g(!1)):(R("管理权限判断失败(HTTP "+A.status+"),管理操作暂不可用"),g(!1))}catch(A){R("管理权限判断失败:"+A.message),g(!1)}},[]),$t=d.useCallback(async()=>{try{const $=(await at(W.projects)).projects||[];vt($),Nt($.length+" 个项目"),ee.current&&$.some(st=>st.id===ee.current)}catch(A){Nt("加载失败:"+A.message)}},[]),kl=d.useCallback(async()=>{try{const A=await at(W.githubRepos);Q(A.repos||[])}catch{}},[]),mn=d.useCallback(async()=>{try{const A=await at(W.relayModels);Fl(A.models||[]),Dl(!1)}catch{Dl(!0)}},[]),Qa=d.useCallback(async()=>{try{const A=await at(W.workers);if(!Array.isArray(A)&&A.workers===void 0&&A.workers_total!==void 0){Dt(null),dt(A.workers_total?"在线 "+A.workers_online+" / 离线 "+A.workers_offline+"(共 "+A.workers_total+";完整 worker 清单仅全局管理员可见)":"无 worker 注册"),H("");return}const $=Array.isArray(A)?A:A.workers||[];Dt($),dt($.length?"":"无 worker 注册"),H("")}catch(A){H("加载失败:"+A.message)}},[]),be=d.useCallback(async A=>{const $=A!==void 0?A:ee.current,st=Pe.current;if(!$){ae([]),ye(""),fe("");return}try{const wt=(await at(W.environments($))).environments||[];ae(wt),ye(wt.length+" 个环境"),(st&&wt.some(Te=>Te.id===st&&Te.status==="ready")||st&&wt.some(Te=>Te.id===st))&&fe(st);const je=V.find(Te=>Te.id===$);je&&je.repo_url?(Le("仓库:"+je.repo_url),Rl(!1)):je?(Le("该项目未绑定仓库,环境无法创建——先选一个仓库保存"),Rl(!0)):(Le(""),Rl(!1))}catch(Yt){xe("加载失败:"+Yt.message)}},[V]),Va=d.useCallback(async A=>{const $=A!==void 0?A:ee.current;if(!$){yt([]);return}try{const st=await at(W.projectMembers($));yt(st.members||[]),gt("")}catch(st){gt("成员加载失败:"+zc(st))}},[]),ha=d.useCallback(async A=>{const $=A!==void 0?A:ee.current;if(!$){Se([]),se([]),D([]),et("先选项目");return}et("");try{const st=await at(W.projectSkills($));Se(st.skills||[])}catch(st){bt("技能加载失败:"+st.message)}try{const st=await at(W.projectApps($));se(st.apps||[])}catch(st){bt("应用加载失败:"+st.message)}try{const st=await at(W.projectMcp($));D(st.servers||st.mcp_servers||[])}catch(st){bt("MCP 服务加载失败:"+st.message)}},[]),su=d.useCallback(A=>{if(!A.trim()){f("key 为空,未保存");return}tg(A.trim()),r(!0),f("已保存到本浏览器(只用于调本站 API)")},[]),ru=d.useCallback(()=>{eg(),r(!1),f("未填 key,数据接口会返回 401"),ol()},[ol]),Wl=d.useCallback(async(A,$)=>{if(!A.trim()){Nt("项目名必填");return}const st={name:A.trim()};$.trim()&&(st.repo_url=$.trim());try{await at(W.projects,"POST",st),Nt("项目已建"),await $t()}catch(Yt){Nt("新建失败:"+Yt.message)}},[$t]),Me=d.useCallback(async(A,$,st)=>{const Yt=ee.current;if(!Yt){xe("先选项目");return}if(!A.trim()){xe("环境名必填");return}const wt={project_id:Yt,name:A.trim()};$.trim()&&(wt.branch=$.trim()),st&&(wt.executor=st);try{await at(W.environmentsCreate,"POST",wt),xe("环境已建,worker 准备中(creating → ready,失败会在列表里写原因)"),await be(Yt)}catch(je){xe("新建失败:"+je.message)}},[be]),fl=d.useCallback(async A=>{if(window.confirm("确定归档该环境吗?归档后新任务不能再选它,进行中的任务不受影响(有进行中任务在用它时归档会被拒绝)。"))try{await at(W.environmentArchive(A),"POST",{}),await be()}catch($){xe("归档失败:"+$.message)}},[be]),Oe=d.useCallback(async A=>{const $=ee.current;if(!$){Be("先选项目");return}if(!A){Be("先选一个仓库");return}try{await at(W.project($),"PATCH",{repo_url:A}),Be("仓库已保存,现在可以建环境了"),await $t(),await be($)}catch(st){Be("保存失败:"+st.message)}},[$t,be]),ou=d.useCallback(async(A,$)=>{const st=ee.current;if(!st){Y("先选项目");return}if(!A.trim()){Y("请填成员 user_id 或用户名");return}try{await at(W.projectMembers(st),"POST",{user_id:A.trim(),role:$})}catch(Yt){if(Yt.message.includes("user not found"))try{await at(W.projectMembers(st),"POST",{username:A.trim(),role:$})}catch(wt){Y(zc(wt));return}else{Y(zc(Yt));return}}Y("已邀请成员"),await Va(st)},[Va]),fu=d.useCallback(async(A,$)=>{const st=ee.current;if(st)try{await at(W.projectMember(st,A),"PATCH",{role:$}),yt(Yt=>Yt.map(wt=>wt.user_id===A?{...wt,role:$}:wt)),Y("角色已改为 "+$)}catch(Yt){Y(zc(Yt))}},[]),du=d.useCallback(async()=>{const A=ee.current;if(!A){lt("先选项目");return}try{await at(W.projectMcpReload(A),"POST",{}),lt("配置已重载"),await ha(A)}catch($){lt("重载失败:"+$.message)}},[ha]),B=d.useCallback(async A=>{const $=ee.current;if(!$){re("先选项目");return}if(!A.trim()){re("服务名不能为空");return}try{const st=await at(W.projectMcpOauth($,A.trim()),"POST",{}),Yt=st.url||st.auth_url||st.login_url||st.authorization_url||"",wt=st.steps||st.instructions||"";let je=`登录尚未完成:请在浏览器打开下面的链接或按步骤完成,完成后回到本页刷新 MCP 状态。 +`+a.stack}}var ee=Object.prototype.hasOwnProperty,Pe=i.unstable_scheduleCallback,qe=i.unstable_cancelCallback,_l=i.unstable_shouldYield,ol=i.unstable_requestPaint,$t=i.unstable_now,kl=i.unstable_getCurrentPriorityLevel,mn=i.unstable_ImmediatePriority,Va=i.unstable_UserBlockingPriority,be=i.unstable_NormalPriority,Qa=i.unstable_LowPriority,ha=i.unstable_IdlePriority,su=i.log,ru=i.unstable_setDisableYieldValue,Wl=null,Me=null;function fl(t){if(typeof su=="function"&&ru(t),Me&&typeof Me.setStrictMode=="function")try{Me.setStrictMode(Wl,t)}catch{}}var Oe=Math.clz32?Math.clz32:du,ou=Math.log,fu=Math.LN2;function du(t){return t>>>=0,t===0?32:31-(ou(t)/fu|0)|0}var B=256,k=262144,P=4194304;function ct(t){var e=t&42;if(e!==0)return e;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&-t;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function A(t,e,l){var a=t.pendingLanes;if(a===0)return 0;var n=0,u=t.suspendedLanes,s=t.pingedLanes;t=t.warmLanes;var o=a&134217727;return o!==0?(a=o&~u,a!==0?n=ct(a):(s&=o,s!==0?n=ct(s):l||(l=o&~t,l!==0&&(n=ct(l))))):(o=a&~u,o!==0?n=ct(o):s!==0?n=ct(s):l||(l=a&~t,l!==0&&(n=ct(l)))),n===0?0:e!==0&&e!==n&&(e&u)===0&&(u=n&-n,l=e&-e,u>=l||u===32&&(l&4194048)!==0)?e:n}function $(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function st(t,e){(e&8)!==0&&(e|=e&32);var l=t.entangledLanes;if(l!==0)for(t=t.entanglements,l&=e;0l;l++)e.push(t);return e}function Te(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function wc(t,e,l,a,n,u){var s=t.pendingLanes;t.pendingLanes=l,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=l,t.entangledLanes&=l,t.errorRecoveryDisabledLanes&=l,t.shellSuspendCounter=0;var o=t.entanglements,m=t.expirationTimes,b=t.hiddenUpdates;for(l=s&~l;0"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Qc=!1;if(Pl)try{var pu={};Object.defineProperty(pu,"passive",{get:function(){Qc=!0}}),window.addEventListener("test",pu,pu),window.removeEventListener("test",pu,pu)}catch{Qc=!1}var va=null,Xc=null,hi=null;function qo(){if(hi)return hi;var t,e=Xc,l=e.length,a,n="value"in va?va.value:va.textContent,u=n.length;for(t=0;t=bu),Lo=" ",Zo=!1;function Ko(t,e){switch(t){case"keyup":return Uv.indexOf(e.keyCode)!==-1;case"keydown":return e.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Jo(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var Tn=!1;function Bv(t,e){switch(t){case"compositionend":return Jo(e);case"keypress":return e.which!==32?null:(Zo=!0,Lo);case"textInput":return t=e.data,t===Lo&&Zo?null:t;default:return null}}function qv(t,e){if(Tn)return t==="compositionend"||!Fc&&Ko(t,e)?(t=qo(),hi=Xc=va=null,Tn=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(e.ctrlKey||e.altKey||e.metaKey)||e.ctrlKey&&e.altKey){if(e.char&&1"u")return null;try{return t.activeElement||t.body}catch{return t.body}}function lf(t){for(;t&&t.firstChild;)t=t.firstChild;return t}function af(t,e){var l=lf(t);t=0;for(var a;l;){if(l.nodeType===3){if(a=t+l.textContent.length,t<=e&&a>=e)return{node:l,offset:e-t};t=a}t:{for(;l;){if(l.nextSibling){l=l.nextSibling;break t}l=l.parentNode}l=void 0}l=lf(l)}}function nf(t,e){return t&&e?t===e?!0:t&&t.nodeType===3?!1:e&&e.nodeType===3?nf(t,e.parentNode):"contains"in t?t.contains(e):t.compareDocumentPosition?!!(t.compareDocumentPosition(e)&16):!1:!1}function uf(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var e=Ic(t.document);e instanceof t.HTMLIFrameElement;){try{var l=typeof e.contentWindow.location.href=="string"}catch{l=!1}if(l)t=e.contentWindow;else break;e=Ic(t.document)}return e}function Pc(t){var e=t&&t.nodeName&&t.nodeName.toLowerCase();return e&&(e==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||e==="textarea"||t.contentEditable==="true")}var Kv=Pl&&"documentMode"in document&&11>=document.documentMode,_n=null,ts=null,Eu=null,es=!1;function cf(t,e,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;es||_n==null||_n!==Ic(a)||(a=_n,"selectionStart"in a&&Pc(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),Eu&&_u(Eu,a)||(Eu=a,a=Sc(ts,"onSelect"),0>=s,n-=s,Hl=1<<32-Oe(e)+n|l<ht?(ve=rt,rt=null):ve=rt.sibling;var Ut=S(x,rt,T[ht],M);if(Ut===null){rt===null&&(rt=ve);break}t&&rt&&Ut.alternate===null&&e(x,rt),v=u(Ut,v,ht),Rt===null?I=Ut:Rt.sibling=Ut,Rt=Ut,rt=ve}if(ht===T.length)return l(x,rt),At&&aa(x,ht),I;if(rt===null){for(;htht?(ve=rt,rt=null):ve=rt.sibling;var Ya=S(x,rt,Ut.value,M);if(Ya===null){rt===null&&(rt=ve);break}t&&rt&&Ya.alternate===null&&e(x,rt),v=u(Ya,v,ht),Rt===null?I=Ya:Rt.sibling=Ya,Rt=Ya,rt=ve}if(Ut.done)return l(x,rt),At&&aa(x,ht),I;if(rt===null){for(;!Ut.done;ht++,Ut=T.next())Ut=O(x,Ut.value,M),Ut!==null&&(v=u(Ut,v,ht),Rt===null?I=Ut:Rt.sibling=Ut,Rt=Ut);return At&&aa(x,ht),I}for(rt=a(rt);!Ut.done;ht++,Ut=T.next())Ut=_(rt,x,ht,Ut.value,M),Ut!==null&&(t&&(ve=Ut.alternate,ve!==null&&rt.delete(ve.key===null?ht:ve.key)),v=u(Ut,v,ht),Rt===null?I=Ut:Rt.sibling=Ut,Rt=Ut);return t&&rt.forEach(function(By){return e(x,By)}),At&&aa(x,ht),I}function Et(x,v,T,M){if(typeof T=="object"&&T!==null&&T.type===dt&&T.key===null&&T.props.ref===void 0&&(T=T.props.children),typeof T=="object"&&T!==null){switch(T.$$typeof){case Dt:t:{for(var I=T.key;v!==null;){if(v.key===I){if(I=T.type,I===dt){if(v.tag===7){l(x,v.sibling),M=n(v,T.props.children),ba(M,T),M.return=x,x=M;break t}}else if(v.elementType===I||typeof I=="object"&&I!==null&&I.$$typeof===xt&&ln(I)===v.type){l(x,v.sibling),M=n(v,T.props),ba(M,T),M.return=x,x=M;break t}l(x,v);break}else e(x,v);v=v.sibling}T.type===dt?(M=Fa(T.props.children,x.mode,M,T.key),ba(M,T),M.return=x,x=M):(M=_i(T.type,T.key,T.props,null,x.mode,M),ba(M,T),M.return=x,x=M)}return s(x);case qt:t:{for(I=T.key;v!==null;){if(v.key===I)if(v.tag===4&&v.stateNode.containerInfo===T.containerInfo&&v.stateNode.implementation===T.implementation){l(x,v.sibling),M=n(v,T.children||[]),M.return=x,x=M;break t}else{l(x,v);break}else e(x,v);v=v.sibling}M=ss(T,x.mode,M),M.return=x,x=M}return s(x);case xt:return T=ln(T),Et(x,v,T,M)}if(Tt(T))return Z(x,v,T,M);if(et(T)){if(I=et(T),typeof I!="function")throw Error(f(150));return T=I.call(T),it(x,v,T,M)}if(typeof T.then=="function")return Et(x,v,ki(T),M);if(T.$$typeof===yt)return Et(x,v,Oi(x,T),M);wi(x,T)}return typeof T=="string"&&T!==""||typeof T=="number"||typeof T=="bigint"?(T=""+T,v!==null&&v.tag===6?(l(x,v.sibling),M=n(v,T),M.return=x,x=M):(l(x,v),M=cs(T,x.mode,M),M.return=x,x=M),s(x)):l(x,v)}return function(x,v,T,M){try{zu=0;var I=Et(x,v,T,M);return Dn=null,I}catch(rt){if(rt===Rn||rt===Ri)throw rt;var Rt=Je(29,rt,null,x.mode);return Rt.lanes=M,Rt.return=x,Rt}finally{}}}var nn=zf(!0),Rf=zf(!1),ja=!1;function ps(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Ss(t,e){t=t.updateQueue,e.updateQueue===t&&(e.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function Ta(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function _a(t,e,l){var a=t.updateQueue;if(a===null)return null;if(a=a.shared,(Gt&2)!==0){var n=a.pending;return n===null?e.next=e:(e.next=n.next,n.next=e),a.pending=e,e=Ti(t),vf(t,null,l),e}return ji(t,a,e,l),Ti(t)}function Ru(t,e,l){if(e=e.updateQueue,e!==null&&(e=e.shared,(l&4194048)!==0)){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,hn(t,l)}}function xs(t,e){var l=t.updateQueue,a=t.alternate;if(a!==null&&(a=a.updateQueue,l===a)){var n=null,u=null;if(l=l.firstBaseUpdate,l!==null){do{var s={lane:l.lane,tag:l.tag,payload:l.payload,callback:null,next:null};u===null?n=u=s:u=u.next=s,l=l.next}while(l!==null);u===null?n=u=e:u=u.next=e}else n=u=e;l={baseState:a.baseState,firstBaseUpdate:n,lastBaseUpdate:u,shared:a.shared,callbacks:a.callbacks},t.updateQueue=l;return}t=l.lastBaseUpdate,t===null?l.firstBaseUpdate=e:t.next=e,l.lastBaseUpdate=e}var bs=!1;function Du(){if(bs){var t=zn;if(t!==null)throw t}}function ku(t,e,l,a){bs=!1;var n=t.updateQueue;ja=!1;var u=n.firstBaseUpdate,s=n.lastBaseUpdate,o=n.shared.pending;if(o!==null){n.shared.pending=null;var m=o,b=m.next;m.next=null,s===null?u=b:s.next=b,s=m;var E=t.alternate;E!==null&&(E=E.updateQueue,o=E.lastBaseUpdate,o!==s&&(o===null?E.firstBaseUpdate=b:o.next=b,E.lastBaseUpdate=m))}if(u!==null){var O=n.baseState;s=0,E=b=m=null,o=u;do{var S=o.lane&-536870913,_=S!==o.lane;if(_?(zt&S)===S:(a&S)===S){S!==0&&S===tn&&(bs=!0),E!==null&&(E=E.next={lane:0,tag:o.tag,payload:o.payload,callback:null,next:null});t:{var Z=t,it=o;S=e;var Et=l;switch(it.tag){case 1:if(Z=it.payload,typeof Z=="function"){O=Z.call(Et,O,S);break t}O=Z;break t;case 3:Z.flags=Z.flags&-65537|128;case 0:if(Z=it.payload,S=typeof Z=="function"?Z.call(Et,O,S):Z,S==null)break t;O=V({},O,S);break t;case 2:ja=!0}}S=o.callback,S!==null&&(t.flags|=64,_&&(t.flags|=8192),_=n.callbacks,_===null?n.callbacks=[S]:_.push(S))}else _={lane:S,tag:o.tag,payload:o.payload,callback:o.callback,next:null},E===null?(b=E=_,m=O):E=E.next=_,s|=S;if(o=o.next,o===null){if(o=n.shared.pending,o===null)break;_=o,o=_.next,_.next=null,n.lastBaseUpdate=_,n.shared.pending=null}}while(!0);E===null&&(m=O),n.baseState=m,n.firstBaseUpdate=b,n.lastBaseUpdate=E,u===null&&(n.shared.lanes=0),za|=s,t.lanes=s,t.memoizedState=O}}function Df(t,e){if(typeof t!="function")throw Error(f(191,t));t.call(e)}function kf(t,e){var l=t.callbacks;if(l!==null)for(t.callbacks=null,t=0;tu?u:8;var s=lt.T,o={};o.types=s!==null?s.types:null,lt.T=o,Gs(t,!1,e,l);try{var m=n(),b=lt.S;if(b!==null&&b(o,m),m!==null&&typeof m=="object"&&typeof m.then=="function"){var E=n0(m,a);Bu(t,e,E,il(t))}else Bu(t,e,a,il(t))}catch(O){Bu(t,e,{then:function(){},status:"rejected",reason:O},il())}finally{mt.p=u,s!==null&&o.types!==null&&(s.types=o.types),lt.T=s}}function o0(){}function qs(t,e,l,a){if(t.tag!==5)throw Error(f(476));var n=dd(t).queue;fd(t,n,e,re,l===null?o0:function(){return md(t),l(a)})}function dd(t){var e=t.memoizedState;if(e!==null)return e;e={memoizedState:re,baseState:re,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ca,lastRenderedState:re},next:null};var l={};return e.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:ca,lastRenderedState:l},next:null},t.memoizedState=e,t=t.alternate,t!==null&&(t.memoizedState=e),e}function md(t){var e=dd(t);e.next===null&&(e=t.alternate.memoizedState),Bu(t,e.next.queue,{},il())}function Ys(){return ze(eu)}function hd(){return ce().memoizedState}function vd(){return ce().memoizedState}function f0(t){for(var e=t.return;e!==null;){switch(e.tag){case 24:case 3:var l=il();t=Ta(l);var a=_a(e,t,l);a!==null&&(Ie(a,e,l),Ru(a,e,l)),e={cache:hs()},t.payload=e;return}e=e.return}}function d0(t,e,l){var a=il();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},Li(t)?gd(e,l):(l=us(t,e,l,a),l!==null&&(Ie(l,t,a),pd(l,e,a)))}function yd(t,e,l){var a=il();Bu(t,e,l,a)}function Bu(t,e,l,a){var n={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null};if(Li(t))gd(e,n);else{var u=t.alternate;if(t.lanes===0&&(u===null||u.lanes===0)&&(u=e.lastRenderedReducer,u!==null))try{var s=e.lastRenderedState,o=u(s,l);if(n.hasEagerState=!0,n.eagerState=o,el(o,s))return ji(t,e,n,0),Wt===null&&bi(),!1}catch{}finally{}if(l=us(t,e,n,a),l!==null)return Ie(l,t,a),pd(l,e,a),!0}return!1}function Gs(t,e,l,a){if(a={lane:2,revertLane:zr(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Li(t)){if(e)throw Error(f(479))}else e=us(t,l,a,2),e!==null&&Ie(e,t,2)}function Li(t){var e=t.alternate;return t===_t||e!==null&&e===_t}function gd(t,e){kn=Bi=!0;var l=t.pending;l===null?e.next=e:(e.next=l.next,l.next=e),t.pending=e}function pd(t,e,l){if((l&4194048)!==0){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,hn(t,l)}}var Zi={readContext:ze,use:Gi,useCallback:ue,useContext:ue,useEffect:ue,useImperativeHandle:ue,useLayoutEffect:ue,useInsertionEffect:ue,useMemo:ue,useReducer:ue,useRef:ue,useState:ue,useDebugValue:ue,useDeferredValue:ue,useTransition:ue,useSyncExternalStore:ue,useId:ue,useHostTransitionStatus:ue,useFormState:ue,useActionState:ue,useOptimistic:ue,useMemoCache:ue,useCacheRefresh:ue,useEffectEvent:ue},Sd={readContext:ze,use:Gi,useCallback:function(t,e){return Ge().memoizedState=[t,e===void 0?null:e],t},useContext:ze,useEffect:ld,useImperativeHandle:function(t,e,l){l=l!=null?l.concat([t]):null,Qi(4194308,4,id.bind(null,e,t),l)},useLayoutEffect:function(t,e){return Qi(4194308,4,t,e)},useInsertionEffect:function(t,e){Qi(4,2,t,e)},useMemo:function(t,e){var l=Ge();e=e===void 0?null:e;var a=t();if(un){fl(!0);try{t()}finally{fl(!1)}}return l.memoizedState=[a,e],a},useReducer:function(t,e,l){var a=Ge();if(l!==void 0){var n=l(e);if(un){fl(!0);try{l(e)}finally{fl(!1)}}}else n=e;return a.memoizedState=a.baseState=n,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:n},a.queue=t,t=t.dispatch=d0.bind(null,_t,t),[a.memoizedState,t]},useRef:function(t){var e=Ge();return t={current:t},e.memoizedState=t},useState:function(t){t=ks(t);var e=t.queue,l=yd.bind(null,_t,e);return e.dispatch=l,[t.memoizedState,l]},useDebugValue:Hs,useDeferredValue:function(t,e){var l=Ge();return Bs(l,t,e)},useTransition:function(){var t=ks(!1);return t=fd.bind(null,_t,t.queue,!0,!1),Ge().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,e,l){var a=_t,n=Ge();if(At){if(l===void 0)throw Error(f(407));l=l()}else{if(l=e(),Wt===null)throw Error(f(349));(zt&127)!==0||Yf(a,e,l)}n.memoizedState=l;var u={value:l,getSnapshot:e};return n.queue=u,ld(Vf.bind(null,a,u,t),[t]),a.flags|=2048,Un(9,{destroy:void 0},Gf.bind(null,a,u,l,e),null),l},useId:function(){var t=Ge(),e=Wt.identifierPrefix;if(At){var l=Bl,a=Hl;l=(a&~(1<<32-Oe(a)-1)).toString(32)+l,e="_"+e+"R_"+l,l=qi++,0<\/script>",u=u.removeChild(u.firstChild);break;case"select":u=typeof a.is=="string"?s.createElement("select",{is:a.is}):s.createElement("select"),a.multiple?u.multiple=!0:a.size&&(u.size=a.size);break;default:u=typeof a.is=="string"?s.createElement(n,{is:a.is}):s.createElement(n)}}u[ge]=e,u[Ke]=a;t:for(s=e.child;s!==null;){if(s.tag===5||s.tag===6)u.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===e)break t;for(;s.sibling===null;){if(s.return===null||s.return===e)break t;s=s.return}s.sibling.return=s.return,s=s.sibling}e.stateNode=u;t:switch(we(u,n,a),n){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break t;case"img":a=!0;break t;default:a=!1}a&&ra(e)}}return Pt(e),e.subtreeFlags&=-33554433,tr(e,e.type,t===null?null:t.memoizedProps,e.pendingProps,l),null;case 6:if(t&&e.stateNode!=null)t.memoizedProps!==a&&ra(e);else{if(typeof a!="string"&&e.stateNode===null)throw Error(f(166));if(t=xe.current,On(e)){if(t=e.stateNode,l=e.memoizedProps,a=null,n=Ee,n!==null)switch(n.tag){case 27:case 5:a=n.memoizedProps}t[ge]=e,t=!!(t.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||Vm(t.nodeValue,l)),t||Sa(e,!0)}else t=$u(t).createTextNode(a),t[ge]=e,e.stateNode=t}return Pt(e),null;case 31:if(l=e.memoizedState,t===null||t.memoizedState!==null){if(a=On(e),l!==null){if(t===null){if(!a)throw Error(f(318));if(t=e.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(f(557));t[ge]=e}else Wa(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;Pt(e),t=!1}else l=fs(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=l),t=!0;if(!t)return e.flags&256?(ll(e),e):(ll(e),null);if((e.flags&128)!==0)throw Error(f(558))}return Pt(e),null;case 13:if(a=e.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(n=On(e),a!==null&&a.dehydrated!==null){if(t===null){if(!n)throw Error(f(318));if(n=e.memoizedState,n=n!==null?n.dehydrated:null,!n)throw Error(f(317));n[ge]=e}else Wa(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;Pt(e),n=!1}else n=fs(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=n),n=!0;if(!n)return e.flags&256?(ll(e),e):(ll(e),null)}return ll(e),(e.flags&128)!==0?(e.lanes=l,e):(l=a!==null,t=t!==null&&t.memoizedState!==null,l&&(a=e.child,n=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(n=a.alternate.memoizedState.cachePool.pool),u=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(u=a.memoizedState.cachePool.pool),u!==n&&(a.flags|=2048)),l!==t&&l&&(e.child.flags|=8192),Ii(e,e.updateQueue),Pt(e),null);case 4:return zl(),t===null&&wr(e.stateNode.containerInfo),e.flags|=67108864,Pt(e),null;case 10:return ua(e.type),Pt(e),null;case 19:if(Es(e),a=e.memoizedState,a===null)return Pt(e),null;if(n=(e.flags&128)!==0,u=a.rendering,u===null)if(n)Yu(a,!1);else{if(ie!==0||t!==null&&(t.flags&128)!==0)for(t=e.child;t!==null;){if(u=Hi(t),u!==null){for(e.flags|=128,Yu(a,!1),t=u.updateQueue,e.updateQueue=t,Ii(e,t),e.subtreeFlags=0,t=l,l=e.child;l!==null;)yf(l,t),l=l.sibling;return wu(e,De.current&1|2),At&&aa(e,a.treeForkCount),e.child}t=t.sibling}a.tail!==null&&$t()>oc&&(e.flags|=128,n=!0,Yu(a,!1),e.lanes=4194304)}else{if(!n)if(t=Hi(u),t!==null){if(e.flags|=128,n=!0,t=t.updateQueue,e.updateQueue=t,Ii(e,t),Yu(a,!0),a.tail===null&&a.tailMode!=="collapsed"&&a.tailMode!=="visible"&&!u.alternate&&!At)return Pt(e),null}else 2*$t()-a.renderingStartTime>oc&&l!==536870912&&(e.flags|=128,n=!0,Yu(a,!1),e.lanes=4194304);a.isBackwards?(u.sibling=e.child,e.child=u):(t=a.last,t!==null?t.sibling=u:e.child=u,a.last=u)}if(a.tail!==null){t=a.tail;t:{for(l=t;l!==null;){if(l.alternate!==null){l=!1;break t}l=l.sibling}l=!0}return a.rendering=t,a.tail=t.sibling,a.renderingStartTime=$t(),t.sibling=null,u=De.current,u=n?u&1|2:u&1,a.tailMode==="visible"||a.tailMode==="collapsed"||!l||At?wu(e,u):(l=u,Vt(Re,e),Vt(De,l),He===null&&(He=e)),At&&aa(e,a.treeForkCount),t}return Pt(e),null;case 22:case 23:return ll(e),Ts(),a=e.memoizedState!==null,t!==null?t.memoizedState!==null!==a&&(e.flags|=8192):a&&(e.flags|=8192),a?(l&536870912)!==0&&(e.flags&128)===0&&(Pt(e),e.subtreeFlags&6&&(e.flags|=8192)):Pt(e),l=e.updateQueue,l!==null&&Ii(e,l.retryQueue),l=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(l=t.memoizedState.cachePool.pool),a=null,e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),a!==l&&(e.flags|=2048),t!==null&&ae(en),null;case 24:return l=null,t!==null&&(l=t.memoizedState.cache),e.memoizedState.cache!==l&&(e.flags|=2048),ua(de),Pt(e),null;case 25:return null;case 30:return e.flags|=33554432,Pt(e),null}throw Error(f(156,e.tag))}function S0(t,e){switch(rs(e),e.tag){case 1:return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 3:return ua(de),zl(),t=e.flags,(t&65536)!==0&&(t&128)===0?(e.flags=t&-65537|128,e):null;case 26:case 27:case 5:return Tl(e),null;case 31:if(e.memoizedState!==null){if(ll(e),e.alternate===null)throw Error(f(340));Wa()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 13:if(ll(e),t=e.memoizedState,t!==null&&t.dehydrated!==null){if(e.alternate===null)throw Error(f(340));Wa()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 19:return Es(e),t=e.flags,t&65536?(e.flags=t&-65537|128,t=e.memoizedState,t!==null&&(t.rendering=null,t.tail=null),e.flags|=4,e):null;case 4:return zl(),null;case 10:return ua(e.type),null;case 22:case 23:return ll(e),Ts(),t!==null&&ae(en),t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 24:return ua(de),null;case 25:return null;default:return null}}function Xd(t,e){switch(rs(e),e.tag){case 3:ua(de),zl();break;case 26:case 27:case 5:Tl(e);break;case 4:zl();break;case 31:e.memoizedState!==null&&ll(e);break;case 13:ll(e);break;case 19:Es(e);break;case 10:ua(e.type);break;case 22:case 23:ll(e),Ts(),t!==null&&ae(en);break;case 24:ua(de)}}function Gu(t,e){try{var l=e.updateQueue,a=l!==null?l.lastEffect:null;if(a!==null){var n=a.next;l=n;do{if((l.tag&t)===t){a=void 0;var u=l.create,s=l.inst;a=u(),s.destroy=a}l=l.next}while(l!==n)}}catch(o){Lt(e,e.return,o)}}function Aa(t,e,l){try{var a=e.updateQueue,n=a!==null?a.lastEffect:null;if(n!==null){var u=n.next;a=u;do{if((a.tag&t)===t){var s=a.inst,o=s.destroy;if(o!==void 0){s.destroy=void 0,n=e;var m=l,b=o;try{b()}catch(E){Lt(n,m,E)}}}a=a.next}while(a!==u)}}catch(E){Lt(e,e.return,E)}}function Ld(t){var e=t.updateQueue;if(e!==null){var l=t.stateNode;try{kf(e,l)}catch(a){Lt(t,t.return,a)}}}function Zd(t,e,l){l.props=cn(t.type,t.memoizedProps),l.state=t.memoizedState;try{l.componentWillUnmount()}catch(a){Lt(t,e,a)}}function ql(t,e){try{var l=t.ref;if(l!==null){switch(t.tag){case 26:case 27:case 5:var a=t.stateNode;break;case 30:var n=t.stateNode,u=ta(t.memoizedProps,n);(n.ref===null||n.ref.name!==u)&&(n.ref=Pm(u)),a=n.ref;break;case 7:if(t.stateNode===null){var s=new cl(t);g(t.child,!1,ry,s,void 0,void 0),t.stateNode=s}a=t.stateNode;break;default:a=t.stateNode}typeof l=="function"?t.refCleanup=l(a):l.current=a}}catch(o){Lt(t,e,o)}}function ke(t,e){var l=t.ref,a=t.refCleanup;if(l!==null)if(typeof a=="function")try{a()}catch(n){Lt(t,e,n)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof l=="function")try{l(null)}catch(n){Lt(t,e,n)}else l.current=null}function Pi(t,e){if((t.tag===5||t.tag===27||t.tag===6)&&t.alternate===null&&e!==null)for(var l=0;l title"))),we(a,e,l),a[ge]=t,_e(a),e=a;break t;case"link":if(u=yh("link","href",n).get(e+(l.href||""))){for(s=0;sEt&&(s=Et,Et=it,it=s);var x=af(o,it),v=af(o,Et);if(x&&v&&(_.rangeCount!==1||_.anchorNode!==x.node||_.anchorOffset!==x.offset||_.focusNode!==v.node||_.focusOffset!==v.offset)){var T=O.createRange();T.setStart(x.node,x.offset),_.removeAllRanges(),it>Et?(_.addRange(T),_.extend(v.node,v.offset)):(T.setEnd(v.node,v.offset),_.addRange(T))}}}}for(O=[],_=o;_=_.parentNode;)_.nodeType===1&&O.push({element:_,left:_.scrollLeft,top:_.scrollTop});for(typeof o.focus=="function"&&o.focus(),o=0;ol?32:l,lt.T=null,l=br,br=null;var u=Ml,s=Ll;if(le=0,fn=Ml=null,Ll=0,(Gt&6)!==0)throw Error(f(331));var o=Gt;if(Gt|=4,vm(u.current),dm(u,u.current,s,l),Gt=o,Ju(0,!1),Me&&typeof Me.onPostCommitFiberRoot=="function")try{Me.onPostCommitFiberRoot(Wl,u)}catch{}return!0}finally{mt.p=n,lt.T=a,Om(t,e)}}function zm(t,e,l){e=hl(l,e),e=Xs(t.stateNode,e,2),t=_a(t,e,2),t!==null&&(Te(t,2),Zl(t))}function Lt(t,e,l){if(t.tag===3)zm(t,t,l);else for(;e!==null;){if(e.tag===3){zm(e,t,l);break}else if(e.tag===1){var a=e.stateNode;if(typeof e.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(Ra===null||!Ra.has(a))){t=hl(l,t),l=Cd(2),a=_a(e,l,2),a!==null&&(Ad(l,a,e,t),Te(a,2),Zl(a));break}}e=e.return}}function Ar(t,e,l){var a=t.pingCache;if(a===null){a=t.pingCache=new T0;var n=new Set;a.set(e,n)}else n=a.get(e),n===void 0&&(n=new Set,a.set(e,n));n.has(l)||(pr=!0,n.add(l),t=z0.bind(null,t,e,l),e.then(t,t))}function z0(t,e,l){var a=t.pingCache;a!==null&&a.delete(e),t.pingedLanes|=t.suspendedLanes&l,t.warmLanes&=~l,Wt===t&&(zt&l)===l&&((ie===4||ie===3&&(zt&62914560)===zt&&300>$t()-rc)&&(Gt&2)===0?Kn(t,0):sc|=l,Vn===zt&&(Vn=0)),Zl(t)}function Rm(t,e){e===0&&(e=wt()),t=Ja(t,e),t!==null&&(Te(t,e),Zl(t))}function R0(t){var e=t.memoizedState,l=0;e!==null&&(l=e.retryLane),Rm(t,l)}function D0(t,e){var l=0;switch(t.tag){case 31:case 13:var a=t.stateNode,n=t.memoizedState;n!==null&&(l=n.retryLane);break;case 19:a=t.stateNode;break;case 22:a=t.stateNode._retryCache;break;default:throw Error(f(314))}a!==null&&a.delete(e),Rm(t,l)}function k0(t,e){return Pe(t,e)}var Fn=null,Wn=null,Mr=!1,gc=!1,Or=!1,ka=0;function Zl(t){t!==Wn&&t.next===null&&(Wn===null?Fn=Wn=t:Wn=Wn.next=t),gc=!0,Mr||(Mr=!0,U0())}function Ju(t,e){if(!Or&&gc){Or=!0;do for(var l=!1,a=Fn;a!==null;){if(t!==0){var n=a.pendingLanes;if(n===0)var u=0;else{var s=a.suspendedLanes,o=a.pingedLanes;u=(1<<31-Oe(42|t)+1)-1,u&=n&~(s&~o),u=u&201326741?u&201326741|1:u?u|2:0}u!==0&&(l=!0,Um(a,u))}else u=zt,u=A(a,a===Wt?u:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(u&3)===0||$(a,u)||(l=!0,Um(a,u));a=a.next}while(l);Or=!1}}function w0(){Dm()}function Dm(){gc=Mr=!1;var t=0;ka!==0&&K0()&&(t=ka);for(var e=$t(),l=null,a=Fn;a!==null;){var n=a.next,u=km(a,e);u===0?(a.next=null,l===null?Fn=n:l.next=n,n===null&&(Wn=l)):(l=a,(t!==0||(u&3)!==0)&&(gc=!0)),a=n}le!==0&&le!==5||Ju(t),ka!==0&&(ka=0)}function km(t,e){for(var l=t.suspendedLanes,a=t.pingedLanes,n=t.expirationTimes,u=t.pendingLanes&-62914561;0o)break;var E=m.transferSize,O=m.initiatorType;E&&Qm(O)&&(m=m.responseEnd,s+=E*(mjc){it.length=Z;break}T=new Promise(P0.bind(T)),it.push(T)}}}if(0"u"?null:document;function dh(t,e,l){var a=In;if(a&&typeof e=="string"&&e){var n=dl(e);n='link[rel="'+t+'"][href="'+n+'"]',typeof l=="string"&&(n+='[crossorigin="'+l+'"]'),fh.has(n)||(fh.add(n),t={rel:t,crossOrigin:l,href:e},a.querySelector(n)===null&&(e=a.createElement("link"),we(e,"link",t),_e(e),a.head.appendChild(e)))}}function py(t){fa.D(t),dh("dns-prefetch",t,null)}function Sy(t,e){fa.C(t,e),dh("preconnect",t,e)}function xy(t,e,l){fa.L(t,e,l);var a=In;if(a&&t&&e){var n='link[rel="preload"][as="'+dl(e)+'"]';e==="image"&&l&&l.imageSrcSet?(n+='[imagesrcset="'+dl(l.imageSrcSet)+'"]',typeof l.imageSizes=="string"&&(n+='[imagesizes="'+dl(l.imageSizes)+'"]')):n+='[href="'+dl(t)+'"]';var u=n;switch(e){case"style":u=Pn(t);break;case"script":u=tu(t)}if(!(xl.has(u)||(t=V({rel:"preload",href:e==="image"&&l&&l.imageSrcSet?void 0:t,as:e},l),xl.set(u,t),a.querySelector(n)!==null||e==="style"&&a.querySelector(Pu(u))||e==="script"&&a.querySelector(ti(u))))){var s=a.createElement("link");we(s,"link",t),e==="style"&&(s[ri]=!0,s.onload=s.onerror=function(){_o(s)}),_e(s),a.head.appendChild(s)}}}function by(t,e){fa.m(t,e);var l=In;if(l&&t){var a=e&&typeof e.as=="string"?e.as:"script",n='link[rel="modulepreload"][as="'+dl(a)+'"][href="'+dl(t)+'"]',u=n;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":u=tu(t)}if(!xl.has(u)&&(t=V({rel:"modulepreload",href:t},e),xl.set(u,t),l.querySelector(n)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(l.querySelector(ti(u)))return}a=l.createElement("link"),we(a,"link",t),_e(a),l.head.appendChild(a)}}}function jy(t,e,l){fa.S(t,e,l);var a=In;if(a&&t){var n=gn(a).hoistableStyles,u=Pn(t);e=e||"default";var s=n.get(u);if(!s){var o={loading:0,preload:null};if(s=a.querySelector(Pu(u)))o.loading=5;else{t=V({rel:"stylesheet",href:t,"data-precedence":e},l),(l=xl.get(u))&&$r(t,l);var m=s=a.createElement("link");_e(m),we(m,"link",t),m._p=new Promise(function(b,E){m.onload=b,m.onerror=E}),m.addEventListener("load",function(){o.loading|=1}),m.addEventListener("error",function(){o.loading|=2}),o.loading|=4,xc(s,e,a)}s={type:"stylesheet",instance:s,count:1,state:o},n.set(u,s)}}}function Ty(t,e){fa.X(t,e);var l=In;if(l&&t){var a=gn(l).hoistableScripts,n=tu(t),u=a.get(n);u||(u=l.querySelector(ti(n)),u||(t=V({src:t,async:!0},e),(e=xl.get(n))&&Ir(t,e),u=l.createElement("script"),_e(u),we(u,"link",t),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function _y(t,e){fa.M(t,e);var l=In;if(l&&t){var a=gn(l).hoistableScripts,n=tu(t),u=a.get(n);u||(u=l.querySelector(ti(n)),u||(t=V({src:t,async:!0,type:"module"},e),(e=xl.get(n))&&Ir(t,e),u=l.createElement("script"),_e(u),we(u,"link",t),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function mh(t,e,l,a){var n=(n=xe.current)?Iu(n):null;if(!n)throw Error(f(446));switch(t){case"meta":case"title":return null;case"style":return typeof l.precedence=="string"&&typeof l.href=="string"?(l=Pn(l.href),e=gn(n).hoistableStyles,a=e.get(l),a||(a={type:"style",instance:null,count:0,state:null},e.set(l,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(l.rel==="stylesheet"&&typeof l.href=="string"&&typeof l.precedence=="string"){t=Pn(l.href);var u=gn(n).hoistableStyles,s=u.get(t);if(s||(n=n.ownerDocument||n,s={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},u.set(t,s),(u=n.querySelector(Pu(t)))?u._p||(s.instance=u,s.state.loading=5):(u=xl.get(t),u||(u={rel:"preload",as:"style",href:l.href,crossOrigin:l.crossOrigin,integrity:l.integrity,media:l.media,hrefLang:l.hrefLang,referrerPolicy:l.referrerPolicy},xl.set(t,u)),Ey(n,t,u,s.state))),e&&a===null)throw Error(f(528,""));return s}if(e&&a!==null)throw Error(f(529,""));return null;case"script":return e=l.async,l=l.src,typeof l=="string"&&e&&typeof e!="function"&&typeof e!="symbol"?(l=tu(l),e=gn(n).hoistableScripts,a=e.get(l),a||(a={type:"script",instance:null,count:0,state:null},e.set(l,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(f(444,t))}}function Pn(t){return'href="'+dl(t)+'"'}function Pu(t){return'link[rel="stylesheet"]['+t+"]"}function hh(t){return V({},t,{"data-precedence":t.precedence,precedence:null})}function Ey(t,e,l,a){if(e=t.querySelector('link[rel="preload"][as="style"]['+e+"]")){if(e[ri]!==!0){a.loading=1;return}}else e=t.createElement("link"),e[ri]=!0,e.onload=e.onerror=_o.bind(null,e),we(e,"link",l),_e(e),t.head.appendChild(e);a.preload=e,e.addEventListener("load",function(){return a.loading|=1}),e.addEventListener("error",function(){return a.loading|=2})}function tu(t){return'[src="'+dl(t)+'"]'}function ti(t){return"script[async]"+t}function vh(t,e,l){if(e.count++,e.instance===null)switch(e.type){case"style":var a=t.querySelector('style[data-href~="'+dl(l.href)+'"]');if(a)return e.instance=a,_e(a),a;var n=V({},l,{"data-href":l.href,"data-precedence":l.precedence,href:null,precedence:null});return a=(t.ownerDocument||t).createElement("style"),_e(a),we(a,"style",n),xc(a,l.precedence,t),e.instance=a;case"stylesheet":n=Pn(l.href);var u=t.querySelector(Pu(n));if(u)return e.state.loading|=4,e.instance=u,_e(u),u;a=hh(l),(n=xl.get(n))&&$r(a,n),u=(t.ownerDocument||t).createElement("link"),_e(u);var s=u;return s._p=new Promise(function(o,m){s.onload=o,s.onerror=m}),we(u,"link",a),e.state.loading|=4,xc(u,l.precedence,t),e.instance=u;case"script":return u=tu(l.src),(n=t.querySelector(ti(u)))?(e.instance=n,_e(n),n):(a=l,(n=xl.get(u))&&(a=V({},l),Ir(a,n)),t=t.ownerDocument||t,n=t.createElement("script"),_e(n),we(n,"link",a),t.head.appendChild(n),e.instance=n);case"void":return null;default:throw Error(f(443,e.type))}else e.type==="stylesheet"&&(e.state.loading&4)===0&&(a=e.instance,e.state.loading|=4,xc(a,l.precedence,t));return e.instance}function xc(t,e,l){for(var a=l.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),n=a.length?a[a.length-1]:null,u=n,s=0;s title"):null)}function Ny(t,e,l){if(l===1||e.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof e.precedence!="string"||typeof e.href!="string"||e.href==="")break;return!0;case"link":if(typeof e.rel!="string"||typeof e.href!="string"||e.href===""||e.onLoad||e.onError)break;switch(e.rel){case"stylesheet":return t=e.disabled,typeof e.precedence=="string"&&t==null;default:return!0}case"script":if(e.async&&typeof e.async!="function"&&typeof e.async!="symbol"&&!e.onLoad&&!e.onError&&e.src&&typeof e.src=="string")return!0}return!1}function gh(t,e){return t==="img"&&e.src!=null&&e.src!==""&&e.onLoad==null&&e.loading!=="lazy"}function ph(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function Sh(t){return(t.width||100)*(t.height||100)*(typeof devicePixelRatio=="number"?devicePixelRatio:1)*.25}function xh(t,e){typeof e.decode=="function"&&(t.imgCount++,e.complete||(t.imgBytes+=Sh(e),t.suspenseyImages.push(e)),t=My.bind(t),e.decode().then(t,t))}function Cy(t,e,l,a){if(l.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(l.state.loading&4)===0){if(l.instance===null){var n=Pn(a.href),u=e.querySelector(Pu(n));if(u){e=u._p,e!==null&&typeof e=="object"&&typeof e.then=="function"&&(t.count++,t=ei.bind(t),e.then(t,t)),l.state.loading|=4,l.instance=u,_e(u);return}u=e.ownerDocument||e,a=hh(a),(n=xl.get(n))&&$r(a,n),u=u.createElement("link"),_e(u);var s=u;s._p=new Promise(function(o,m){s.onload=o,s.onerror=m}),we(u,"link",a),l.instance=u}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(l,e),(e=l.state.preload)&&(l.state.loading&3)===0&&(t.count++,l=ei.bind(t),e.addEventListener("load",l),e.addEventListener("error",l))}}var jc=0;function Ay(t,e){return t.stylesheets&&t.count===0&&_c(t,t.stylesheets),0jc?50:800)+e);return t.unsuspend=l,function(){t.unsuspend=null,clearTimeout(a),clearTimeout(n)}}:null}function bh(t){if(t.count===0&&(t.imgCount===0||!t.waitingForImages)){if(t.stylesheets)_c(t,t.stylesheets);else if(t.unsuspend){var e=t.unsuspend;t.unsuspend=null,e()}}}function ei(){this.count--,bh(this)}function My(){this.imgCount--,bh(this)}var Tc=null;function _c(t,e){t.stylesheets=null,t.unsuspend!==null&&(t.count++,Tc=new Map,e.forEach(Oy,t),Tc=null,ei.call(t))}function Oy(t,e){if(!(e.state.loading&4)){var l=Tc.get(t);if(l)var a=l.get(null);else{l=new Map,Tc.set(t,l);for(var n=t.querySelectorAll("link[data-precedence],style[data-precedence]"),u=0;u"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(r){console.error(r)}}return i(),so.exports=Ky(),so.exports}var Fy=Jy();const Wy=Wh(Fy),$h=d.createContext(null);function Kt(){const i=d.useContext($h);if(!i)throw new Error("ConsoleContext missing");return i}const ft=encodeURIComponent,W={health:"/health",relayModels:"/relay/models",quotaMe:"/users/me/quota",userKeys:"/users/me/keys",userKey:i=>"/users/me/keys/"+ft(i),githubRepos:"/github/repos",projects:"/projects",project:i=>"/projects/"+ft(i),projectMembers:i=>"/projects/"+ft(i)+"/members",projectMember:(i,r)=>"/projects/"+ft(i)+"/members/"+ft(r),projectSkills:i=>"/projects/"+ft(i)+"/skills",projectApps:i=>"/projects/"+ft(i)+"/apps",projectMcp:i=>"/projects/"+ft(i)+"/mcp-servers",projectMcpReload:i=>"/projects/"+ft(i)+"/mcp-servers/reload",projectMcpOauth:(i,r)=>"/projects/"+ft(i)+"/mcp-servers/"+ft(r)+"/oauth-login",projectAutomations:i=>"/projects/"+ft(i)+"/automations",projectAutomation:(i,r)=>"/projects/"+ft(i)+"/automations/"+ft(r),projectAutomationCancel:(i,r)=>"/projects/"+ft(i)+"/automations/"+ft(r)+"/cancel",projectTriage:i=>"/projects/"+ft(i)+"/triage",projectTodos:i=>"/projects/"+ft(i)+"/todos",projectTodo:(i,r)=>"/projects/"+ft(i)+"/todos/"+ft(r),workers:"/workers",environments:i=>"/environments?project_id="+ft(i),environmentsCreate:"/environments",environmentArchive:i=>"/environments/"+ft(i)+"/archive",tasks:i=>"/tasks?project_id="+ft(i),tasksPage:(i,r,h,f,p,N)=>{let C="/tasks?project_id="+ft(i)+"&limit="+r+"&offset="+h+"&order=desc";return f&&(C+="&status="+ft(f)),p&&(C+="&executor="+ft(p)),N&&N.trim()&&(C+="&q="+ft(N.trim())),C},tasksByParent:(i,r)=>"/tasks?project_id="+ft(i)+"&parent_task_id="+ft(r),tasksCreate:"/tasks",task:i=>"/tasks/"+ft(i),taskAction:(i,r)=>"/tasks/"+ft(i)+"/"+r,taskFork:i=>"/tasks/"+ft(i)+"/fork",taskContinue:i=>"/tasks/"+ft(i)+"/continue",taskTrace:i=>"/tasks/"+ft(i)+"/trace",taskTraceStream:i=>"/tasks/"+ft(i)+"/trace/stream",taskAgents:i=>"/tasks/"+ft(i)+"/agents",taskReviews:i=>"/tasks/"+ft(i)+"/reviews",taskReviewFindings:(i,r)=>"/tasks/"+ft(i)+"/reviews/"+ft(r)+"/findings",taskApprovals:i=>"/tasks/"+ft(i)+"/approvals",taskApprovalDecision:(i,r,h)=>"/tasks/"+ft(i)+"/approvals/"+ft(r)+"/"+h,taskFollowups:i=>"/tasks/"+ft(i)+"/followups",taskChatHistory:i=>"/tasks/"+ft(i)+"/chat-history",taskMessages:i=>"/tasks/"+ft(i)+"/messages",taskMessagesTo:i=>"/tasks/"+ft(i)+"/messages",taskThread:i=>"/tasks/"+ft(i)+"/thread",taskThreads:i=>"/tasks/"+ft(i)+"/threads",taskThreadAction:(i,r)=>"/tasks/"+ft(i)+"/thread/"+r,taskCost:i=>"/tasks/"+ft(i)+"/cost",taskBrowserState:i=>"/tasks/"+ft(i)+"/browser/state",taskBrowserStream:i=>"/tasks/"+ft(i)+"/browser/stream",taskBrowserInput:i=>"/tasks/"+ft(i)+"/browser/input",taskBrowserControl:i=>"/tasks/"+ft(i)+"/browser/control",uploads:"/uploads",adminQuotas:"/admin/quotas",adminQuotaUser:i=>"/admin/quotas?user_id="+ft(i),adminSweep:"/admin/sweep",adminBackup:"/admin/backup"},$y={exec:"命令",file:"文件",call:"调用",tool:"工具",message:"消息",reasoning:"思考",plan:"计划","agent-start":"子代理","agent-done":"子代理"},uu=["completed","failed","interrupted","merged","merge-failed"],Iy={queued:"排队中,尚未被 worker 认领",running:"运行中,模型回合进行中",completed:"模型回合结束、改动在 worktree 里尚未合并",failed:"模型回合失败,可重试",interrupted:"已被取消/中断,可重试",merged:"控制面已标记 merged,不代表 git 主分支已合入","merge-failed":"合并落盘失败,可重试"},Lh=["low","medium","high","xhigh","max"],Py=["viewer","contributor","maintainer","owner"],So="codex-console-key";function sl(){try{return localStorage.getItem(So)||""}catch{return""}}function tg(i){localStorage.setItem(So,i)}function eg(){localStorage.removeItem(So)}function Ae(i){return i?i.slice(0,8):""}function Kl(i){return i?new Date(i*1e3).toLocaleString():""}async function at(i,r="GET",h){const f={"Content-Type":"application/json"},p=sl();p&&(f["X-API-Key"]=p);const N={method:r,headers:f};h!==void 0&&(N.body=JSON.stringify(h));const C=await fetch(i,N);let j=null;try{j=await C.json()}catch{j=null}if(!C.ok){const z=j;let R=z&&z.error?z.error:"HTTP "+C.status;throw C.status===401&&(R+="(key 未填或无效)"),C.status===429&&(R+="(触发限流,稍后再试)"),C.status===402&&(z&&z.unpriced_token_cap!==void 0?R+="(无价 token 已满,联系管理员调整)":z&&z.error==="coordinator_quota_reserve_exceeded"?R="协调员分工预留不足:剩余美元配额不够覆盖全部分工,子任务费用记父任务创建人账,整个父任务已被挡下(未建任何子任务)":R+="(美元配额已满,联系管理员调整)"),new Error(R)}return j}async function lg(i){const r=new FormData;r.append("paths",JSON.stringify(i.map(p=>p.path))),i.forEach((p,N)=>r.append("file"+N,p.file));const h=await fetch(W.uploads,{method:"POST",headers:{"X-API-Key":sl()},body:r}),f=await h.json().catch(()=>({}));if(!h.ok)throw new Error(f&&f.error||"HTTP "+h.status);return f.upload_id}function ag(i,r,h){const f=[],p={},N=(C,j)=>{if(C)for(let z=0;zC.name),N(r,C=>C.webkitRelativePath||C.name),N(h,C=>C.name),f}function xo(i){const r=Date.now()/1e3;let h=null;if(typeof i.last_seen=="number"?h=r-i.last_seen:typeof i.last_seen_seconds_ago=="number"&&(h=i.last_seen_seconds_ago),(h===null||h<0)&&(h=null),h===null)return{text:"未知",age:null};const f=Math.floor(h);return f<60?{text:f+" 秒前",age:h}:f<3600?{text:Math.floor(f/60)+" 分钟前",age:h}:{text:Math.floor(f/3600)+" 小时前",age:h}}function zc(i){const r=i.message||"";return r.includes("forbidden")?"没有权限:只有项目 owner 或全局管理员可以管理成员":r.includes("already_member")?"该用户已是成员,改角色请用成员行的下拉框":r.includes("cannot demote the only owner")?"不能把唯一 owner 降为非 owner:请先把另一位成员升为 owner":r.includes("user not found")?"找不到该用户:请核对 user_id 或用户名是否正确":r.includes("member not found")?"该用户不是本项目成员":r.includes("unknown role")?"角色无效:只能是 viewer / contributor / maintainer / owner":r.includes("project not found")?"项目不存在(可能已被删除)":"成员操作失败:"+r}function mo(i){const r=i.message||"";return r.includes("forbidden")?"无全局管理员权限:服务端返回 403(CODEX_GLOBAL_ADMINS 未包含当前用户),请联系管理员":r.includes("user not found")?"找不到该用户:请核对用户 id 是否正确":"管理操作失败:"+r}function ho(i){return i==null?"不限额":"$"+(i/1e6).toFixed(2)}function go(i){const r=$y[i.kind||""]||i.kind||"?";let h="";return i.kind==="exec"?h=(i.command||"")+"(exit="+(i.exitCode??"")+","+(i.durationMs??"")+"ms,cwd="+(i.cwd||"")+")":i.kind==="file"?h=(i.path||"")+" "+(i.change||"")+" "+(i.status||""):i.kind==="message"?h=(i.phase?"["+i.phase+"] ":"")+(i.text||"")+(i.truncated?"(链路摘要已截断至 300 字,完整回复见右侧任务结果)":""):i.kind==="reasoning"?h=i.summary||"":i.kind==="tool"?h=(i.server||"")+"/"+(i.tool||"")+" "+(i.status||""):i.kind==="call"?h=i.name||"":i.kind==="plan"?h=i.text||"":i.kind==="agent-start"?h="开始:"+(i.name||"")+(i.prompt?"("+i.prompt+")":""):i.kind==="agent-done"?h="结束:"+(i.name||"")+" "+(i.status||""):h=JSON.stringify(i).slice(0,300),"["+r+"] "+h}function ng(){const[i,r]=d.useState(!!sl()),[h,f]=d.useState(sl()?"已保存到本浏览器(只用于调本站 API)":"未填 key,数据接口会返回 401"),[p,N]=d.useState("未加载"),[C,j]=d.useState("未加载"),[z,R]=d.useState("填 key 后判断权限…"),[q,g]=d.useState(!1),[U,F]=d.useState(""),[X,J]=d.useState(""),[Q,vt]=d.useState([]),[St,jt]=d.useState(""),[K,Nt]=d.useState(""),[w,V]=d.useState([]),[nt,Dt]=d.useState(null),[qt,dt]=d.useState("未加载"),[kt,H]=d.useState(""),[ot,yt]=d.useState([]),[G,Y]=d.useState(""),[tt,gt]=d.useState(""),[xt,Se]=d.useState([]),[L,se]=d.useState([]),[y,D]=d.useState([]),[ut,et]=d.useState("先选项目"),[Ct,bt]=d.useState(""),[Tt,lt]=d.useState(""),[mt,re]=d.useState(""),[Ga,jl]=d.useState(""),[Xe,ae]=d.useState([]),[Vt,ye]=d.useState(""),[Jl,xe]=d.useState(""),[da,Le]=d.useState(""),[zl,Rl]=d.useState(!1),[Tl,Be]=d.useState(""),[oe,fe]=d.useState(""),[rl,Fl]=d.useState([]),[ma,Dl]=d.useState(!1),ee=d.useRef("");ee.current=St;const Pe=d.useRef("");Pe.current=oe;const qe=d.useCallback(async()=>{try{const A=await at(W.health);j("backend="+A.backend+" db_ok="+A.db_ok+" worker_ok="+A.worker_ok+" commit="+String(A.commit||"").slice(0,7))}catch(A){j("加载失败:"+A.message)}},[]),_l=d.useCallback(async()=>{if(!sl()){N("未填 key,配额不显示");return}try{const A=await at(W.quotaMe),$=((A.used_usd_micros||0)/1e6).toFixed(2),st=A.quota_usd_micros===null||A.quota_usd_micros===void 0?"美元:本月用量 $"+$+"(不限额)":"美元:本月用量 $"+$+" / $"+((A.quota_usd_micros||0)/1e6).toFixed(2)+"(剩余 $"+((A.remaining_usd_micros||0)/1e6).toFixed(2)+")",Yt=A.unpriced_token_cap===void 0||A.unpriced_tokens_used===void 0?"无价 token:未返回(旧服务端)":"无价 token:"+A.unpriced_tokens_used+" / "+A.unpriced_token_cap+" tokens";N(st+" | "+Yt)}catch(A){N("配额加载失败:"+A.message)}},[]),ol=d.useCallback(async()=>{if(!sl()){R("未填 key,管理区不可用"),g(!1);return}try{const A=await fetch(W.adminQuotas,{headers:{"X-API-Key":sl()}});A.status===400?(R("已确认为全局管理员,管理操作可用"),g(!0)):A.status===403?(R("管理区仅全局管理员可用:当前 key 无全局管理员权限(服务端返回 403)"),g(!1)):A.status===401?(R("key 无效,管理区不可用(服务端返回 401)"),g(!1)):(R("管理权限判断失败(HTTP "+A.status+"),管理操作暂不可用"),g(!1))}catch(A){R("管理权限判断失败:"+A.message),g(!1)}},[]),$t=d.useCallback(async()=>{try{const $=(await at(W.projects)).projects||[];vt($),Nt($.length+" 个项目"),ee.current&&$.some(st=>st.id===ee.current)}catch(A){Nt("加载失败:"+A.message)}},[]),kl=d.useCallback(async()=>{try{const A=await at(W.githubRepos);V(A.repos||[])}catch{}},[]),mn=d.useCallback(async()=>{try{const A=await at(W.relayModels);Fl(A.models||[]),Dl(!1)}catch{Dl(!0)}},[]),Va=d.useCallback(async()=>{try{const A=await at(W.workers);if(!Array.isArray(A)&&A.workers===void 0&&A.workers_total!==void 0){Dt(null),dt(A.workers_total?"在线 "+A.workers_online+" / 离线 "+A.workers_offline+"(共 "+A.workers_total+";完整 worker 清单仅全局管理员可见)":"无 worker 注册"),H("");return}const $=Array.isArray(A)?A:A.workers||[];Dt($),dt($.length?"":"无 worker 注册"),H("")}catch(A){H("加载失败:"+A.message)}},[]),be=d.useCallback(async A=>{const $=A!==void 0?A:ee.current,st=Pe.current;if(!$){ae([]),ye(""),fe("");return}try{const wt=(await at(W.environments($))).environments||[];ae(wt),ye(wt.length+" 个环境"),(st&&wt.some(Te=>Te.id===st&&Te.status==="ready")||st&&wt.some(Te=>Te.id===st))&&fe(st);const je=Q.find(Te=>Te.id===$);je&&je.repo_url?(Le("仓库:"+je.repo_url),Rl(!1)):je?(Le("该项目未绑定仓库,环境无法创建——先选一个仓库保存"),Rl(!0)):(Le(""),Rl(!1))}catch(Yt){xe("加载失败:"+Yt.message)}},[Q]),Qa=d.useCallback(async A=>{const $=A!==void 0?A:ee.current;if(!$){yt([]);return}try{const st=await at(W.projectMembers($));yt(st.members||[]),gt("")}catch(st){gt("成员加载失败:"+zc(st))}},[]),ha=d.useCallback(async A=>{const $=A!==void 0?A:ee.current;if(!$){Se([]),se([]),D([]),et("先选项目");return}et("");try{const st=await at(W.projectSkills($));Se(st.skills||[])}catch(st){bt("技能加载失败:"+st.message)}try{const st=await at(W.projectApps($));se(st.apps||[])}catch(st){bt("应用加载失败:"+st.message)}try{const st=await at(W.projectMcp($));D(st.servers||st.mcp_servers||[])}catch(st){bt("MCP 服务加载失败:"+st.message)}},[]),su=d.useCallback(A=>{if(!A.trim()){f("key 为空,未保存");return}tg(A.trim()),r(!0),f("已保存到本浏览器(只用于调本站 API)")},[]),ru=d.useCallback(()=>{eg(),r(!1),f("未填 key,数据接口会返回 401"),ol()},[ol]),Wl=d.useCallback(async(A,$)=>{if(!A.trim()){Nt("项目名必填");return}const st={name:A.trim()};$.trim()&&(st.repo_url=$.trim());try{await at(W.projects,"POST",st),Nt("项目已建"),await $t()}catch(Yt){Nt("新建失败:"+Yt.message)}},[$t]),Me=d.useCallback(async(A,$,st)=>{const Yt=ee.current;if(!Yt){xe("先选项目");return}if(!A.trim()){xe("环境名必填");return}const wt={project_id:Yt,name:A.trim()};$.trim()&&(wt.branch=$.trim()),st&&(wt.executor=st);try{await at(W.environmentsCreate,"POST",wt),xe("环境已建,worker 准备中(creating → ready,失败会在列表里写原因)"),await be(Yt)}catch(je){xe("新建失败:"+je.message)}},[be]),fl=d.useCallback(async A=>{if(window.confirm("确定归档该环境吗?归档后新任务不能再选它,进行中的任务不受影响(有进行中任务在用它时归档会被拒绝)。"))try{await at(W.environmentArchive(A),"POST",{}),await be()}catch($){xe("归档失败:"+$.message)}},[be]),Oe=d.useCallback(async A=>{const $=ee.current;if(!$){Be("先选项目");return}if(!A){Be("先选一个仓库");return}try{await at(W.project($),"PATCH",{repo_url:A}),Be("仓库已保存,现在可以建环境了"),await $t(),await be($)}catch(st){Be("保存失败:"+st.message)}},[$t,be]),ou=d.useCallback(async(A,$)=>{const st=ee.current;if(!st){Y("先选项目");return}if(!A.trim()){Y("请填成员 user_id 或用户名");return}try{await at(W.projectMembers(st),"POST",{user_id:A.trim(),role:$})}catch(Yt){if(Yt.message.includes("user not found"))try{await at(W.projectMembers(st),"POST",{username:A.trim(),role:$})}catch(wt){Y(zc(wt));return}else{Y(zc(Yt));return}}Y("已邀请成员"),await Qa(st)},[Qa]),fu=d.useCallback(async(A,$)=>{const st=ee.current;if(st)try{await at(W.projectMember(st,A),"PATCH",{role:$}),yt(Yt=>Yt.map(wt=>wt.user_id===A?{...wt,role:$}:wt)),Y("角色已改为 "+$)}catch(Yt){Y(zc(Yt))}},[]),du=d.useCallback(async()=>{const A=ee.current;if(!A){lt("先选项目");return}try{await at(W.projectMcpReload(A),"POST",{}),lt("配置已重载"),await ha(A)}catch($){lt("重载失败:"+$.message)}},[ha]),B=d.useCallback(async A=>{const $=ee.current;if(!$){re("先选项目");return}if(!A.trim()){re("服务名不能为空");return}try{const st=await at(W.projectMcpOauth($,A.trim()),"POST",{}),Yt=st.url||st.auth_url||st.login_url||st.authorization_url||"",wt=st.steps||st.instructions||"";let je=`登录尚未完成:请在浏览器打开下面的链接或按步骤完成,完成后回到本页刷新 MCP 状态。 `;Yt&&(je+=Yt+` `),wt&&(je+=(Array.isArray(wt)?wt.join(` `):String(wt))+` -`),je+=JSON.stringify(st,null,2).slice(0,4e3),jl(je),re("已返回登录信息,见下方")}catch(st){re("发起失败:"+st.message)}},[]),k=d.useCallback(async()=>{const A=X.trim();if(!A){F("请填要查配额的用户 id");return}try{const $=await at(W.adminQuotaUser(A));F("用户 "+A.slice(0,8)+":本月用量 "+ho($.used_usd_micros)+" / 配额 "+ho($.quota_usd_micros)+($.remaining_usd_micros!==null&&$.remaining_usd_micros!==void 0?"(剩余 "+ho($.remaining_usd_micros)+")":"")+" | 月份 "+($.month||""))}catch($){F(mo($))}},[X]),P=d.useCallback(async()=>{if(window.confirm("确定手动触发 sweep 吗?超阈值的陈旧 running 任务会被标为 interrupted。"))try{const $=(await at(W.adminSweep,"POST",{})).swept||[];F("sweep 完成,清理 "+$.length+" 个任务"+($.length?":"+$.join("、"):""))}catch(A){F(mo(A))}},[]),ct=d.useCallback(async()=>{try{const A=await at(W.adminBackup,"POST",{});F("备份完成:backend="+A.backend+" | 路径 "+A.path+" | 大小 "+A.size_bytes+" 字节 | 任务 "+A.task_count+" 行")}catch(A){F(mo(A))}},[]);return{keySaved:i,keyState:h,quotaText:p,healthText:C,adminGate:z,adminBody:q,adminResult:U,adminUserId:X,setAdminUserId:J,projects:V,projectId:St,setProjectId:jt,projectMsg:K,githubRepos:w,workers:nt,workersSummary:qt,workersError:kt,members:ot,memberMsg:G,memberError:tt,skills:xt,apps:L,mcpServers:y,skillMsg:ut,skillError:Ct,mcpMsg:Tt,mcpOauthMsg:mt,mcpOauthBox:Ga,envs:Xe,envCount:Qt,envMsg:Jl,envRepoLine:da,envRepoBar:zl,envRepoMsg:Tl,envSelect:oe,setEnvSelect:fe,models:rl,modelCustomVisible:ma,refreshHealth:qe,loadQuota:_l,probeAdmin:ol,refreshProjects:$t,loadGithubRepos:kl,loadRelayModels:mn,refreshWorkers:Qa,refreshEnvs:be,refreshMembers:Va,refreshSkills:ha,saveKeyInput:su,clearKeyInput:ru,createProject:Wl,createEnv:Me,archiveEnv:fl,saveEnvRepo:Oe,inviteMember:ou,changeMemberRole:fu,reloadMcp:du,mcpOauth:B,adminQuota:k,adminSweep:P,adminBackup:ct}}const vo=20;function ug(){const[i,r]=d.useState([]),[h,f]=d.useState(""),[p,N]=d.useState(""),[C,j]=d.useState(""),[z,R]=d.useState(""),[q,g]=d.useState(0),[U,F]=d.useState(0),[X,J]=d.useState({}),V=d.useRef(0),vt=d.useRef({status:"",executor:"",q:""});vt.current={status:p,executor:C,q:z};const St=d.useRef(""),jt=d.useCallback(async(ot,yt,G)=>{const Y=ot!==void 0?ot:yt;if(!Y){r([]),F(0),J({});return}St.current=Y;const tt=G!==void 0?G:V.current,gt=vt.current;try{const xt=await at(W.tasksPage(Y,vo,tt*vo,gt.status,gt.executor,gt.q));r(xt.tasks||[]),F(xt.total??(xt.tasks||[]).length),J(xt.status_counts||{}),f("")}catch(xt){f("加载失败:"+xt.message)}},[]),K=d.useCallback(ot=>{const yt=Math.max(0,ot);V.current=yt,g(yt)},[]),Nt=d.useCallback(ot=>{K(ot),jt(St.current||void 0,void 0,ot)},[jt,K]);d.useEffect(()=>{if(!St.current)return;const ot=setTimeout(()=>{jt(St.current,void 0,0)},300);return()=>clearTimeout(ot)},[p,C,z]);const w=d.useCallback(ot=>{N(ot),K(0)},[K]),Q=d.useCallback(ot=>{j(ot),K(0)},[K]),nt=d.useCallback(ot=>{R(ot),K(0)},[K]),Dt=d.useCallback(async ot=>{K(0),await jt(ot,void 0,0)},[jt,K]),qt=Math.max(1,Math.ceil(U/vo)),dt=U===0?"共 0 个任务":"共 "+U+" 个任务,第 "+(q+1)+" / "+qt+" 页",kt=i,H=d.useCallback(async(ot,yt)=>{const G={...ot};yt.length&&(G.upload_id=await lg(yt));const Y=await at(W.tasksCreate,"POST",G);return"任务已建:"+Ae(Y.id)},[]);return{tasks:i,taskError:h,statusFilter:p,setStatusFilter:w,executorFilter:C,setExecutorFilter:Q,searchText:z,setSearchText:nt,filteredTasks:kt,taskCountText:dt,refreshTasks:jt,refreshTasksFirstPage:Dt,page:q,totalPages:qt,taskTotal:U,taskStatusCounts:X,goToPage:Nt,dispatch:H}}function ig(i,r){const[h,f]=d.useState(null),[p,N]=d.useState(""),[C,j]=d.useState(""),[z,R]=d.useState(""),[q,g]=d.useState(""),[U,F]=d.useState(void 0),[X,J]=d.useState(!1),[V,vt]=d.useState(""),[St,jt]=d.useState([]),[K,Nt]=d.useState([]),[w,Q]=d.useState(""),[nt,Dt]=d.useState([]),[qt,dt]=d.useState(""),[kt,H]=d.useState(""),[ot,yt]=d.useState([]),[G,Y]=d.useState(""),[tt,gt]=d.useState(""),[xt,Se]=d.useState(""),[L,se]=d.useState(""),[y,D]=d.useState([]),[ut,et]=d.useState([]),[Ct,bt]=d.useState(""),[Tt,lt]=d.useState([]),[mt,re]=d.useState(""),[Ga,jl]=d.useState(""),[Xe,ae]=d.useState(""),[Qt,ye]=d.useState(""),[Jl,xe]=d.useState([]),[da,Le]=d.useState(""),[zl,Rl]=d.useState(null),Tl=d.useRef(null),Be=d.useRef(null),oe=d.useRef(null);oe.current=h;const fe=d.useCallback(()=>{if(Tl.current){try{Tl.current.abort()}catch{}Tl.current=null}Be.current&&(clearTimeout(Be.current),Be.current=null)},[]),rl=d.useCallback(async B=>{try{const k=await at(W.taskAgents(B));Nt(k.agents||[]),Q(k.note||"")}catch(k){Nt([]),Q("子代理链路加载失败:"+k.message)}},[]),Fl=d.useCallback(B=>{Be.current&&clearTimeout(Be.current),Be.current=setTimeout(()=>void rl(B),1e3)},[rl]),ma=d.useCallback(async B=>{if(!B.coordinator_parts){jt([]);return}try{const k=await at(W.tasksByParent(B.project_id||"",B.id));jt(k.tasks||[])}catch{jt([])}},[]),Dl=d.useCallback(async B=>{try{const k=await at(W.taskCost(B));F(k)}catch{F(null)}},[]),ee=d.useCallback(async B=>{try{const k=await at(W.taskFollowups(B));D(k.followups||[])}catch{D([])}},[]),Pe=d.useCallback(async B=>{try{const k=await at(W.taskChatHistory(B));Rl(k)}catch{Rl(null)}},[]),qe=d.useCallback(async B=>{var P;const k=B||((P=oe.current)==null?void 0:P.id);if(k)try{const ct=await at(W.taskReviews(k));Dt(ct.reviews||[]),H("")}catch(ct){H("评审队列加载失败:"+ct.message)}},[]),_l=d.useCallback(async B=>{var P;const k=B||((P=oe.current)==null?void 0:P.id);if(k)try{const ct=await at(W.taskApprovals(k));et(ct.approvals||[])}catch(ct){bt("审批队列加载失败:"+ct.message)}},[]),ol=d.useCallback(async B=>{try{const k=await at(W.taskMessages(B));lt(k.messages||[])}catch{lt([])}},[]),$t=d.useCallback(async B=>{try{const k=await at(W.taskThread(B)),P=k.thread||k;let ct=P.turns;ct==null&&(ct=P.turn_count),ct==null&&(ct=P.turns_count),jl("id "+(P.id||B)+" | 名字 "+(P.name||P.thread_name||"")+" | 归档 "+(P.archived===void 0||P.archived===null?"":P.archived?"是":"否")+" | cwd "+(P.cwd||"")+(ct!=null?" | turn 数 "+ct:""))}catch(k){jl("线程加载失败:"+k.message)}},[]),kl=d.useCallback(async B=>{try{const k=await at(W.taskThreads(B));xe(k.threads||[])}catch{xe([])}},[]),mn=d.useCallback(B=>{const k=["completed","failed","interrupted"].includes(B);ae(k?"终态任务,线程操作可用":"仅终态任务可操作线程,当前状态 "+B+",按钮已置灰")},[]),Qa=d.useCallback(async(B,k)=>{if(fe(),yt([]),Y(""),gt(""),uu.includes(k)){try{const wt=await at(W.taskTrace(B));yt(wt.entries||[]),Y(wt.note||""),gt(wt.status||"")}catch(wt){Y("链路加载失败:"+wt.message)}return}const P=new AbortController;Tl.current=P;let ct=[];const A={};let $="",st=!1,Yt="";try{const wt={},je=sl();je&&(wt["X-API-Key"]=je);const Te=await fetch(W.taskTraceStream(B),{headers:wt,signal:P.signal});if(!Te.ok){let $l="HTTP "+Te.status;try{const wl=await Te.json();wl&&wl.error&&($l=wl.error)}catch{}throw Te.status===401&&($l+="(key 未填或无效)"),new Error($l)}const wc=Te.body.getReader(),si=new TextDecoder;let hn="";for(;;){const $l=await wc.read();if($l.done)break;hn+=si.decode($l.value,{stream:!0});const wl=hn.split(` +`),je+=JSON.stringify(st,null,2).slice(0,4e3),jl(je),re("已返回登录信息,见下方")}catch(st){re("发起失败:"+st.message)}},[]),k=d.useCallback(async()=>{const A=X.trim();if(!A){F("请填要查配额的用户 id");return}try{const $=await at(W.adminQuotaUser(A));F("用户 "+A.slice(0,8)+":本月用量 "+ho($.used_usd_micros)+" / 配额 "+ho($.quota_usd_micros)+($.remaining_usd_micros!==null&&$.remaining_usd_micros!==void 0?"(剩余 "+ho($.remaining_usd_micros)+")":"")+" | 月份 "+($.month||""))}catch($){F(mo($))}},[X]),P=d.useCallback(async()=>{if(window.confirm("确定手动触发 sweep 吗?超阈值的陈旧 running 任务会被标为 interrupted。"))try{const $=(await at(W.adminSweep,"POST",{})).swept||[];F("sweep 完成,清理 "+$.length+" 个任务"+($.length?":"+$.join("、"):""))}catch(A){F(mo(A))}},[]),ct=d.useCallback(async()=>{try{const A=await at(W.adminBackup,"POST",{});F("备份完成:backend="+A.backend+" | 路径 "+A.path+" | 大小 "+A.size_bytes+" 字节 | 任务 "+A.task_count+" 行")}catch(A){F(mo(A))}},[]);return{keySaved:i,keyState:h,quotaText:p,healthText:C,adminGate:z,adminBody:q,adminResult:U,adminUserId:X,setAdminUserId:J,projects:Q,projectId:St,setProjectId:jt,projectMsg:K,githubRepos:w,workers:nt,workersSummary:qt,workersError:kt,members:ot,memberMsg:G,memberError:tt,skills:xt,apps:L,mcpServers:y,skillMsg:ut,skillError:Ct,mcpMsg:Tt,mcpOauthMsg:mt,mcpOauthBox:Ga,envs:Xe,envCount:Vt,envMsg:Jl,envRepoLine:da,envRepoBar:zl,envRepoMsg:Tl,envSelect:oe,setEnvSelect:fe,models:rl,modelCustomVisible:ma,refreshHealth:qe,loadQuota:_l,probeAdmin:ol,refreshProjects:$t,loadGithubRepos:kl,loadRelayModels:mn,refreshWorkers:Va,refreshEnvs:be,refreshMembers:Qa,refreshSkills:ha,saveKeyInput:su,clearKeyInput:ru,createProject:Wl,createEnv:Me,archiveEnv:fl,saveEnvRepo:Oe,inviteMember:ou,changeMemberRole:fu,reloadMcp:du,mcpOauth:B,adminQuota:k,adminSweep:P,adminBackup:ct}}const vo=20;function ug(){const[i,r]=d.useState([]),[h,f]=d.useState(""),[p,N]=d.useState(""),[C,j]=d.useState(""),[z,R]=d.useState(""),[q,g]=d.useState(0),[U,F]=d.useState(0),[X,J]=d.useState({}),Q=d.useRef(0),vt=d.useRef({status:"",executor:"",q:""});vt.current={status:p,executor:C,q:z};const St=d.useRef(""),jt=d.useCallback(async(ot,yt,G)=>{const Y=ot!==void 0?ot:yt;if(!Y){r([]),F(0),J({});return}St.current=Y;const tt=G!==void 0?G:Q.current,gt=vt.current;try{const xt=await at(W.tasksPage(Y,vo,tt*vo,gt.status,gt.executor,gt.q));r(xt.tasks||[]),F(xt.total??(xt.tasks||[]).length),J(xt.status_counts||{}),f("")}catch(xt){f("加载失败:"+xt.message)}},[]),K=d.useCallback(ot=>{const yt=Math.max(0,ot);Q.current=yt,g(yt)},[]),Nt=d.useCallback(ot=>{K(ot),jt(St.current||void 0,void 0,ot)},[jt,K]);d.useEffect(()=>{if(!St.current)return;const ot=setTimeout(()=>{jt(St.current,void 0,0)},300);return()=>clearTimeout(ot)},[p,C,z]);const w=d.useCallback(ot=>{N(ot),K(0)},[K]),V=d.useCallback(ot=>{j(ot),K(0)},[K]),nt=d.useCallback(ot=>{R(ot),K(0)},[K]),Dt=d.useCallback(async ot=>{K(0),await jt(ot,void 0,0)},[jt,K]),qt=Math.max(1,Math.ceil(U/vo)),dt=U===0?"共 0 个任务":"共 "+U+" 个任务,第 "+(q+1)+" / "+qt+" 页",kt=i,H=d.useCallback(async(ot,yt)=>{const G={...ot};yt.length&&(G.upload_id=await lg(yt));const Y=await at(W.tasksCreate,"POST",G);return"任务已建:"+Ae(Y.id)},[]);return{tasks:i,taskError:h,statusFilter:p,setStatusFilter:w,executorFilter:C,setExecutorFilter:V,searchText:z,setSearchText:nt,filteredTasks:kt,taskCountText:dt,refreshTasks:jt,refreshTasksFirstPage:Dt,page:q,totalPages:qt,taskTotal:U,taskStatusCounts:X,goToPage:Nt,dispatch:H}}function ig(i,r){const[h,f]=d.useState(null),[p,N]=d.useState(""),[C,j]=d.useState(""),[z,R]=d.useState(""),[q,g]=d.useState(""),[U,F]=d.useState(void 0),[X,J]=d.useState(!1),[Q,vt]=d.useState(""),[St,jt]=d.useState([]),[K,Nt]=d.useState([]),[w,V]=d.useState(""),[nt,Dt]=d.useState([]),[qt,dt]=d.useState(""),[kt,H]=d.useState(""),[ot,yt]=d.useState([]),[G,Y]=d.useState(""),[tt,gt]=d.useState(""),[xt,Se]=d.useState(""),[L,se]=d.useState(""),[y,D]=d.useState([]),[ut,et]=d.useState([]),[Ct,bt]=d.useState(""),[Tt,lt]=d.useState([]),[mt,re]=d.useState(""),[Ga,jl]=d.useState(""),[Xe,ae]=d.useState(""),[Vt,ye]=d.useState(""),[Jl,xe]=d.useState([]),[da,Le]=d.useState(""),[zl,Rl]=d.useState(null),Tl=d.useRef(null),Be=d.useRef(null),oe=d.useRef(null);oe.current=h;const fe=d.useCallback(()=>{if(Tl.current){try{Tl.current.abort()}catch{}Tl.current=null}Be.current&&(clearTimeout(Be.current),Be.current=null)},[]),rl=d.useCallback(async B=>{try{const k=await at(W.taskAgents(B));Nt(k.agents||[]),V(k.note||"")}catch(k){Nt([]),V("子代理链路加载失败:"+k.message)}},[]),Fl=d.useCallback(B=>{Be.current&&clearTimeout(Be.current),Be.current=setTimeout(()=>void rl(B),1e3)},[rl]),ma=d.useCallback(async B=>{if(!B.coordinator_parts){jt([]);return}try{const k=await at(W.tasksByParent(B.project_id||"",B.id));jt(k.tasks||[])}catch{jt([])}},[]),Dl=d.useCallback(async B=>{try{const k=await at(W.taskCost(B));F(k)}catch{F(null)}},[]),ee=d.useCallback(async B=>{try{const k=await at(W.taskFollowups(B));D(k.followups||[])}catch{D([])}},[]),Pe=d.useCallback(async B=>{try{const k=await at(W.taskChatHistory(B));Rl(k)}catch{Rl(null)}},[]),qe=d.useCallback(async B=>{var P;const k=B||((P=oe.current)==null?void 0:P.id);if(k)try{const ct=await at(W.taskReviews(k));Dt(ct.reviews||[]),H("")}catch(ct){H("评审队列加载失败:"+ct.message)}},[]),_l=d.useCallback(async B=>{var P;const k=B||((P=oe.current)==null?void 0:P.id);if(k)try{const ct=await at(W.taskApprovals(k));et(ct.approvals||[])}catch(ct){bt("审批队列加载失败:"+ct.message)}},[]),ol=d.useCallback(async B=>{try{const k=await at(W.taskMessages(B));lt(k.messages||[])}catch{lt([])}},[]),$t=d.useCallback(async B=>{try{const k=await at(W.taskThread(B)),P=k.thread||k;let ct=P.turns;ct==null&&(ct=P.turn_count),ct==null&&(ct=P.turns_count),jl("id "+(P.id||B)+" | 名字 "+(P.name||P.thread_name||"")+" | 归档 "+(P.archived===void 0||P.archived===null?"":P.archived?"是":"否")+" | cwd "+(P.cwd||"")+(ct!=null?" | turn 数 "+ct:""))}catch(k){jl("线程加载失败:"+k.message)}},[]),kl=d.useCallback(async B=>{try{const k=await at(W.taskThreads(B));xe(k.threads||[])}catch{xe([])}},[]),mn=d.useCallback(B=>{const k=["completed","failed","interrupted"].includes(B);ae(k?"终态任务,线程操作可用":"仅终态任务可操作线程,当前状态 "+B+",按钮已置灰")},[]),Va=d.useCallback(async(B,k)=>{if(fe(),yt([]),Y(""),gt(""),uu.includes(k)){try{const wt=await at(W.taskTrace(B));yt(wt.entries||[]),Y(wt.note||""),gt(wt.status||"")}catch(wt){Y("链路加载失败:"+wt.message)}return}const P=new AbortController;Tl.current=P;let ct=[];const A={};let $="",st=!1,Yt="";try{const wt={},je=sl();je&&(wt["X-API-Key"]=je);const Te=await fetch(W.taskTraceStream(B),{headers:wt,signal:P.signal});if(!Te.ok){let $l="HTTP "+Te.status;try{const wl=await Te.json();wl&&wl.error&&($l=wl.error)}catch{}throw Te.status===401&&($l+="(key 未填或无效)"),new Error($l)}const wc=Te.body.getReader(),si=new TextDecoder;let hn="";for(;;){const $l=await wc.read();if($l.done)break;hn+=si.decode($l.value,{stream:!0});const wl=hn.split(` `);hn=wl.pop()||"";for(const mu of wl){const hu=mu.split(` `).filter(Ze=>Ze.indexOf("data: ")===0).map(Ze=>Ze.slice(6)).join(` @@ -64,17 +64,17 @@ Error generating stack: `+a.message+` `+k.result_text.slice(0,8e3)+` `),k.error_text&&(P+=`=== error === `+k.error_text.slice(0,8e3)+` -`),P||(P="(暂无 result/error 输出)"),N(P),g(""),F(void 0),Dl(B),J(!1),vt(""),re(""),ye(""),mn(k.status);const ct=i(k.project_id),A=ct==="maintainer"||ct==="owner",$=ct==="contributor"||A,st=uu.includes(k.status),Yt=[];["failed","interrupted","merge-failed"].includes(k.status)&&A||Yt.push("重试:不可用——仅 failed/interrupted/merge-failed 可重试"+(A?"":",且仅 maintainer 可重试")),!st&&$||Yt.push("取消:不可用——"+(st?"终态任务不可取消":"仅项目成员(contributor 及以上)可取消")),k.status==="completed"&&A||Yt.push("合并:不可用——仅 completed 可合并"+(A?"":",且仅 maintainer 可合并")),k.status==="merged"&&A||Yt.push("提 PR:不可用——仅 merged 可提 PR"+(A?"":",且仅 maintainer 可提 PR")),R(Yt.join(";")),j(""),Qa(B,k.status),rl(B),ma(k),Pe(B),ee(B),ol(B),$t(B),kl(B),qe(B),_l(B)}catch(k){f(null),N("加载失败:"+k.message)}},[fe,Qa,rl,ma,Pe,ee,ol,$t,kl,qe,_l,Dl,i]),Va=d.useCallback(async()=>{const B=oe.current;if(B){try{const k=await at(W.task(B.id));f(k);let P="";k.result_text&&(P+=`=== result === +`),P||(P="(暂无 result/error 输出)"),N(P),g(""),F(void 0),Dl(B),J(!1),vt(""),re(""),ye(""),mn(k.status);const ct=i(k.project_id),A=ct==="maintainer"||ct==="owner",$=ct==="contributor"||A,st=uu.includes(k.status),Yt=[];["failed","interrupted","merge-failed"].includes(k.status)&&A||Yt.push("重试:不可用——仅 failed/interrupted/merge-failed 可重试"+(A?"":",且仅 maintainer 可重试")),!st&&$||Yt.push("取消:不可用——"+(st?"终态任务不可取消":"仅项目成员(contributor 及以上)可取消")),k.status==="completed"&&A||Yt.push("合并:不可用——仅 completed 可合并"+(A?"":",且仅 maintainer 可合并")),k.status==="merged"&&A||Yt.push("提 PR:不可用——仅 merged 可提 PR"+(A?"":",且仅 maintainer 可提 PR")),R(Yt.join(";")),j(""),Va(B,k.status),rl(B),ma(k),Pe(B),ee(B),ol(B),$t(B),kl(B),qe(B),_l(B)}catch(k){f(null),N("加载失败:"+k.message)}},[fe,Va,rl,ma,Pe,ee,ol,$t,kl,qe,_l,Dl,i]),Qa=d.useCallback(async()=>{const B=oe.current;if(B){try{const k=await at(W.task(B.id));f(k);let P="";k.result_text&&(P+=`=== result === `+k.result_text.slice(0,8e3)+` `),k.error_text&&(P+=`=== error === `+k.error_text.slice(0,8e3)+` -`),N(P||"(暂无 result/error 输出)")}catch{}ee(B.id),Pe(B.id),qe(B.id),_l(B.id),ma(B),rl(B.id),ol(B.id),$t(B.id),kl(B.id),Dl(B.id)}},[ee,Pe,qe,_l,ma,rl,ol,$t,kl,Dl]),ha=d.useCallback(async B=>{const k=oe.current;if(!k)return;const P={cancel:"确定取消该任务吗?",merge:"确定把 completed 任务标为 merged 吗?",pr:"确定为该任务提 PR 吗?(会在仓库真实建 PR)"};if(!(P[B]&&!window.confirm(P[B])))try{const ct=await at(W.taskAction(k.id,B),"POST",{});j(B+" 已提交:"+JSON.stringify(ct).slice(0,300)),r(),await be(k.id)}catch(ct){j(B+" 失败:"+ct.message)}},[be,r]),su=d.useCallback(async()=>{const B=oe.current;if(B)try{const k=await at(W.taskCost(B.id));g(JSON.stringify(k,null,2).slice(0,4e3))}catch(k){g("查成本失败:"+k.message)}},[]),ru=d.useCallback(async B=>{const k=oe.current;if(k){if(!B.trim()){vt("分支消息必填");return}if(!uu.includes(k.status)){vt("仅终态任务可分支");return}try{const P=await at(W.taskFork(k.id),"POST",{message:B.trim()});vt("分支任务已建:"+P.id.slice(0,8)),r()}catch(P){vt("分支失败:"+P.message)}}},[r]),Wl=d.useCallback(async B=>{const k=oe.current;if(k)try{await at(W.taskReviews(k.id),"POST",B),dt("评审已登记(requested)"),await qe(k.id)}catch(P){dt("登记评审需求失败:"+P.message)}},[qe]),Me=d.useCallback(B=>{const k=oe.current;if(!k)return;const P=window.prompt("输入该条评审的 findings(将写回记录并标为 completed):");P!==null&&at(W.taskReviewFindings(k.id,B),"POST",{findings:P}).then(()=>void qe(k.id)).catch(ct=>dt("写 findings 失败:"+ct.message))},[qe]),fl=d.useCallback((B,k)=>{const P=oe.current;if(!P)return;const ct=k==="approved"?"approve":"deny";at(W.taskApprovalDecision(P.id,B,ct),"POST",{}).then(()=>{bt("已记录"+(k==="approved"?"允许":"拒绝")+"(当前版本只留回执,不拦截实时行为)"),_l(P.id)}).catch(A=>bt("决定失败:"+A.message))},[_l]),Oe=d.useCallback(async(B,k,P)=>{const ct=oe.current;if(ct){if(!k.trim()){re("目标任务 id 不能为空");return}if(!P.trim()){re("消息正文不能为空");return}try{const A=await at(W.taskMessagesTo(k.trim()),"POST",{from_task_id:ct.id,body:P.trim(),mode:B});re("已发送("+A.delivery+")"),await ol(ct.id)}catch(A){re("发送失败:"+A.message)}}},[ol]),ou=d.useCallback((B,k)=>{const P=oe.current;if(!P)return;if(!["completed","failed","interrupted"].includes(P.status)){ye("仅 completed/failed/interrupted 任务可操作线程(merged/merge-failed 不可改,服务端同样 409)");return}const ct={};if(B==="rename"){if(!k.trim()){ye("新线程名不能为空");return}ct.name=k.trim()}at(W.taskThreadAction(P.id,B),"POST",ct).then(A=>{ye(B+" 已提交:"+JSON.stringify(A).slice(0,300)),$t(P.id),kl(P.id)}).catch(A=>ye(B+" 失败:"+A.message))},[$t,kl]),fu=d.useCallback(async B=>{const k=oe.current;if(!k)return;const P=B.trim();if(!P){Le("消息不能为空");return}try{uu.includes(k.status)&&k.status!=="merged"?(await at(W.taskContinue(k.id),"POST",{message:P}),Le("已重新排队,同一会话继续"),r(),await be(k.id)):["queued","running"].includes(k.status)?(await at(W.taskFollowups(k.id),"POST",{body:P,mode:"direct"}),Le("已直接发送"),await ee(k.id),await Pe(k.id)):Le("merged/merge-failed 任务不可继续,也不可发后续消息")}catch(ct){Le("发送失败:"+ct.message)}},[be,r,ee,Pe]),du=d.useCallback(async()=>{const B=oe.current;if(!B){se("先选一个任务");return}try{const k=await at(W.taskTrace(B.id)),P=new Blob([JSON.stringify({task_id:B.id,status:k.status,note:k.note||"",count:(k.entries||[]).length,entries:k.entries||[]},null,2)],{type:"application/json"}),ct=document.createElement("a");ct.href=URL.createObjectURL(P),ct.download="trace-"+B.id.slice(0,8)+".json",document.body.appendChild(ct),ct.click(),ct.remove(),setTimeout(()=>URL.revokeObjectURL(ct.href),1e3),se("已下载完整链路 "+(k.entries||[]).length+" 条")}catch(k){se("下载失败:"+k.message)}},[]);return d.useEffect(()=>()=>fe(),[fe]),{currentTask:h,detailBody:p,actionMsg:C,actionReasons:z,costBox:q,costSummary:U,forkVisible:X,setForkVisible:J,forkMsg:V,coordChildren:St,agents:K,agentsNote:w,reviews:nt,reviewMsg:qt,reviewError:kt,traceEntries:ot,traceNote:G,traceDone:tt,traceKind:xt,setTraceKind:Se,traceMsg:L,followups:y,approvals:ut,approvalMsg:Ct,peerMessages:Tt,peerMsg:mt,threadText:Ga,threadGateNote:Xe,threadMsg:Qt,threadList:Jl,chatMsg:da,chatHistory:zl,showDetail:be,refreshDetailLists:Va,doAction:ha,taskCost:su,forkTask:ru,postReview:Wl,submitFindings:Me,decideApproval:fl,postPeer:Oe,threadAction:ou,sendChatMessage:fu,downloadTrace:du,refreshReviews:()=>void qe(),refreshApprovals:()=>void _l()}}function cg(i){const[r,h]=d.useState([]),[f,p]=d.useState(""),[N,C]=d.useState(null),[j,z]=d.useState(""),R=d.useCallback(async X=>{const J=X!==void 0?X:i();if(!J){h([]);return}try{const V=await at(W.projectAutomations(J));h(V.automations||[])}catch(V){p("自动化加载失败:"+V.message)}},[i]),q=d.useCallback(async(X,J,V,vt)=>{const St=i();if(!St){p("先选项目");return}if(!X.trim()){p("自动化名称必填");return}if(!J.trim()){p("定时任务内容必填");return}const jt={name:X.trim(),prompt:J.trim()};V.trim()&&(jt.interval_seconds=parseInt(V.trim(),10)),vt.trim()&&(jt.repo_url=vt.trim());try{await at(W.projectAutomations(St),"POST",jt),p("自动化已创建(登记定义,到期后由执行器认领)"),await R(St)}catch(K){p("创建失败:"+K.message)}},[i,R]),g=d.useCallback(async(X,J,V,vt,St)=>{const jt=i();if(!jt)return p("先选项目"),!1;const K=J.trim(),Nt=V.trim();if(!K)return p("自动化名称必填"),!1;if(!Nt)return p("定时任务内容必填"),!1;const w={};if(K!==(X.name||"")&&(w.name=K),Nt!==(X.prompt||"")&&(w.prompt=Nt),vt.trim()){const nt=parseInt(vt.trim(),10);if(Number.isNaN(nt))return p("间隔秒数必须是数字"),!1;if(nt<60)return p("间隔秒数最小 60"),!1;nt!==X.interval_seconds&&(w.interval_seconds=nt)}const Q=St.trim();if(Q!==(X.repo_url||"")&&(w.repo_url=Q===""?null:Q),Object.keys(w).length===0)return p("没有改动"),!1;try{return await at(W.projectAutomation(jt,X.id),"PATCH",w),p("自动化已更新"),await R(jt),!0}catch(nt){return p("更新失败:"+nt.message),!1}},[i,R]),U=d.useCallback(X=>{const J=i();J&&at(W.projectAutomationCancel(J,X),"POST",{}).then(()=>{p("自动化已取消"),R(J)}).catch(V=>p("取消失败:"+V.message))},[i,R]),F=d.useCallback(async X=>{const J=X!==void 0?X:i();if(!J){C(null);return}try{const V=await at(W.projectTriage(J));C(V),z("")}catch(V){z("分诊加载失败:"+V.message)}},[i]);return{automations:r,autoMsg:f,triage:N,triageError:j,refreshAutomations:R,createAutomation:q,updateAutomation:g,cancelAutomation:U,refreshTriage:F}}function sg(i,r){const h=d.useRef(i);h.current=i,d.useEffect(()=>{const f=setInterval(()=>{const p=document.activeElement;h.current(p?p.tagName==="SELECT":!1)},r);return()=>clearInterval(f)},[r])}function rg(i,r){const h=d.useRef(i);h.current=i,d.useEffect(()=>{const f=setInterval(()=>h.current(),r);return()=>clearInterval(f)},[r])}function og(i){const r=h=>String(h).padStart(2,"0");return r(i.getHours())+":"+r(i.getMinutes())+":"+r(i.getSeconds())}function fg(){var J;const i=ng(),r=ug(),h=d.useRef("");h.current=i.projectId;const f=d.useCallback(()=>h.current,[]),p=cg(f),N=d.useCallback(V=>{const vt=i.projects.find(St=>St.id===V);return vt&&vt.my_role||null},[i.projects]),C=d.useCallback(()=>{r.refreshTasks(void 0,h.current)},[r]),j=ig(N,C),[z,R]=d.useState("--:--:--"),q=d.useCallback(V=>{i.setProjectId(V),h.current=V,r.refreshTasksFirstPage(V),i.refreshEnvs(V),i.refreshMembers(V),i.refreshSkills(V),p.refreshAutomations(V),p.refreshTriage(V)},[i,r,p]),g=(J=i.projects[0])==null?void 0:J.id;d.useEffect(()=>{!i.projectId&&g&&q(g)},[i.projectId,g,q]);const U=d.useCallback(V=>{i.saveKeyInput(V),V.trim()&&(i.refreshHealth(),i.refreshProjects(),i.refreshWorkers(),h.current&&(r.refreshTasks(h.current),i.refreshEnvs(h.current),i.refreshMembers(h.current)),i.loadRelayModels(),i.loadQuota(),i.loadGithubRepos(),i.probeAdmin())},[i,r]),F=d.useCallback(V=>{if(R(og(new Date)),!sl()){i.refreshHealth();return}r.refreshTasks(void 0,h.current),i.refreshHealth(),i.loadQuota(),i.refreshWorkers(),i.refreshMembers(),i.refreshSkills(),p.refreshAutomations(),p.refreshTriage(),j.refreshDetailLists(),V||(i.refreshProjects(),i.refreshEnvs())},[i,r,p,j]),X=d.useCallback(()=>{sl()&&(i.loadRelayModels(),i.loadGithubRepos())},[i]);return d.useEffect(()=>{i.refreshHealth(),i.refreshProjects(),i.refreshWorkers(),i.loadQuota(),i.loadGithubRepos(),sl()&&i.loadRelayModels(),i.probeAdmin()},[]),{keySaved:i.keySaved,keyState:i.keyState,saveKeyInput:U,clearKeyInput:i.clearKeyInput,quotaText:i.quotaText,healthText:i.healthText,refreshHealth:i.refreshHealth,adminGate:i.adminGate,adminBody:i.adminBody,adminResult:i.adminResult,adminUserId:i.adminUserId,setAdminUserId:i.setAdminUserId,adminQuota:i.adminQuota,adminSweep:i.adminSweep,adminBackup:i.adminBackup,projects:i.projects,projectId:i.projectId,projectMsg:i.projectMsg,selectProject:q,refreshProjects:i.refreshProjects,createProject:i.createProject,githubRepos:i.githubRepos,workers:i.workers,workersSummary:i.workersSummary,workersError:i.workersError,refreshWorkers:i.refreshWorkers,members:i.members,memberMsg:i.memberMsg,memberError:i.memberError,refreshMembers:()=>void i.refreshMembers(),inviteMember:i.inviteMember,changeMemberRole:i.changeMemberRole,skills:i.skills,apps:i.apps,mcpServers:i.mcpServers,skillMsg:i.skillMsg,skillError:i.skillError,mcpMsg:i.mcpMsg,mcpOauthMsg:i.mcpOauthMsg,mcpOauthBox:i.mcpOauthBox,refreshSkills:()=>void i.refreshSkills(),reloadMcp:i.reloadMcp,mcpOauth:i.mcpOauth,envs:i.envs,envCount:i.envCount,envMsg:i.envMsg,envRepoLine:i.envRepoLine,envRepoBar:i.envRepoBar,envRepoMsg:i.envRepoMsg,envSelect:i.envSelect,setEnvSelect:i.setEnvSelect,refreshEnvs:()=>void i.refreshEnvs(),createEnv:i.createEnv,archiveEnv:i.archiveEnv,saveEnvRepo:i.saveEnvRepo,models:i.models,modelCustomVisible:i.modelCustomVisible,tasks:r.tasks,taskCountText:r.taskCountText,taskError:r.taskError,refreshTasks:()=>void r.refreshTasks(void 0,h.current),statusFilter:r.statusFilter,setStatusFilter:r.setStatusFilter,executorFilter:r.executorFilter,setExecutorFilter:r.setExecutorFilter,searchText:r.searchText,setSearchText:r.setSearchText,filteredTasks:r.filteredTasks,taskPage:r.page,taskTotalPages:r.totalPages,taskTotal:r.taskTotal,taskStatusCounts:r.taskStatusCounts,goToTaskPage:r.goToPage,currentTask:j.currentTask,detailBody:j.detailBody,actionMsg:j.actionMsg,actionReasons:j.actionReasons,showDetail:V=>void j.showDetail(V),doAction:j.doAction,taskCost:j.taskCost,costBox:j.costBox,costSummary:j.costSummary,forkVisible:j.forkVisible,setForkVisible:j.setForkVisible,forkTask:j.forkTask,forkMsg:j.forkMsg,coordChildren:j.coordChildren,coordText:"",agents:j.agents,agentsNote:j.agentsNote,reviews:j.reviews,reviewMsg:j.reviewMsg,reviewError:j.reviewError,refreshReviews:j.refreshReviews,postReview:j.postReview,submitFindings:j.submitFindings,traceEntries:j.traceEntries,traceNote:j.traceNote,traceDone:j.traceDone,traceKind:j.traceKind,setTraceKind:j.setTraceKind,downloadTrace:j.downloadTrace,traceMsg:j.traceMsg,sendChatMessage:j.sendChatMessage,chatMsg:j.chatMsg,chatHistory:j.chatHistory,followups:j.followups,approvals:j.approvals,approvalMsg:j.approvalMsg,refreshApprovals:j.refreshApprovals,decideApproval:j.decideApproval,peerMessages:j.peerMessages,peerMsg:j.peerMsg,postPeer:j.postPeer,automations:p.automations,autoMsg:p.autoMsg,refreshAutomations:()=>void p.refreshAutomations(),createAutomation:p.createAutomation,updateAutomation:p.updateAutomation,cancelAutomation:p.cancelAutomation,triage:p.triage,triageError:p.triageError,refreshTriage:()=>void p.refreshTriage(),threadText:j.threadText,threadGateNote:j.threadGateNote,threadMsg:j.threadMsg,threadList:j.threadList,threadAction:j.threadAction,dispatch:async(V,vt)=>{const St=await r.dispatch(V,vt);return r.refreshTasksFirstPage(h.current),St},dispatchMsg:"",refreshAll:F,refreshSlow:X,refreshStamp:z}}function nu({label:i,value:r,sub:h}){return c.jsxs("div",{className:"dash-card data-card",children:[c.jsx("div",{className:"dash-card-label",children:i}),c.jsx("div",{className:"dash-card-value",children:r}),c.jsx("div",{className:"muted",children:h})]})}function dg(){const i=Kt(),r=i.healthText==="未加载"?"未加载":i.healthText.includes("worker_ok=true")?"正常":i.healthText.includes("worker_ok=false")?"异常":i.healthText.includes("加载失败")?"加载失败":"未知",h=i.quotaText.match(/剩余 \$([0-9.]+)/),f=i.quotaText.includes("加载失败")?"加载失败":i.quotaText.includes("未填 key")||i.quotaText==="未加载"?"未加载":h?"$"+h[1]:"配额",p=["running","completed","failed","queued","merged","interrupted"].map(g=>g+" "+(i.taskStatusCounts[g]||0)).join(" | "),N=i.projects.find(g=>g.id===i.projectId),C=i.taskError?{value:"加载失败",sub:i.taskError}:i.projectId?{value:String(i.taskTotal),sub:i.taskCountText+" | "+p+(N?" | "+N.name:"")}:{value:"先选项目",sub:i.projectMsg||"项目加载后自动选第一个"};let j;if(i.workersError)j={value:"加载失败",sub:i.workersError};else if(i.workers===null)j={value:i.workersSummary||"未加载",sub:"完整 worker 清单仅全局管理员可见"};else{const g=i.workers.filter(U=>{if(U.alive!==void 0&&U.alive!==null)return!!U.alive;const F=xo(U).age;return F!==null&&F<=120}).length;j={value:g+" 在线 / "+i.workers.length,sub:i.workers.length-g+" 离线"}}const z=i.projectMsg.includes("加载失败")?{value:"加载失败",sub:i.projectMsg}:{value:i.projects.length+" 个",sub:N?"当前项目:"+N.name:"未选项目(加载后自动选第一个)"},R=[i.envMsg,i.memberError,i.skillError].filter(g=>g&&g.includes("失败")),q=i.projectId?R.length?{value:"部分加载失败",sub:R.join(" | ")}:{value:i.envs.length+" 个环境",sub:i.members.length+" 成员 | "+i.skills.length+" 技能 | "+i.apps.length+" 应用 | "+i.mcpServers.length+" MCP"}:{value:"先选项目",sub:"选项目后显示环境 / 成员 / 技能计数"};return c.jsxs("section",{className:"dash-cards","aria-label":"数据卡片",children:[c.jsx(nu,{label:"健康",value:r,sub:i.healthText}),c.jsx(nu,{label:"配额",value:f,sub:i.quotaText}),c.jsx(nu,{label:"任务",value:C.value,sub:C.sub}),c.jsx(nu,{label:"Workers",value:j.value,sub:j.sub}),c.jsx(nu,{label:"项目",value:z.value,sub:z.sub}),c.jsx(nu,{label:"项目数据",value:q.value,sub:q.sub})]})}function Ft({id:i,className:r,title:h,children:f}){return c.jsxs("section",{id:i,className:"card"+(r?" "+r:""),children:[h?c.jsx("h2",{children:h}):null,f]})}function Ht({children:i}){return c.jsx("div",{className:"bar",children:i})}function bl({items:i,empty:r}){return i.length?c.jsx("ul",{className:"data-list",children:i.map((h,f)=>c.jsx("li",{children:h},f))}):c.jsx("div",{className:"muted",children:r||"(暂无)"})}function cu({children:i}){return c.jsx("ul",{className:"data-list",children:i})}function mg(i){if(i.alive!==void 0&&i.alive!==null)return!!i.alive;const r=xo(i);return r.age!==null&&r.age<=120}function hg(i){const r=i.filter(h=>h.executor==="cloud-hypervisor");return{online:r.filter(mg).length,total:r.length}}function ci(i,r,h){return i?{state:"known",...hg(i)}:h?{state:"unknown",reason:"load-failed"}:r.includes("无 worker 注册")?{state:"known",online:0,total:0}:r.includes("完整 worker 清单仅全局管理员可见")?{state:"unknown",reason:"aggregate-only"}:{state:"unknown",reason:"not-loaded"}}function kc(i,r,h){return i!=="cloud-hypervisor"||r!=="queued"?"":h.state==="known"&&h.online===0?"可能原因:当前无 cloud-hypervisor worker 在线,任务在排队(基于 /workers 推断;VM 状态/IP/快照等后端未返回字段不展示)。请先确认 KVM 宿主上的 CHV worker 已起,worker 上线后可认领。":""}function vg(){const i=Kt(),[r,h]=d.useState(""),[f,p]=d.useState(""),[N,C]=d.useState(!1),[j,z]=d.useState(!1),[R,q]=d.useState(!1),[g,U]=d.useState("docker"),[F,X]=d.useState(""),[J,V]=d.useState(""),[vt,St]=d.useState(""),[jt,K]=d.useState(""),[Nt,w]=d.useState(""),[Q,nt]=d.useState(""),[Dt,qt]=d.useState(""),dt=d.useRef(null),kt=d.useRef(null),H=d.useRef(null),[ot,yt]=d.useState(!1),[G,Y]=d.useState(""),tt=ci(i.workers,i.workersSummary,i.workersError),gt=g==="cloud-hypervisor";function xt(){var L,se,y,D,ut,et;yt(!!((se=(L=dt.current)==null?void 0:L.files)!=null&&se.length||(D=(y=kt.current)==null?void 0:y.files)!=null&&D.length||(et=(ut=H.current)==null?void 0:ut.files)!=null&&et.length))}async function Se(){var et,Ct,bt;if(Y(""),!i.projectId){Y("先选项目");return}if(!r.trim()){Y("prompt 必填");return}const L={project_id:i.projectId,prompt:r.trim()};i.envSelect&&(L.environment_id=i.envSelect),g&&(L.executor=g);const se=J.trim()||F;se&&(L.model=se),vt&&(L.effort=vt);const y=Q.trim()||Nt;if(y&&(L.child_model=y),Dt&&(L.child_effort=Dt),jt&&(L.approval=jt),R){if(jt==="full"){Y("计划模式不能与完全访问同用");return}if(N||j){Y("计划模式不能与 goal 模式同用");return}L.plan=1}j?(L.goal_mode=1,L.no_time_limit=1):N&&(L.goal_mode=1);const D=f.trim();if(D){try{L.coordinator_parts=JSON.parse(D)}catch{Y("协调员子任务 JSON 解析失败");return}if(!Array.isArray(L.coordinator_parts)){Y("协调员子任务必须是数组");return}if(N||j||R){Y("协调员任务不能与 goal/计划模式同用:协调员任务本身不跑模型回合,请取消 goal/计划勾选,或清空协调员 JSON");return}}const ut=ag(((et=dt.current)==null?void 0:et.files)||null,((Ct=kt.current)==null?void 0:Ct.files)||null,((bt=H.current)==null?void 0:bt.files)||null);if(gt&&ut.length){Y("cloud-hypervisor 任务不支持附件:请先清空附件再提交(后端对带附件的 CHV 任务会 400)");return}try{const Tt=await i.dispatch(L,ut);Y(Tt),h(""),p(""),dt.current&&(dt.current.value=""),kt.current&&(kt.current.value=""),H.current&&(H.current.value=""),yt(!1)}catch(Tt){Y("提交失败:"+((Tt==null?void 0:Tt.message)||String(Tt)))}}return c.jsxs(Ft,{id:"nav-newtask",className:"composer",title:"新建任务",children:[c.jsxs(Ht,{children:[c.jsxs("select",{"aria-label":"环境",value:i.envSelect,onChange:L=>i.setEnvSelect(L.target.value),children:[c.jsx("option",{value:"",children:"环境:不指定(走项目仓库)"}),i.envs.map(L=>c.jsxs("option",{value:L.id,disabled:L.status!=="ready",children:[L.name,"(",L.status,")"]},L.id))]}),c.jsxs("select",{"aria-label":"执行器",value:g,onChange:L=>U(L.target.value),children:[c.jsx("option",{value:"docker",children:"docker(默认,多用户下隔离最强)"}),c.jsx("option",{value:"local",children:"local"}),c.jsx("option",{value:"cloud",children:"cloud"}),c.jsx("option",{value:"cloud-hypervisor",children:"cloud-hypervisor(实验,默认关闭)"})]}),c.jsxs("label",{children:[c.jsx("input",{type:"checkbox",checked:N,onChange:L=>C(L.target.checked)})," goal 模式"]}),c.jsxs("label",{children:[c.jsx("input",{type:"checkbox",checked:j,onChange:L=>z(L.target.checked)})," goal 模式无敌版"]}),c.jsxs("label",{children:[c.jsx("input",{type:"checkbox",checked:R,onChange:L=>q(L.target.checked)})," 计划模式(只读)"]})]}),gt?c.jsx("div",{className:"muted",children:"实验执行器,默认关闭:需后端已开启 CHV worker 且宿主有 KVM,否则任务无人认领、一直排队;带附件时建任务即被后端拒绝(错误文案原样显示在下方)。前端只传 executor 值,不绕过后端白名单;默认仍是 docker。"}):null,gt?c.jsxs("div",{className:"muted",children:[tt.state==="known"?c.jsxs(c.Fragment,{children:["CHV worker 在线 ",tt.online," / 共 ",tt.total,"(来自 /workers)",tt.online===0?c.jsx("b",{children:":当前无 CHV worker 在线,任务将一直排队,请先确认宿主 worker 已起;本提示不拦提交,worker 随后上线可认领,后端是最终权威。"}):null]}):tt.reason==="aggregate-only"?"CHV worker 在线数未知:完整 worker 清单仅全局管理员可见,非管理员只能看到聚合数,无法按执行器分项。":tt.reason==="load-failed"?"CHV worker 在线数未知:/workers 加载失败,请到 Workers 区刷新后重试。":"CHV worker 在线数未知:/workers 尚未加载,请到 Workers 区刷新。"," ",c.jsx("button",{type:"button",onClick:i.refreshWorkers,children:"刷新 CHV 可用性"})]}):null,c.jsx("textarea",{className:"prompt stacked",placeholder:"任务 prompt(必填)",value:r,onChange:L=>h(L.target.value)}),c.jsx("textarea",{className:"coord stacked",placeholder:"协调员子任务 JSON(可选),留空为普通任务",value:f,onChange:L=>p(L.target.value)}),c.jsxs("div",{className:"muted",children:["协调员格式:[","{",'"name":"后端","prompt":"做后端"',"}",",","{",'"name":"前端","prompt":"做前端"',"}","];提交后 worker 并行建子任务、等齐、合流汇总。不能与 goal/计划模式同用。协调员与 goal/计划互斥:勾了 goal 或计划模式时不要填协调员 JSON,提交前会校验并拦截。子任务费用记父任务创建人账;父任务创建时按分工数预留配额,余额不足时整个父任务被挡下。"]}),c.jsxs(Ht,{children:[c.jsxs("select",{"aria-label":"模型",value:F,onChange:L=>X(L.target.value),children:[c.jsx("option",{value:"",children:"模型:默认(跟随配置)"}),i.models.map(L=>c.jsx("option",{value:L,children:L},L))]}),i.modelCustomVisible?c.jsx("input",{type:"text",placeholder:"手动输入模型(中转列表取不到时用)",value:J,onChange:L=>V(L.target.value)}):null,c.jsxs("select",{"aria-label":"推理强度",value:vt,onChange:L=>St(L.target.value),children:[c.jsx("option",{value:"",children:"推理强度:默认(跟随配置)"}),Lh.map(L=>c.jsx("option",{value:L,children:L},L))]}),c.jsxs("select",{"aria-label":"权限",value:jt,onChange:L=>K(L.target.value),children:[c.jsx("option",{value:"",children:"权限:默认(跟随配置)"}),c.jsx("option",{value:"ask",children:"请求批准"}),c.jsx("option",{value:"auto",children:"帮我批准"}),c.jsx("option",{value:"full",children:"完全访问"})]})]}),c.jsxs(Ht,{children:[c.jsxs("select",{"aria-label":"子任务模型",value:Nt,onChange:L=>w(L.target.value),children:[c.jsx("option",{value:"",children:"子任务模型:跟随本任务模型"}),i.models.map(L=>c.jsx("option",{value:L,children:L},L))]}),c.jsx("input",{type:"text",placeholder:"子任务模型手动输入(中转列表里没有时填,如 gpt-6-luna;留空=跟随本任务模型)",value:Q,onChange:L=>nt(L.target.value)}),c.jsxs("select",{"aria-label":"子任务强度",value:Dt,onChange:L=>qt(L.target.value),children:[c.jsx("option",{value:"",children:"子任务强度:跟随本任务强度"}),Lh.map(L=>c.jsx("option",{value:L,children:L},L))]})]}),c.jsx("div",{className:"muted",children:"子任务模型/强度是派生默认:本任务用 marker task-create 或协调员分工派生的子任务,未显式写模型/强度时用它;块/分工里显式写了则覆盖它。也可以不在这里填、直接在 prompt 里写“子任务用 gpt-6-luna max”,建任务时服务端会解析成同一对字段(任务详情里可核对)。"}),N?c.jsx("div",{className:"muted",children:"goal 模式:输入框内容即目标,不需要专用 prompt 与验收命令;不限轮次,模型确认达成后在回复末尾输出“目标已达成”即结束,未达成会一直运行,可用取消停止。"}):null,j?c.jsx("div",{className:"muted",children:"goal 模式无敌版:goal 模式 + 任务不限时(去掉 worker/docker 总超时;单轮 600 秒护栏还在,不限的是任务总时长与总轮次)。服务端默认的自动续跑与断点镜像同时生效。"}):null,c.jsxs(Ht,{children:[c.jsx("span",{className:"muted",children:"附件(放进任务工作区,图片与普通文件不区分):"}),c.jsx("input",{ref:dt,type:"file",multiple:!0,title:"文件",disabled:gt,onChange:xt}),c.jsx("input",{ref:kt,type:"file",multiple:!0,title:"文件夹",disabled:gt,onChange:xt,webkitdirectory:""}),c.jsx("input",{ref:H,type:"file",accept:"image/*",multiple:!0,title:"图片",disabled:gt,onChange:xt})]}),gt?c.jsxs("div",{className:"muted",children:["cloud-hypervisor 任务不支持附件:附件输入已禁用(后端对带附件的 CHV 任务会 400,错误文案原样显示在下方;切换回其他执行器可恢复附件输入)。",ot?c.jsxs(c.Fragment,{children:["当前仍有已选附件,请先清空再提交。 ",c.jsx("button",{type:"button",onClick:()=>{dt.current&&(dt.current.value=""),kt.current&&(kt.current.value=""),H.current&&(H.current.value=""),yt(!1)},children:"清空附件"})]}):""]}):null,c.jsxs(Ht,{children:[c.jsx("button",{className:"seal",onClick:Se,children:"提交任务"}),c.jsx("span",{className:"muted",children:G||i.dispatchMsg})]})]})}function Ih(i){const r=(i.prompt||"").trim();return r?[...r][0]:i.id?[...i.id][0]:"?"}function Rc(i){const r=Math.max(0,Math.floor(i));if(r<60)return r+"秒";if(r<3600){const p=Math.floor(r/60),N=r%60;return N?p+"分"+N+"秒":p+"分"}if(r<86400){const p=Math.floor(r/3600),N=Math.floor(r%3600/60);return N?p+"小时"+N+"分":p+"小时"}const h=Math.floor(r/86400),f=Math.floor(r%86400/3600);return f?h+"天"+f+"小时":h+"天"}function iu(i){if(i==null)return null;const r=Number(i);return Number.isFinite(r)?r:null}function Ph(i){const r=Date.now()/1e3,h=iu(i.started_at),f=iu(i.finished_at),p=iu(i.created_at);return h!==null&&f!==null&&f>=h?[Rc(f-h),"总耗时:开始→结束(末次认领的执行时长)"]:h!==null&&i.status==="running"?["进行中 "+Rc(r-h),"总耗时:进行中,已用时长从开始算起,随刷新更新"]:p!==null&&f!==null&&f>=p?[Rc(f-p),"总耗时:创建→完成(含排队等待;无开始时间时的兜底口径)"]:p!==null&&(i.status==="queued"||i.status==="running")?[(i.status==="queued"?"排队中 ":"进行中 ")+Rc(r-p),"总耗时:从创建算起的已用时长(无开始时间时的兜底口径)"]:["—","总耗时:无可用时间戳"]}function yg(i){if(i==null||!Number.isFinite(Number(i)))return"—";const r=Number(i)/1e6;return"$"+(r>0&&r<.01?r.toFixed(4):r.toFixed(2))}function tv(i){const r=iu(i.total_tokens);if(r!==null)return[r.toLocaleString()+" token","token:服务端 total_tokens"];const h=iu(i.actual_tokens),f=iu(i.estimated_tokens);return h!==null||f!==null?[((h||0)+(f||0)).toLocaleString()+" token","token:actual + estimated 分项加总"]:["token —","token:本任务对象未带 token 字段;单任务真数在详情的成本里可查,不逐个调成本接口、不编造"]}function gg({onNew:i}){const r=Kt(),h=ci(r.workers,r.workersSummary,r.workersError);return c.jsxs("div",{children:[c.jsxs("div",{className:"search-box",children:[c.jsxs("svg",{viewBox:"0 0 24 24",width:"18",height:"18",fill:"none",stroke:"currentColor",strokeWidth:"1.8",strokeLinecap:"round","aria-hidden":"true",children:[c.jsx("circle",{cx:"11",cy:"11",r:"7"}),c.jsx("path",{d:"M21 21l-4.3-4.3"})]}),c.jsx("input",{type:"text",placeholder:"搜索","aria-label":"搜索任务",value:r.searchText,onChange:f=>r.setSearchText(f.target.value)})]}),c.jsx("div",{className:"bar",children:c.jsxs("select",{"aria-label":"项目",value:r.projectId,onChange:f=>r.selectProject(f.target.value),children:[r.projects.length===0?c.jsx("option",{value:"",children:"(先加载项目)"}):null,r.projects.map(f=>c.jsxs("option",{value:f.id,children:[f.name," (",Ae(f.id),")"]},f.id))]})}),c.jsxs("div",{className:"list-section-head",children:[c.jsx("span",{children:"主要聊天"}),c.jsx("button",{type:"button","aria-label":"派发新任务",title:"派发新任务",onClick:i,children:"+"})]}),c.jsxs("div",{className:"bar",children:[c.jsxs("select",{"aria-label":"状态筛选",value:r.statusFilter,onChange:f=>r.setStatusFilter(f.target.value),children:[c.jsx("option",{value:"",children:"状态:全部"}),["queued","running","completed","failed","interrupted","merged","merge-failed"].map(f=>c.jsx("option",{value:f,children:f},f))]}),c.jsxs("select",{"aria-label":"执行器筛选",value:r.executorFilter,onChange:f=>r.setExecutorFilter(f.target.value),children:[c.jsx("option",{value:"",children:"执行器:全部"}),c.jsx("option",{value:"local",children:"local"}),c.jsx("option",{value:"docker",children:"docker"}),c.jsx("option",{value:"cloud",children:"cloud"}),c.jsx("option",{value:"cloud-hypervisor",children:"cloud-hypervisor(实验)"})]})]}),c.jsxs("div",{className:"bar",children:[c.jsx("button",{onClick:r.refreshTasks,children:"刷新"}),c.jsx("span",{className:"muted",children:r.taskCountText})]}),c.jsxs("div",{className:"bar",children:[c.jsx("button",{disabled:r.taskPage<=0,onClick:()=>r.goToTaskPage(r.taskPage-1),children:"上一页"}),c.jsxs("span",{className:"muted",children:["第 ",r.taskPage+1," / ",r.taskTotalPages," 页(每页 20 条,最新在前)"]}),c.jsx("button",{disabled:r.taskPage+1>=r.taskTotalPages,onClick:()=>r.goToTaskPage(r.taskPage+1),children:"下一页"})]}),r.taskError?c.jsx("div",{className:"err",children:r.taskError}):r.projectId?r.filteredTasks.length===0?c.jsx("div",{className:"muted",children:r.taskTotal?"没有符合筛选条件的任务":"该项目暂无任务"}):c.jsx("ul",{className:"issue-list",children:r.filteredTasks.map(f=>{var R;const[p,N]=Ph(f),[C,j]=tv(f),z=Ih(f);return c.jsxs("li",{className:"issue-item"+(((R=r.currentTask)==null?void 0:R.id)===f.id?" selected":""),onClick:()=>r.showDetail(f.id),children:[c.jsx("span",{className:"issue-state st-"+f.status,"aria-hidden":"true"}),c.jsx("span",{className:"issue-first",title:"任务首字:"+z,"aria-hidden":"true",children:z}),c.jsxs("span",{style:{flex:1,minWidth:0},children:[c.jsxs("div",{className:"issue-title",children:[f.plan?"[计划] ":"",f.forked_from?"[分支] ":"",f.coordinator_parts?"[协调] ":"",f.parent_task_id?"[协调子任务] ":"",String(f.prompt||Ae(f.id)).slice(0,60)]}),c.jsxs("div",{className:"issue-meta",children:[Ae(f.id)," · ",c.jsx("span",{className:"st-"+f.status,children:f.status})," · ",f.executor||"",f.updated_at?" · "+new Date(f.updated_at*1e3).toLocaleString():""," · ",c.jsxs("span",{title:N,children:["耗时 ",p]})," · ",c.jsx("span",{title:j,children:C})]}),(f.status==="failed"||f.status==="interrupted"||f.status==="merge-failed")&&f.error_text?c.jsx("div",{className:"issue-meta",title:f.error_text,children:/503|Service Unavailable/.test(f.error_text)?"上游 503:模型服务暂时不可用":f.error_text.replace(/\s+/g," ").slice(0,60)}):null,kc(f.executor,f.status,h)?c.jsx("div",{className:"issue-meta",title:kc(f.executor,f.status,h),children:kc(f.executor,f.status,h)}):null]})]},f.id)})}):c.jsx("div",{className:"muted",children:"先选项目"})]})}function pg(i){const r=d.useRef(null),[h,f]=d.useState(!0),p=d.useRef(!0),N=()=>{const C=r.current;if(!C)return;const j=C.scrollHeight-C.scrollTop-C.clientHeight<80;p.current=j,f(j)};return d.useEffect(()=>{p.current&&r.current&&(r.current.scrollTop=r.current.scrollHeight)},[i]),{ref:r,following:h,onScroll:N}}function Sg({msg:i}){if(i.role==="status")return c.jsx("div",{className:"chat-status",children:i.text});const r=i.label||(i.role==="user"?"用户":i.role==="assistant"?"助手":"工具");return c.jsxs("div",{className:"gh-comment "+i.role,children:[c.jsx("div",{className:"gh-comment-header",children:c.jsx("span",{className:"gh-comment-author",children:r})}),c.jsx("div",{className:"gh-comment-body",children:i.text})]})}function xg({messages:i,empty:r}){const{ref:h,following:f,onScroll:p}=pg(i.length);return c.jsxs("div",{className:"chat-list",ref:h,onScroll:p,children:[i.length===0?c.jsx("div",{className:"muted",children:r||"(暂无消息)"}):i.map(N=>c.jsx(Sg,{msg:N},N.id)),!f&&i.length>0?c.jsx("button",{className:"chat-follow",onClick:()=>{h.current&&(h.current.scrollTop=h.current.scrollHeight)},children:"回到最新 ↓"}):null]})}function bg({onSend:i,disabled:r,placeholder:h}){const[f,p]=d.useState(""),N=()=>{const C=f.trim();!C||r||(i(C),p(""))};return c.jsxs("div",{className:"gh-comment-box",children:[c.jsx("textarea",{"aria-label":"会话输入",rows:3,placeholder:h||"消息",value:f,disabled:r,onChange:C=>p(C.target.value),onKeyDown:C=>{C.key==="Enter"&&!C.shiftKey&&(C.preventDefault(),N())}}),c.jsx("div",{className:"gh-comment-footer",children:c.jsx("button",{type:"button",className:"primary","aria-label":"发送",disabled:r||!f.trim(),onClick:N,children:"发送"})})]})}function Zh(i,r){const h=i.id||"trace-"+r;if(i.kind==="message"){const f=(i.phase?"["+i.phase+"] ":"")+(i.text||"");return{id:h,role:"assistant",kind:i.kind,text:i.truncated?f+` -(链路摘要已截断至 300 字,完整回复见右侧任务结果)`:f,label:"助手"}}if(i.kind==="exec"||i.kind==="file"||i.kind==="tool"||i.kind==="call"){const f=i.kind==="exec"?"命令":i.kind==="file"?"文件":i.kind==="tool"?"工具":"调用";return{id:h,role:"tool",kind:i.kind,text:go(i),label:f}}return i.kind==="plan"?{id:h,role:"status",kind:i.kind,text:i.text||""}:i.kind==="reasoning"?{id:h,role:"status",kind:i.kind,text:"思考:"+(i.summary||"")}:i.kind==="agent-start"?{id:h,role:"status",kind:i.kind,text:"子代理开始:"+(i.name||"")+(i.prompt?"("+i.prompt+")":"")}:i.kind==="agent-done"?{id:h,role:"status",kind:i.kind,text:"子代理结束:"+(i.name||"")+" "+(i.status||"")}:{id:h,role:"status",kind:i.kind,text:go(i)}}function jg(){const i=Kt(),r=i.currentTask,h=d.useMemo(()=>{if(!r)return[];let z;if(i.chatHistory){z=i.chatHistory.messages.map(X=>({id:X.id,role:X.role,text:X.text,label:X.label,kind:X.kind}));const U=new Set(z.map(X=>X.id)),F=i.traceEntries.filter(X=>X.id&&!U.has(X.id)&&!String(X.id).startsWith("fu-")).map((X,J)=>Zh(X,J));z=[...z,...F]}else{const U=r.prompt?[{id:"task-prompt",role:"user",text:r.prompt}]:[],F=i.traceEntries.filter(J=>!String(J.id||"").startsWith("fu-")).map((J,V)=>Zh(J,V)),X=i.followups.map((J,V)=>({id:"followup-"+V,role:"user",text:String(J.body||""),label:"后续消息"}));z=[...U,...F,...X]}const R=i.traceKind,q=R?z.filter(U=>U.role==="user"||!U.kind||U.kind===R):z,g=i.traceDone?[{id:"trace-done",role:"status",text:"链路已结束("+i.traceDone+")"}]:[];return[...q,...g]},[r,i.chatHistory,i.traceEntries,i.followups,i.traceKind,i.traceDone]);if(!r)return c.jsx("div",{className:"muted",children:"点任务行查看详情后,这里显示继续会话"});const f=uu.includes(r.status),p=["queued","running"].includes(r.status),N=f||p,C=f?"终态任务:发送走继续接口(同一会话再跑一轮)":p?"运行中/排队中:发送走后续消息直接发送(跑完当前轮后优先执行)":"merged/merge-failed 任务不可继续,也不可发后续消息",j=i.chatHistory?i.chatHistory.legacy_continue_count??null:null;return c.jsxs("div",{className:"chat-column",children:[c.jsx("div",{className:"muted",children:"实时对话"}),c.jsxs(Ht,{children:[c.jsxs("select",{"aria-label":"链路类型",value:i.traceKind,onChange:z=>i.setTraceKind(z.target.value),children:[c.jsx("option",{value:"",children:"链路类型:全部"}),["exec","file","call","tool","message","reasoning","plan","agent-start","agent-done"].map(z=>c.jsx("option",{value:z,children:z==="exec"?"命令":z==="file"?"文件":z==="call"?"调用":z==="tool"?"工具":z==="message"?"消息":z==="reasoning"?"思考":z==="plan"?"计划":"子代理"},z))]}),c.jsx("button",{onClick:i.downloadTrace,children:"下载完整链路"}),c.jsx("span",{className:"muted",children:i.traceMsg})]}),i.traceNote?c.jsx("div",{children:c.jsx("span",{className:"err",children:i.traceNote})}):null,j!==null&&j>0?c.jsxs("div",{className:"muted",children:["此前有 ",j," 条继续消息发生在持久化之前,未入库,上面历史不完整;部署后新发的继续消息会正常显示。"]}):null,i.chatHistory&&i.chatHistory.trace_note?c.jsx("div",{className:"muted",children:i.chatHistory.trace_note}):null,c.jsx(xg,{messages:h,empty:"(暂无会话消息)"}),c.jsx(bg,{disabled:!N,placeholder:"消息",onSend:z=>void i.sendChatMessage(z)}),c.jsxs("div",{className:"muted",children:[C,"。",i.chatMsg]}),c.jsx("div",{className:"muted",children:"对话时间线:首条是本任务 prompt(用户);助手回复、命令/文件/工具/调用、计划/思考/子代理开始结束都在同一列里,不再另起一份链路名单。流式进行中时新条目实时追加;你在底部时自动跟随,往上翻时暂停跟随,点“回到最新”恢复。"})]})}function Tg(){return Kt().currentTask?c.jsx("div",{className:"conversation",children:c.jsx(jg,{})}):c.jsx("div",{className:"conversation",children:c.jsx("div",{className:"muted",children:"在任务列表里点一条任务,对话和详情就切到该任务;也可以在上面派发一个新任务。"})})}function _g(){const i=Kt(),r=i.currentTask,[h,f]=d.useState("");if(!r)return null;const p=uu.includes(r.status),N=["failed","interrupted","merge-failed"].includes(r.status);return c.jsxs(c.Fragment,{children:[c.jsxs(Ht,{children:[c.jsx("button",{disabled:!N,title:N?"":"仅 failed/interrupted/merge-failed 可重试",onClick:()=>i.doAction("retry"),children:"重试"}),c.jsx("button",{className:"danger",disabled:p,title:p?"终态任务不可取消":"",onClick:()=>i.doAction("cancel"),children:"取消"}),c.jsx("button",{disabled:r.status!=="completed",title:r.status!=="completed"?"仅 completed 可合并":"",onClick:()=>i.doAction("merge"),children:"合并(completed → merged)"}),c.jsx("button",{disabled:r.status!=="merged",title:r.status!=="merged"?"仅 merged 可提 PR":"",onClick:()=>i.doAction("pr"),children:"提 PR(须 merged)"}),p?c.jsx("button",{onClick:()=>i.setForkVisible(!0),children:"分支"}):null,c.jsx("button",{onClick:i.taskCost,children:"查成本"}),c.jsxs("span",{className:"muted",children:[i.actionMsg," ",i.actionReasons]})]}),i.forkVisible?c.jsxs("div",{children:[c.jsx("input",{type:"text",placeholder:"分支首轮消息(必填,带历史的新会话)",style:{width:"100%",boxSizing:"border-box",margin:"6px 0"},value:h,onChange:C=>f(C.target.value)}),c.jsxs(Ht,{children:[c.jsx("button",{className:"seal",onClick:async()=>{await i.forkTask(h),f("")},children:"创建分支任务"}),c.jsx("span",{className:"muted",children:i.forkMsg})]})]}):null]})}function Eg(){const r=Kt().currentTask,[h,f]=d.useState(""),[p,N]=d.useState(null),[C,j]=d.useState("未开始"),[z,R]=d.useState(""),[q,g]=d.useState(!1),U=d.useRef(null),F=d.useRef(null),X=d.useCallback(async()=>{if(r)try{const w=await at(W.taskBrowserState(r.id));N(w),R("")}catch(w){const Q=w instanceof Error?w.message:String(w);Q.includes("503")?N(null):R(Q)}},[r]);d.useEffect(()=>{var w;if(f(""),N(null),j("未开始"),R(""),g(!1),(w=U.current)==null||w.abort(),!!r)return X(),()=>{var Q;(Q=U.current)==null||Q.abort()}},[r,X]),d.useEffect(()=>{if(!r||q)return;const w=setInterval(()=>void X(),5e3);return()=>clearInterval(w)},[r,q,X]);const J=d.useCallback(async()=>{var Q;if(!r)return;(Q=U.current)==null||Q.abort();const w=new AbortController;U.current=w,g(!0),j("连接中…"),R("");try{const nt=await fetch(W.taskBrowserStream(r.id),{headers:{"X-API-Key":sl()},signal:w.signal});if(!nt.ok||!nt.body){const kt=await nt.json().catch(()=>null);throw new Error(kt&&kt.error||"HTTP "+nt.status)}j("直播中");const Dt=nt.body.getReader(),qt=new TextDecoder;let dt="";for(;;){const{done:kt,value:H}=await Dt.read();if(kt)break;dt+=qt.decode(H,{stream:!0});let ot=dt.indexOf(` +`),N(P||"(暂无 result/error 输出)")}catch{}ee(B.id),Pe(B.id),qe(B.id),_l(B.id),ma(B),rl(B.id),ol(B.id),$t(B.id),kl(B.id),Dl(B.id)}},[ee,Pe,qe,_l,ma,rl,ol,$t,kl,Dl]),ha=d.useCallback(async B=>{const k=oe.current;if(!k)return;const P={cancel:"确定取消该任务吗?",merge:"确定把 completed 任务标为 merged 吗?",pr:"确定为该任务提 PR 吗?(会在仓库真实建 PR)"};if(!(P[B]&&!window.confirm(P[B])))try{const ct=await at(W.taskAction(k.id,B),"POST",{});j(B+" 已提交:"+JSON.stringify(ct).slice(0,300)),r(),await be(k.id)}catch(ct){j(B+" 失败:"+ct.message)}},[be,r]),su=d.useCallback(async()=>{const B=oe.current;if(B)try{const k=await at(W.taskCost(B.id));g(JSON.stringify(k,null,2).slice(0,4e3))}catch(k){g("查成本失败:"+k.message)}},[]),ru=d.useCallback(async B=>{const k=oe.current;if(k){if(!B.trim()){vt("分支消息必填");return}if(!uu.includes(k.status)){vt("仅终态任务可分支");return}try{const P=await at(W.taskFork(k.id),"POST",{message:B.trim()});vt("分支任务已建:"+P.id.slice(0,8)),r()}catch(P){vt("分支失败:"+P.message)}}},[r]),Wl=d.useCallback(async B=>{const k=oe.current;if(k)try{await at(W.taskReviews(k.id),"POST",B),dt("评审已登记(requested)"),await qe(k.id)}catch(P){dt("登记评审需求失败:"+P.message)}},[qe]),Me=d.useCallback(B=>{const k=oe.current;if(!k)return;const P=window.prompt("输入该条评审的 findings(将写回记录并标为 completed):");P!==null&&at(W.taskReviewFindings(k.id,B),"POST",{findings:P}).then(()=>void qe(k.id)).catch(ct=>dt("写 findings 失败:"+ct.message))},[qe]),fl=d.useCallback((B,k)=>{const P=oe.current;if(!P)return;const ct=k==="approved"?"approve":"deny";at(W.taskApprovalDecision(P.id,B,ct),"POST",{}).then(()=>{bt("已记录"+(k==="approved"?"允许":"拒绝")+"(当前版本只留回执,不拦截实时行为)"),_l(P.id)}).catch(A=>bt("决定失败:"+A.message))},[_l]),Oe=d.useCallback(async(B,k,P)=>{const ct=oe.current;if(ct){if(!k.trim()){re("目标任务 id 不能为空");return}if(!P.trim()){re("消息正文不能为空");return}try{const A=await at(W.taskMessagesTo(k.trim()),"POST",{from_task_id:ct.id,body:P.trim(),mode:B});re("已发送("+A.delivery+")"),await ol(ct.id)}catch(A){re("发送失败:"+A.message)}}},[ol]),ou=d.useCallback((B,k)=>{const P=oe.current;if(!P)return;if(!["completed","failed","interrupted"].includes(P.status)){ye("仅 completed/failed/interrupted 任务可操作线程(merged/merge-failed 不可改,服务端同样 409)");return}const ct={};if(B==="rename"){if(!k.trim()){ye("新线程名不能为空");return}ct.name=k.trim()}at(W.taskThreadAction(P.id,B),"POST",ct).then(A=>{ye(B+" 已提交:"+JSON.stringify(A).slice(0,300)),$t(P.id),kl(P.id)}).catch(A=>ye(B+" 失败:"+A.message))},[$t,kl]),fu=d.useCallback(async B=>{const k=oe.current;if(!k)return;const P=B.trim();if(!P){Le("消息不能为空");return}try{uu.includes(k.status)&&k.status!=="merged"?(await at(W.taskContinue(k.id),"POST",{message:P}),Le("已重新排队,同一会话继续"),r(),await be(k.id)):["queued","running"].includes(k.status)?(await at(W.taskFollowups(k.id),"POST",{body:P,mode:"direct"}),Le("已直接发送"),await ee(k.id),await Pe(k.id)):Le("merged/merge-failed 任务不可继续,也不可发后续消息")}catch(ct){Le("发送失败:"+ct.message)}},[be,r,ee,Pe]),du=d.useCallback(async()=>{const B=oe.current;if(!B){se("先选一个任务");return}try{const k=await at(W.taskTrace(B.id)),P=new Blob([JSON.stringify({task_id:B.id,status:k.status,note:k.note||"",count:(k.entries||[]).length,entries:k.entries||[]},null,2)],{type:"application/json"}),ct=document.createElement("a");ct.href=URL.createObjectURL(P),ct.download="trace-"+B.id.slice(0,8)+".json",document.body.appendChild(ct),ct.click(),ct.remove(),setTimeout(()=>URL.revokeObjectURL(ct.href),1e3),se("已下载完整链路 "+(k.entries||[]).length+" 条")}catch(k){se("下载失败:"+k.message)}},[]);return d.useEffect(()=>()=>fe(),[fe]),{currentTask:h,detailBody:p,actionMsg:C,actionReasons:z,costBox:q,costSummary:U,forkVisible:X,setForkVisible:J,forkMsg:Q,coordChildren:St,agents:K,agentsNote:w,reviews:nt,reviewMsg:qt,reviewError:kt,traceEntries:ot,traceNote:G,traceDone:tt,traceKind:xt,setTraceKind:Se,traceMsg:L,followups:y,approvals:ut,approvalMsg:Ct,peerMessages:Tt,peerMsg:mt,threadText:Ga,threadGateNote:Xe,threadMsg:Vt,threadList:Jl,chatMsg:da,chatHistory:zl,showDetail:be,refreshDetailLists:Qa,doAction:ha,taskCost:su,forkTask:ru,postReview:Wl,submitFindings:Me,decideApproval:fl,postPeer:Oe,threadAction:ou,sendChatMessage:fu,downloadTrace:du,refreshReviews:()=>void qe(),refreshApprovals:()=>void _l()}}function cg(i){const[r,h]=d.useState([]),[f,p]=d.useState(""),[N,C]=d.useState(null),[j,z]=d.useState(""),R=d.useCallback(async X=>{const J=X!==void 0?X:i();if(!J){h([]);return}try{const Q=await at(W.projectAutomations(J));h(Q.automations||[])}catch(Q){p("自动化加载失败:"+Q.message)}},[i]),q=d.useCallback(async(X,J,Q,vt)=>{const St=i();if(!St){p("先选项目");return}if(!X.trim()){p("自动化名称必填");return}if(!J.trim()){p("定时任务内容必填");return}const jt={name:X.trim(),prompt:J.trim()};Q.trim()&&(jt.interval_seconds=parseInt(Q.trim(),10)),vt.trim()&&(jt.repo_url=vt.trim());try{await at(W.projectAutomations(St),"POST",jt),p("自动化已创建(登记定义,到期后由执行器认领)"),await R(St)}catch(K){p("创建失败:"+K.message)}},[i,R]),g=d.useCallback(async(X,J,Q,vt,St)=>{const jt=i();if(!jt)return p("先选项目"),!1;const K=J.trim(),Nt=Q.trim();if(!K)return p("自动化名称必填"),!1;if(!Nt)return p("定时任务内容必填"),!1;const w={};if(K!==(X.name||"")&&(w.name=K),Nt!==(X.prompt||"")&&(w.prompt=Nt),vt.trim()){const nt=parseInt(vt.trim(),10);if(Number.isNaN(nt))return p("间隔秒数必须是数字"),!1;if(nt<60)return p("间隔秒数最小 60"),!1;nt!==X.interval_seconds&&(w.interval_seconds=nt)}const V=St.trim();if(V!==(X.repo_url||"")&&(w.repo_url=V===""?null:V),Object.keys(w).length===0)return p("没有改动"),!1;try{return await at(W.projectAutomation(jt,X.id),"PATCH",w),p("自动化已更新"),await R(jt),!0}catch(nt){return p("更新失败:"+nt.message),!1}},[i,R]),U=d.useCallback(X=>{const J=i();J&&at(W.projectAutomationCancel(J,X),"POST",{}).then(()=>{p("自动化已取消"),R(J)}).catch(Q=>p("取消失败:"+Q.message))},[i,R]),F=d.useCallback(async X=>{const J=X!==void 0?X:i();if(!J){C(null);return}try{const Q=await at(W.projectTriage(J));C(Q),z("")}catch(Q){z("分诊加载失败:"+Q.message)}},[i]);return{automations:r,autoMsg:f,triage:N,triageError:j,refreshAutomations:R,createAutomation:q,updateAutomation:g,cancelAutomation:U,refreshTriage:F}}function sg(i,r){const h=d.useRef(i);h.current=i,d.useEffect(()=>{const f=setInterval(()=>{const p=document.activeElement;h.current(p?p.tagName==="SELECT":!1)},r);return()=>clearInterval(f)},[r])}function rg(i,r){const h=d.useRef(i);h.current=i,d.useEffect(()=>{const f=setInterval(()=>h.current(),r);return()=>clearInterval(f)},[r])}function og(i){const r=h=>String(h).padStart(2,"0");return r(i.getHours())+":"+r(i.getMinutes())+":"+r(i.getSeconds())}function fg(){var J;const i=ng(),r=ug(),h=d.useRef("");h.current=i.projectId;const f=d.useCallback(()=>h.current,[]),p=cg(f),N=d.useCallback(Q=>{const vt=i.projects.find(St=>St.id===Q);return vt&&vt.my_role||null},[i.projects]),C=d.useCallback(()=>{r.refreshTasks(void 0,h.current)},[r]),j=ig(N,C),[z,R]=d.useState("--:--:--"),q=d.useCallback(Q=>{i.setProjectId(Q),h.current=Q,r.refreshTasksFirstPage(Q),i.refreshEnvs(Q),i.refreshMembers(Q),i.refreshSkills(Q),p.refreshAutomations(Q),p.refreshTriage(Q)},[i,r,p]),g=(J=i.projects[0])==null?void 0:J.id;d.useEffect(()=>{!i.projectId&&g&&q(g)},[i.projectId,g,q]);const U=d.useCallback(Q=>{i.saveKeyInput(Q),Q.trim()&&(i.refreshHealth(),i.refreshProjects(),i.refreshWorkers(),h.current&&(r.refreshTasks(h.current),i.refreshEnvs(h.current),i.refreshMembers(h.current)),i.loadRelayModels(),i.loadQuota(),i.loadGithubRepos(),i.probeAdmin())},[i,r]),F=d.useCallback(Q=>{if(R(og(new Date)),!sl()){i.refreshHealth();return}r.refreshTasks(void 0,h.current),i.refreshHealth(),i.loadQuota(),i.refreshWorkers(),i.refreshMembers(),i.refreshSkills(),p.refreshAutomations(),p.refreshTriage(),j.refreshDetailLists(),Q||(i.refreshProjects(),i.refreshEnvs())},[i,r,p,j]),X=d.useCallback(()=>{sl()&&(i.loadRelayModels(),i.loadGithubRepos())},[i]);return d.useEffect(()=>{i.refreshHealth(),i.refreshProjects(),i.refreshWorkers(),i.loadQuota(),i.loadGithubRepos(),sl()&&i.loadRelayModels(),i.probeAdmin()},[]),{keySaved:i.keySaved,keyState:i.keyState,saveKeyInput:U,clearKeyInput:i.clearKeyInput,quotaText:i.quotaText,healthText:i.healthText,refreshHealth:i.refreshHealth,adminGate:i.adminGate,adminBody:i.adminBody,adminResult:i.adminResult,adminUserId:i.adminUserId,setAdminUserId:i.setAdminUserId,adminQuota:i.adminQuota,adminSweep:i.adminSweep,adminBackup:i.adminBackup,projects:i.projects,projectId:i.projectId,projectMsg:i.projectMsg,selectProject:q,refreshProjects:i.refreshProjects,createProject:i.createProject,githubRepos:i.githubRepos,workers:i.workers,workersSummary:i.workersSummary,workersError:i.workersError,refreshWorkers:i.refreshWorkers,members:i.members,memberMsg:i.memberMsg,memberError:i.memberError,refreshMembers:()=>void i.refreshMembers(),inviteMember:i.inviteMember,changeMemberRole:i.changeMemberRole,skills:i.skills,apps:i.apps,mcpServers:i.mcpServers,skillMsg:i.skillMsg,skillError:i.skillError,mcpMsg:i.mcpMsg,mcpOauthMsg:i.mcpOauthMsg,mcpOauthBox:i.mcpOauthBox,refreshSkills:()=>void i.refreshSkills(),reloadMcp:i.reloadMcp,mcpOauth:i.mcpOauth,envs:i.envs,envCount:i.envCount,envMsg:i.envMsg,envRepoLine:i.envRepoLine,envRepoBar:i.envRepoBar,envRepoMsg:i.envRepoMsg,envSelect:i.envSelect,setEnvSelect:i.setEnvSelect,refreshEnvs:()=>void i.refreshEnvs(),createEnv:i.createEnv,archiveEnv:i.archiveEnv,saveEnvRepo:i.saveEnvRepo,models:i.models,modelCustomVisible:i.modelCustomVisible,tasks:r.tasks,taskCountText:r.taskCountText,taskError:r.taskError,refreshTasks:()=>void r.refreshTasks(void 0,h.current),statusFilter:r.statusFilter,setStatusFilter:r.setStatusFilter,executorFilter:r.executorFilter,setExecutorFilter:r.setExecutorFilter,searchText:r.searchText,setSearchText:r.setSearchText,filteredTasks:r.filteredTasks,taskPage:r.page,taskTotalPages:r.totalPages,taskTotal:r.taskTotal,taskStatusCounts:r.taskStatusCounts,goToTaskPage:r.goToPage,currentTask:j.currentTask,detailBody:j.detailBody,actionMsg:j.actionMsg,actionReasons:j.actionReasons,showDetail:Q=>void j.showDetail(Q),doAction:j.doAction,taskCost:j.taskCost,costBox:j.costBox,costSummary:j.costSummary,forkVisible:j.forkVisible,setForkVisible:j.setForkVisible,forkTask:j.forkTask,forkMsg:j.forkMsg,coordChildren:j.coordChildren,coordText:"",agents:j.agents,agentsNote:j.agentsNote,reviews:j.reviews,reviewMsg:j.reviewMsg,reviewError:j.reviewError,refreshReviews:j.refreshReviews,postReview:j.postReview,submitFindings:j.submitFindings,traceEntries:j.traceEntries,traceNote:j.traceNote,traceDone:j.traceDone,traceKind:j.traceKind,setTraceKind:j.setTraceKind,downloadTrace:j.downloadTrace,traceMsg:j.traceMsg,sendChatMessage:j.sendChatMessage,chatMsg:j.chatMsg,chatHistory:j.chatHistory,followups:j.followups,approvals:j.approvals,approvalMsg:j.approvalMsg,refreshApprovals:j.refreshApprovals,decideApproval:j.decideApproval,peerMessages:j.peerMessages,peerMsg:j.peerMsg,postPeer:j.postPeer,automations:p.automations,autoMsg:p.autoMsg,refreshAutomations:()=>void p.refreshAutomations(),createAutomation:p.createAutomation,updateAutomation:p.updateAutomation,cancelAutomation:p.cancelAutomation,triage:p.triage,triageError:p.triageError,refreshTriage:()=>void p.refreshTriage(),threadText:j.threadText,threadGateNote:j.threadGateNote,threadMsg:j.threadMsg,threadList:j.threadList,threadAction:j.threadAction,dispatch:async(Q,vt)=>{const St=await r.dispatch(Q,vt);return r.refreshTasksFirstPage(h.current),St},dispatchMsg:"",refreshAll:F,refreshSlow:X,refreshStamp:z}}function nu({label:i,value:r,sub:h}){return c.jsxs("div",{className:"dash-card data-card",children:[c.jsx("div",{className:"dash-card-label",children:i}),c.jsx("div",{className:"dash-card-value",children:r}),c.jsx("div",{className:"muted",children:h})]})}function dg(){const i=Kt(),r=i.healthText==="未加载"?"未加载":i.healthText.includes("worker_ok=true")?"正常":i.healthText.includes("worker_ok=false")?"异常":i.healthText.includes("加载失败")?"加载失败":"未知",h=i.quotaText.match(/剩余 \$([0-9.]+)/),f=i.quotaText.includes("加载失败")?"加载失败":i.quotaText.includes("未填 key")||i.quotaText==="未加载"?"未加载":h?"$"+h[1]:"配额",p=["running","completed","failed","queued","merged","interrupted"].map(g=>g+" "+(i.taskStatusCounts[g]||0)).join(" | "),N=i.projects.find(g=>g.id===i.projectId),C=i.taskError?{value:"加载失败",sub:i.taskError}:i.projectId?{value:String(i.taskTotal),sub:i.taskCountText+" | "+p+(N?" | "+N.name:"")}:{value:"先选项目",sub:i.projectMsg||"项目加载后自动选第一个"};let j;if(i.workersError)j={value:"加载失败",sub:i.workersError};else if(i.workers===null)j={value:i.workersSummary||"未加载",sub:"完整 worker 清单仅全局管理员可见"};else{const g=i.workers.filter(U=>{if(U.alive!==void 0&&U.alive!==null)return!!U.alive;const F=xo(U).age;return F!==null&&F<=120}).length;j={value:g+" 在线 / "+i.workers.length,sub:i.workers.length-g+" 离线"}}const z=i.projectMsg.includes("加载失败")?{value:"加载失败",sub:i.projectMsg}:{value:i.projects.length+" 个",sub:N?"当前项目:"+N.name:"未选项目(加载后自动选第一个)"},R=[i.envMsg,i.memberError,i.skillError].filter(g=>g&&g.includes("失败")),q=i.projectId?R.length?{value:"部分加载失败",sub:R.join(" | ")}:{value:i.envs.length+" 个环境",sub:i.members.length+" 成员 | "+i.skills.length+" 技能 | "+i.apps.length+" 应用 | "+i.mcpServers.length+" MCP"}:{value:"先选项目",sub:"选项目后显示环境 / 成员 / 技能计数"};return c.jsxs("section",{className:"dash-cards","aria-label":"数据卡片",children:[c.jsx(nu,{label:"健康",value:r,sub:i.healthText}),c.jsx(nu,{label:"配额",value:f,sub:i.quotaText}),c.jsx(nu,{label:"任务",value:C.value,sub:C.sub}),c.jsx(nu,{label:"Workers",value:j.value,sub:j.sub}),c.jsx(nu,{label:"项目",value:z.value,sub:z.sub}),c.jsx(nu,{label:"项目数据",value:q.value,sub:q.sub})]})}function Ft({id:i,className:r,title:h,children:f}){return c.jsxs("section",{id:i,className:"card"+(r?" "+r:""),children:[h?c.jsx("h2",{children:h}):null,f]})}function Ht({children:i}){return c.jsx("div",{className:"bar",children:i})}function bl({items:i,empty:r}){return i.length?c.jsx("ul",{className:"data-list",children:i.map((h,f)=>c.jsx("li",{children:h},f))}):c.jsx("div",{className:"muted",children:r||"(暂无)"})}function cu({children:i}){return c.jsx("ul",{className:"data-list",children:i})}function mg(i){if(i.alive!==void 0&&i.alive!==null)return!!i.alive;const r=xo(i);return r.age!==null&&r.age<=120}function hg(i){const r=i.filter(h=>h.executor==="cloud-hypervisor");return{online:r.filter(mg).length,total:r.length}}function ci(i,r,h){return i?{state:"known",...hg(i)}:h?{state:"unknown",reason:"load-failed"}:r.includes("无 worker 注册")?{state:"known",online:0,total:0}:r.includes("完整 worker 清单仅全局管理员可见")?{state:"unknown",reason:"aggregate-only"}:{state:"unknown",reason:"not-loaded"}}function kc(i,r,h){return i!=="cloud-hypervisor"||r!=="queued"?"":h.state==="known"&&h.online===0?"可能原因:当前无 cloud-hypervisor worker 在线,任务在排队(基于 /workers 推断;VM 状态/IP/快照等后端未返回字段不展示)。请先确认 KVM 宿主上的 CHV worker 已起,worker 上线后可认领。":""}function vg(){const i=Kt(),[r,h]=d.useState(""),[f,p]=d.useState(""),[N,C]=d.useState(!1),[j,z]=d.useState(!1),[R,q]=d.useState(!1),[g,U]=d.useState("docker"),[F,X]=d.useState(""),[J,Q]=d.useState(""),[vt,St]=d.useState(""),[jt,K]=d.useState(""),[Nt,w]=d.useState(""),[V,nt]=d.useState(""),[Dt,qt]=d.useState(""),dt=d.useRef(null),kt=d.useRef(null),H=d.useRef(null),[ot,yt]=d.useState(!1),[G,Y]=d.useState(""),tt=ci(i.workers,i.workersSummary,i.workersError),gt=g==="cloud-hypervisor";function xt(){var L,se,y,D,ut,et;yt(!!((se=(L=dt.current)==null?void 0:L.files)!=null&&se.length||(D=(y=kt.current)==null?void 0:y.files)!=null&&D.length||(et=(ut=H.current)==null?void 0:ut.files)!=null&&et.length))}async function Se(){var et,Ct,bt;if(Y(""),!i.projectId){Y("先选项目");return}if(!r.trim()){Y("prompt 必填");return}const L={project_id:i.projectId,prompt:r.trim()};i.envSelect&&(L.environment_id=i.envSelect),g&&(L.executor=g);const se=J.trim()||F;se&&(L.model=se),vt&&(L.effort=vt);const y=V.trim()||Nt;if(y&&(L.child_model=y),Dt&&(L.child_effort=Dt),jt&&(L.approval=jt),R){if(jt==="full"){Y("计划模式不能与完全访问同用");return}if(N||j){Y("计划模式不能与 goal 模式同用");return}L.plan=1}j?(L.goal_mode=1,L.no_time_limit=1):N&&(L.goal_mode=1);const D=f.trim();if(D){try{L.coordinator_parts=JSON.parse(D)}catch{Y("协调员子任务 JSON 解析失败");return}if(!Array.isArray(L.coordinator_parts)){Y("协调员子任务必须是数组");return}if(N||j||R){Y("协调员任务不能与 goal/计划模式同用:协调员任务本身不跑模型回合,请取消 goal/计划勾选,或清空协调员 JSON");return}}const ut=ag(((et=dt.current)==null?void 0:et.files)||null,((Ct=kt.current)==null?void 0:Ct.files)||null,((bt=H.current)==null?void 0:bt.files)||null);if(gt&&ut.length){Y("cloud-hypervisor 任务不支持附件:请先清空附件再提交(后端对带附件的 CHV 任务会 400)");return}try{const Tt=await i.dispatch(L,ut);Y(Tt),h(""),p(""),dt.current&&(dt.current.value=""),kt.current&&(kt.current.value=""),H.current&&(H.current.value=""),yt(!1)}catch(Tt){Y("提交失败:"+((Tt==null?void 0:Tt.message)||String(Tt)))}}return c.jsxs(Ft,{id:"nav-newtask",className:"composer",title:"新建任务",children:[c.jsxs(Ht,{children:[c.jsxs("select",{"aria-label":"环境",value:i.envSelect,onChange:L=>i.setEnvSelect(L.target.value),children:[c.jsx("option",{value:"",children:"环境:不指定(走项目仓库)"}),i.envs.map(L=>c.jsxs("option",{value:L.id,disabled:L.status!=="ready",children:[L.name,"(",L.status,")"]},L.id))]}),c.jsxs("select",{"aria-label":"执行器",value:g,onChange:L=>U(L.target.value),children:[c.jsx("option",{value:"docker",children:"docker(默认,多用户下隔离最强)"}),c.jsx("option",{value:"local",children:"local"}),c.jsx("option",{value:"cloud",children:"cloud"}),c.jsx("option",{value:"cloud-hypervisor",children:"cloud-hypervisor(实验,默认关闭)"})]}),c.jsxs("label",{children:[c.jsx("input",{type:"checkbox",checked:N,onChange:L=>C(L.target.checked)})," goal 模式"]}),c.jsxs("label",{children:[c.jsx("input",{type:"checkbox",checked:j,onChange:L=>z(L.target.checked)})," goal 模式无敌版"]}),c.jsxs("label",{children:[c.jsx("input",{type:"checkbox",checked:R,onChange:L=>q(L.target.checked)})," 计划模式(只读)"]})]}),gt?c.jsx("div",{className:"muted",children:"实验执行器,默认关闭:需后端已开启 CHV worker 且宿主有 KVM,否则任务无人认领、一直排队;带附件时建任务即被后端拒绝(错误文案原样显示在下方)。前端只传 executor 值,不绕过后端白名单;默认仍是 docker。"}):null,gt?c.jsxs("div",{className:"muted",children:[tt.state==="known"?c.jsxs(c.Fragment,{children:["CHV worker 在线 ",tt.online," / 共 ",tt.total,"(来自 /workers)",tt.online===0?c.jsx("b",{children:":当前无 CHV worker 在线,任务将一直排队,请先确认宿主 worker 已起;本提示不拦提交,worker 随后上线可认领,后端是最终权威。"}):null]}):tt.reason==="aggregate-only"?"CHV worker 在线数未知:完整 worker 清单仅全局管理员可见,非管理员只能看到聚合数,无法按执行器分项。":tt.reason==="load-failed"?"CHV worker 在线数未知:/workers 加载失败,请到 Workers 区刷新后重试。":"CHV worker 在线数未知:/workers 尚未加载,请到 Workers 区刷新。"," ",c.jsx("button",{type:"button",onClick:i.refreshWorkers,children:"刷新 CHV 可用性"})]}):null,c.jsx("textarea",{className:"prompt stacked",placeholder:"任务 prompt(必填)",value:r,onChange:L=>h(L.target.value)}),c.jsx("textarea",{className:"coord stacked",placeholder:"协调员子任务 JSON(可选),留空为普通任务",value:f,onChange:L=>p(L.target.value)}),c.jsxs("div",{className:"muted",children:["协调员格式:[","{",'"name":"后端","prompt":"做后端"',"}",",","{",'"name":"前端","prompt":"做前端"',"}","];提交后 worker 并行建子任务、等齐、合流汇总。不能与 goal/计划模式同用。协调员与 goal/计划互斥:勾了 goal 或计划模式时不要填协调员 JSON,提交前会校验并拦截。子任务费用记父任务创建人账;父任务创建时按分工数预留配额,余额不足时整个父任务被挡下。"]}),c.jsxs(Ht,{children:[c.jsxs("select",{"aria-label":"模型",value:F,onChange:L=>X(L.target.value),children:[c.jsx("option",{value:"",children:"模型:默认(跟随配置)"}),i.models.map(L=>c.jsx("option",{value:L,children:L},L))]}),i.modelCustomVisible?c.jsx("input",{type:"text",placeholder:"手动输入模型(中转列表取不到时用)",value:J,onChange:L=>Q(L.target.value)}):null,c.jsxs("select",{"aria-label":"推理强度",value:vt,onChange:L=>St(L.target.value),children:[c.jsx("option",{value:"",children:"推理强度:默认(跟随配置)"}),Lh.map(L=>c.jsx("option",{value:L,children:L},L))]}),c.jsxs("select",{"aria-label":"权限",value:jt,onChange:L=>K(L.target.value),children:[c.jsx("option",{value:"",children:"权限:默认(跟随配置)"}),c.jsx("option",{value:"ask",children:"请求批准"}),c.jsx("option",{value:"auto",children:"帮我批准"}),c.jsx("option",{value:"full",children:"完全访问"})]})]}),c.jsxs(Ht,{children:[c.jsxs("select",{"aria-label":"子任务模型",value:Nt,onChange:L=>w(L.target.value),children:[c.jsx("option",{value:"",children:"子任务模型:跟随本任务模型"}),i.models.map(L=>c.jsx("option",{value:L,children:L},L))]}),c.jsx("input",{type:"text",placeholder:"子任务模型手动输入(中转列表里没有时填,如 gpt-6-luna;留空=跟随本任务模型)",value:V,onChange:L=>nt(L.target.value)}),c.jsxs("select",{"aria-label":"子任务强度",value:Dt,onChange:L=>qt(L.target.value),children:[c.jsx("option",{value:"",children:"子任务强度:跟随本任务强度"}),Lh.map(L=>c.jsx("option",{value:L,children:L},L))]})]}),c.jsx("div",{className:"muted",children:"子任务模型/强度是派生默认:本任务用 marker task-create 或协调员分工派生的子任务,未显式写模型/强度时用它;块/分工里显式写了则覆盖它。也可以不在这里填、直接在 prompt 里写“子任务用 gpt-6-luna max”,建任务时服务端会解析成同一对字段(任务详情里可核对)。"}),N?c.jsx("div",{className:"muted",children:"goal 模式:输入框内容即目标,不需要专用 prompt 与验收命令;不限轮次,模型确认达成后在回复末尾输出“目标已达成”即结束,未达成会一直运行,可用取消停止。"}):null,j?c.jsx("div",{className:"muted",children:"goal 模式无敌版:goal 模式 + 任务不限时(去掉 worker/docker 总超时;单轮 600 秒护栏还在,不限的是任务总时长与总轮次)。服务端默认的自动续跑与断点镜像同时生效。"}):null,c.jsxs(Ht,{children:[c.jsx("span",{className:"muted",children:"附件(放进任务工作区,图片与普通文件不区分):"}),c.jsx("input",{ref:dt,type:"file",multiple:!0,title:"文件",disabled:gt,onChange:xt}),c.jsx("input",{ref:kt,type:"file",multiple:!0,title:"文件夹",disabled:gt,onChange:xt,webkitdirectory:""}),c.jsx("input",{ref:H,type:"file",accept:"image/*",multiple:!0,title:"图片",disabled:gt,onChange:xt})]}),gt?c.jsxs("div",{className:"muted",children:["cloud-hypervisor 任务不支持附件:附件输入已禁用(后端对带附件的 CHV 任务会 400,错误文案原样显示在下方;切换回其他执行器可恢复附件输入)。",ot?c.jsxs(c.Fragment,{children:["当前仍有已选附件,请先清空再提交。 ",c.jsx("button",{type:"button",onClick:()=>{dt.current&&(dt.current.value=""),kt.current&&(kt.current.value=""),H.current&&(H.current.value=""),yt(!1)},children:"清空附件"})]}):""]}):null,c.jsxs(Ht,{children:[c.jsx("button",{className:"seal",onClick:Se,children:"提交任务"}),c.jsx("span",{className:"muted",children:G||i.dispatchMsg})]})]})}function Ih(i){const r=(i.prompt||"").trim();return r?[...r][0]:i.id?[...i.id][0]:"?"}function Rc(i){const r=Math.max(0,Math.floor(i));if(r<60)return r+"秒";if(r<3600){const p=Math.floor(r/60),N=r%60;return N?p+"分"+N+"秒":p+"分"}if(r<86400){const p=Math.floor(r/3600),N=Math.floor(r%3600/60);return N?p+"小时"+N+"分":p+"小时"}const h=Math.floor(r/86400),f=Math.floor(r%86400/3600);return f?h+"天"+f+"小时":h+"天"}function iu(i){if(i==null)return null;const r=Number(i);return Number.isFinite(r)?r:null}function Ph(i){const r=Date.now()/1e3,h=iu(i.started_at),f=iu(i.finished_at),p=iu(i.created_at);return h!==null&&f!==null&&f>=h?[Rc(f-h),"总耗时:开始→结束(末次认领的执行时长)"]:h!==null&&i.status==="running"?["进行中 "+Rc(r-h),"总耗时:进行中,已用时长从开始算起,随刷新更新"]:p!==null&&f!==null&&f>=p?[Rc(f-p),"总耗时:创建→完成(含排队等待;无开始时间时的兜底口径)"]:p!==null&&(i.status==="queued"||i.status==="running")?[(i.status==="queued"?"排队中 ":"进行中 ")+Rc(r-p),"总耗时:从创建算起的已用时长(无开始时间时的兜底口径)"]:["—","总耗时:无可用时间戳"]}function yg(i){if(i==null||!Number.isFinite(Number(i)))return"—";const r=Number(i)/1e6;return"$"+(r>0&&r<.01?r.toFixed(4):r.toFixed(2))}function tv(i){const r=iu(i.total_tokens);if(r!==null)return[r.toLocaleString()+" token","token:服务端 total_tokens"];const h=iu(i.actual_tokens),f=iu(i.estimated_tokens);return h!==null||f!==null?[((h||0)+(f||0)).toLocaleString()+" token","token:actual + estimated 分项加总"]:["token —","token:本任务对象未带 token 字段;单任务真数在详情的成本里可查,不逐个调成本接口、不编造"]}function gg({onNew:i}){const r=Kt(),h=ci(r.workers,r.workersSummary,r.workersError);return c.jsxs("div",{children:[c.jsxs("div",{className:"search-box",children:[c.jsxs("svg",{viewBox:"0 0 24 24",width:"18",height:"18",fill:"none",stroke:"currentColor",strokeWidth:"1.8",strokeLinecap:"round","aria-hidden":"true",children:[c.jsx("circle",{cx:"11",cy:"11",r:"7"}),c.jsx("path",{d:"M21 21l-4.3-4.3"})]}),c.jsx("input",{type:"text",placeholder:"搜索","aria-label":"搜索任务",value:r.searchText,onChange:f=>r.setSearchText(f.target.value)})]}),c.jsx("div",{className:"bar",children:c.jsxs("select",{"aria-label":"项目",value:r.projectId,onChange:f=>r.selectProject(f.target.value),children:[r.projects.length===0?c.jsx("option",{value:"",children:"(先加载项目)"}):null,r.projects.map(f=>c.jsxs("option",{value:f.id,children:[f.name," (",Ae(f.id),")"]},f.id))]})}),c.jsxs("div",{className:"list-section-head",children:[c.jsx("span",{children:"主要聊天"}),c.jsx("button",{type:"button","aria-label":"派发新任务",title:"派发新任务",onClick:i,children:"+"})]}),c.jsxs("div",{className:"bar",children:[c.jsxs("select",{"aria-label":"状态筛选",value:r.statusFilter,onChange:f=>r.setStatusFilter(f.target.value),children:[c.jsx("option",{value:"",children:"状态:全部"}),["queued","running","completed","failed","interrupted","merged","merge-failed"].map(f=>c.jsx("option",{value:f,children:f},f))]}),c.jsxs("select",{"aria-label":"执行器筛选",value:r.executorFilter,onChange:f=>r.setExecutorFilter(f.target.value),children:[c.jsx("option",{value:"",children:"执行器:全部"}),c.jsx("option",{value:"local",children:"local"}),c.jsx("option",{value:"docker",children:"docker"}),c.jsx("option",{value:"cloud",children:"cloud"}),c.jsx("option",{value:"cloud-hypervisor",children:"cloud-hypervisor(实验)"})]})]}),c.jsxs("div",{className:"bar",children:[c.jsx("button",{onClick:r.refreshTasks,children:"刷新"}),c.jsx("span",{className:"muted",children:r.taskCountText})]}),c.jsxs("div",{className:"bar",children:[c.jsx("button",{disabled:r.taskPage<=0,onClick:()=>r.goToTaskPage(r.taskPage-1),children:"上一页"}),c.jsxs("span",{className:"muted",children:["第 ",r.taskPage+1," / ",r.taskTotalPages," 页(每页 20 条,最新在前)"]}),c.jsx("button",{disabled:r.taskPage+1>=r.taskTotalPages,onClick:()=>r.goToTaskPage(r.taskPage+1),children:"下一页"})]}),r.taskError?c.jsx("div",{className:"err",children:r.taskError}):r.projectId?r.filteredTasks.length===0?c.jsx("div",{className:"muted",children:r.taskTotal?"没有符合筛选条件的任务":"该项目暂无任务"}):c.jsx("ul",{className:"issue-list",children:r.filteredTasks.map(f=>{var R;const[p,N]=Ph(f),[C,j]=tv(f),z=Ih(f);return c.jsxs("li",{className:"issue-item"+(((R=r.currentTask)==null?void 0:R.id)===f.id?" selected":""),onClick:()=>r.showDetail(f.id),children:[c.jsx("span",{className:"issue-state st-"+f.status,"aria-hidden":"true"}),c.jsx("span",{className:"issue-first",title:"任务首字:"+z,"aria-hidden":"true",children:z}),c.jsxs("span",{style:{flex:1,minWidth:0},children:[c.jsxs("div",{className:"issue-title",children:[f.plan?"[计划] ":"",f.forked_from?"[分支] ":"",f.coordinator_parts?"[协调] ":"",f.parent_task_id?"[协调子任务] ":"",String(f.prompt||Ae(f.id)).slice(0,60)]}),c.jsxs("div",{className:"issue-meta",children:[Ae(f.id)," · ",c.jsx("span",{className:"st-"+f.status,children:f.status})," · ",f.executor||"",f.updated_at?" · "+new Date(f.updated_at*1e3).toLocaleString():""," · ",c.jsxs("span",{title:N,children:["耗时 ",p]})," · ",c.jsx("span",{title:j,children:C})]}),(f.status==="failed"||f.status==="interrupted"||f.status==="merge-failed")&&f.error_text?c.jsx("div",{className:"issue-meta",title:f.error_text,children:/503|Service Unavailable/.test(f.error_text)?"上游 503:模型服务暂时不可用":f.error_text.replace(/\s+/g," ").slice(0,60)}):null,kc(f.executor,f.status,h)?c.jsx("div",{className:"issue-meta",title:kc(f.executor,f.status,h),children:kc(f.executor,f.status,h)}):null]})]},f.id)})}):c.jsx("div",{className:"muted",children:"先选项目"})]})}function pg(i){const r=d.useRef(null),[h,f]=d.useState(!0),p=d.useRef(!0),N=()=>{const C=r.current;if(!C)return;const j=C.scrollHeight-C.scrollTop-C.clientHeight<80;p.current=j,f(j)};return d.useEffect(()=>{p.current&&r.current&&(r.current.scrollTop=r.current.scrollHeight)},[i]),{ref:r,following:h,onScroll:N}}function Sg({msg:i}){if(i.role==="status")return c.jsx("div",{className:"chat-status",children:i.text});const r=i.label||(i.role==="user"?"用户":i.role==="assistant"?"助手":"工具");return c.jsxs("div",{className:"gh-comment "+i.role,children:[c.jsx("div",{className:"gh-comment-header",children:c.jsx("span",{className:"gh-comment-author",children:r})}),c.jsx("div",{className:"gh-comment-body",children:i.text})]})}function xg({messages:i,empty:r}){const{ref:h,following:f,onScroll:p}=pg(i.length);return c.jsxs("div",{className:"chat-list",ref:h,onScroll:p,children:[i.length===0?c.jsx("div",{className:"muted",children:r||"(暂无消息)"}):i.map(N=>c.jsx(Sg,{msg:N},N.id)),!f&&i.length>0?c.jsx("button",{className:"chat-follow",onClick:()=>{h.current&&(h.current.scrollTop=h.current.scrollHeight)},children:"回到最新 ↓"}):null]})}function bg({onSend:i,disabled:r,placeholder:h}){const[f,p]=d.useState(""),N=()=>{const C=f.trim();!C||r||(i(C),p(""))};return c.jsxs("div",{className:"gh-comment-box",children:[c.jsx("textarea",{"aria-label":"会话输入",rows:3,placeholder:h||"消息",value:f,disabled:r,onChange:C=>p(C.target.value),onKeyDown:C=>{C.key==="Enter"&&!C.shiftKey&&(C.preventDefault(),N())}}),c.jsx("div",{className:"gh-comment-footer",children:c.jsx("button",{type:"button",className:"primary","aria-label":"发送",disabled:r||!f.trim(),onClick:N,children:"发送"})})]})}function Zh(i,r){const h=i.id||"trace-"+r;if(i.kind==="message"){const f=(i.phase?"["+i.phase+"] ":"")+(i.text||"");return{id:h,role:"assistant",kind:i.kind,text:i.truncated?f+` +(链路摘要已截断至 300 字,完整回复见右侧任务结果)`:f,label:"助手"}}if(i.kind==="exec"||i.kind==="file"||i.kind==="tool"||i.kind==="call"){const f=i.kind==="exec"?"命令":i.kind==="file"?"文件":i.kind==="tool"?"工具":"调用";return{id:h,role:"tool",kind:i.kind,text:go(i),label:f}}return i.kind==="plan"?{id:h,role:"status",kind:i.kind,text:i.text||""}:i.kind==="reasoning"?{id:h,role:"status",kind:i.kind,text:"思考:"+(i.summary||"")}:i.kind==="agent-start"?{id:h,role:"status",kind:i.kind,text:"子代理开始:"+(i.name||"")+(i.prompt?"("+i.prompt+")":"")}:i.kind==="agent-done"?{id:h,role:"status",kind:i.kind,text:"子代理结束:"+(i.name||"")+" "+(i.status||"")}:{id:h,role:"status",kind:i.kind,text:go(i)}}function jg(){const i=Kt(),r=i.currentTask,h=d.useMemo(()=>{if(!r)return[];let z;if(i.chatHistory){z=i.chatHistory.messages.map(X=>({id:X.id,role:X.role,text:X.text,label:X.label,kind:X.kind}));const U=new Set(z.map(X=>X.id)),F=i.traceEntries.filter(X=>X.id&&!U.has(X.id)&&!String(X.id).startsWith("fu-")).map((X,J)=>Zh(X,J));z=[...z,...F]}else{const U=r.prompt?[{id:"task-prompt",role:"user",text:r.prompt}]:[],F=i.traceEntries.filter(J=>!String(J.id||"").startsWith("fu-")).map((J,Q)=>Zh(J,Q)),X=i.followups.map((J,Q)=>({id:"followup-"+Q,role:"user",text:String(J.body||""),label:"后续消息"}));z=[...U,...F,...X]}const R=i.traceKind,q=R?z.filter(U=>U.role==="user"||!U.kind||U.kind===R):z,g=i.traceDone?[{id:"trace-done",role:"status",text:"链路已结束("+i.traceDone+")"}]:[];return[...q,...g]},[r,i.chatHistory,i.traceEntries,i.followups,i.traceKind,i.traceDone]);if(!r)return c.jsx("div",{className:"muted",children:"点任务行查看详情后,这里显示继续会话"});const f=uu.includes(r.status),p=["queued","running"].includes(r.status),N=f||p,C=f?"终态任务:发送走继续接口(同一会话再跑一轮)":p?"运行中/排队中:发送走后续消息直接发送(跑完当前轮后优先执行)":"merged/merge-failed 任务不可继续,也不可发后续消息",j=i.chatHistory?i.chatHistory.legacy_continue_count??null:null;return c.jsxs("div",{className:"chat-column",children:[c.jsx("div",{className:"muted",children:"实时对话"}),c.jsxs(Ht,{children:[c.jsxs("select",{"aria-label":"链路类型",value:i.traceKind,onChange:z=>i.setTraceKind(z.target.value),children:[c.jsx("option",{value:"",children:"链路类型:全部"}),["exec","file","call","tool","message","reasoning","plan","agent-start","agent-done"].map(z=>c.jsx("option",{value:z,children:z==="exec"?"命令":z==="file"?"文件":z==="call"?"调用":z==="tool"?"工具":z==="message"?"消息":z==="reasoning"?"思考":z==="plan"?"计划":"子代理"},z))]}),c.jsx("button",{onClick:i.downloadTrace,children:"下载完整链路"}),c.jsx("span",{className:"muted",children:i.traceMsg})]}),i.traceNote?c.jsx("div",{children:c.jsx("span",{className:"err",children:i.traceNote})}):null,j!==null&&j>0?c.jsxs("div",{className:"muted",children:["此前有 ",j," 条继续消息发生在持久化之前,未入库,上面历史不完整;部署后新发的继续消息会正常显示。"]}):null,i.chatHistory&&i.chatHistory.trace_note?c.jsx("div",{className:"muted",children:i.chatHistory.trace_note}):null,c.jsx(xg,{messages:h,empty:"(暂无会话消息)"}),c.jsx(bg,{disabled:!N,placeholder:"消息",onSend:z=>void i.sendChatMessage(z)}),c.jsxs("div",{className:"muted",children:[C,"。",i.chatMsg]}),c.jsx("div",{className:"muted",children:"对话时间线:首条是本任务 prompt(用户);助手回复、命令/文件/工具/调用、计划/思考/子代理开始结束都在同一列里,不再另起一份链路名单。流式进行中时新条目实时追加;你在底部时自动跟随,往上翻时暂停跟随,点“回到最新”恢复。"})]})}function Tg(){return Kt().currentTask?c.jsx("div",{className:"conversation",children:c.jsx(jg,{})}):c.jsx("div",{className:"conversation",children:c.jsx("div",{className:"muted",children:"在任务列表里点一条任务,对话和详情就切到该任务;也可以在上面派发一个新任务。"})})}function _g(){const i=Kt(),r=i.currentTask,[h,f]=d.useState("");if(!r)return null;const p=uu.includes(r.status),N=["failed","interrupted","merge-failed"].includes(r.status);return c.jsxs(c.Fragment,{children:[c.jsxs(Ht,{children:[c.jsx("button",{disabled:!N,title:N?"":"仅 failed/interrupted/merge-failed 可重试",onClick:()=>i.doAction("retry"),children:"重试"}),c.jsx("button",{className:"danger",disabled:p,title:p?"终态任务不可取消":"",onClick:()=>i.doAction("cancel"),children:"取消"}),c.jsx("button",{disabled:r.status!=="completed",title:r.status!=="completed"?"仅 completed 可合并":"",onClick:()=>i.doAction("merge"),children:"合并(completed → merged)"}),c.jsx("button",{disabled:r.status!=="merged",title:r.status!=="merged"?"仅 merged 可提 PR":"",onClick:()=>i.doAction("pr"),children:"提 PR(须 merged)"}),p?c.jsx("button",{onClick:()=>i.setForkVisible(!0),children:"分支"}):null,c.jsx("button",{onClick:i.taskCost,children:"查成本"}),c.jsxs("span",{className:"muted",children:[i.actionMsg," ",i.actionReasons]})]}),i.forkVisible?c.jsxs("div",{children:[c.jsx("input",{type:"text",placeholder:"分支首轮消息(必填,带历史的新会话)",style:{width:"100%",boxSizing:"border-box",margin:"6px 0"},value:h,onChange:C=>f(C.target.value)}),c.jsxs(Ht,{children:[c.jsx("button",{className:"seal",onClick:async()=>{await i.forkTask(h),f("")},children:"创建分支任务"}),c.jsx("span",{className:"muted",children:i.forkMsg})]})]}):null]})}function Eg(){const r=Kt().currentTask,[h,f]=d.useState(""),[p,N]=d.useState(null),[C,j]=d.useState("未开始"),[z,R]=d.useState(""),[q,g]=d.useState(!1),U=d.useRef(null),F=d.useRef(null),X=d.useCallback(async()=>{if(r)try{const w=await at(W.taskBrowserState(r.id));N(w),R("")}catch(w){const V=w instanceof Error?w.message:String(w);V.includes("503")?N(null):R(V)}},[r]);d.useEffect(()=>{var w;if(f(""),N(null),j("未开始"),R(""),g(!1),(w=U.current)==null||w.abort(),!!r)return X(),()=>{var V;(V=U.current)==null||V.abort()}},[r,X]),d.useEffect(()=>{if(!r||q)return;const w=setInterval(()=>void X(),5e3);return()=>clearInterval(w)},[r,q,X]);const J=d.useCallback(async()=>{var V;if(!r)return;(V=U.current)==null||V.abort();const w=new AbortController;U.current=w,g(!0),j("连接中…"),R("");try{const nt=await fetch(W.taskBrowserStream(r.id),{headers:{"X-API-Key":sl()},signal:w.signal});if(!nt.ok||!nt.body){const kt=await nt.json().catch(()=>null);throw new Error(kt&&kt.error||"HTTP "+nt.status)}j("直播中");const Dt=nt.body.getReader(),qt=new TextDecoder;let dt="";for(;;){const{done:kt,value:H}=await Dt.read();if(kt)break;dt+=qt.decode(H,{stream:!0});let ot=dt.indexOf(` `);for(;ot>=0;){const yt=dt.slice(0,ot);dt=dt.slice(ot+2);for(const G of yt.split(` `))if(G.startsWith("data: "))try{const Y=JSON.parse(G.slice(6));Y.waiting?j(Y.task_status==="interrupted"?"任务已中断,等待续跑…":Y.task_status==="paused"?"任务已暂停…":"等待会话…"):Y.image&&(f("data:image/jpeg;base64,"+Y.image),j("直播中"));const tt={};Y.holder&&(tt.holder=Y.holder),Y.url&&(tt.url=Y.url),Y.title!==void 0&&(tt.title=Y.title),Object.keys(tt).length>0&&N(gt=>({...gt||{},...tt}))}catch{}ot=dt.indexOf(` -`)}}j("流已断开")}catch(nt){if(nt.name==="AbortError")return;R(nt instanceof Error?nt.message:String(nt)),j("未开始")}finally{g(!1)}},[r]),V=d.useCallback(()=>{var w;(w=U.current)==null||w.abort(),g(!1),j("已停止")},[]),vt=d.useCallback(async w=>{if(r)try{const Q=await at(W.taskBrowserControl(r.id),"POST",{holder:w});N(Q),R("")}catch(Q){R(Q instanceof Error?Q.message:String(Q))}},[r]),St=d.useCallback(async w=>{if(!r||(p==null?void 0:p.holder)!=="user"||!F.current)return;const Q=F.current.getBoundingClientRect(),nt=Math.round((w.clientX-Q.left)/Q.width*1280),Dt=Math.round((w.clientY-Q.top)/Q.height*800);try{await at(W.taskBrowserInput(r.id),"POST",{type:"mouse_click",x:nt,y:Dt})}catch(qt){R(qt instanceof Error?qt.message:String(qt))}},[r,p]),jt=d.useCallback(async w=>{if(!(!r||(p==null?void 0:p.holder)!=="user")){w.preventDefault();try{w.key.length===1?await at(W.taskBrowserInput(r.id),"POST",{type:"type",text:w.key}):await at(W.taskBrowserInput(r.id),"POST",{type:"key",key:w.key})}catch(Q){R(Q instanceof Error?Q.message:String(Q))}}},[r,p]);if(!r)return c.jsx("div",{className:"muted",children:"点任务行查看详情"});const K=(p==null?void 0:p.holder)||"—",Nt=p!==null;return c.jsxs("div",{children:[c.jsxs("div",{style:{display:"flex",gap:8,flexWrap:"wrap",alignItems:"center"},children:[c.jsxs("span",{children:["状态 ",c.jsx("b",{children:C})]}),c.jsxs("span",{className:"muted",children:["会话 ",Nt?"活跃":"未激活(轮次结束即消失)"]}),c.jsxs("span",{className:"muted",children:["控制权 ",K]}),p!=null&&p.url?c.jsx("span",{className:"muted",title:p.url,children:p.url.slice(0,80)}):null]}),c.jsxs("div",{style:{display:"flex",gap:8,flexWrap:"wrap",marginTop:8},children:[q?c.jsx("button",{type:"button",onClick:V,children:"停止观看"}):c.jsx("button",{type:"button",onClick:()=>void J(),children:"开始观看"}),c.jsx("button",{type:"button",onClick:()=>void X(),children:"刷新状态"}),K!=="user"?c.jsx("button",{type:"button",onClick:()=>void vt("user"),disabled:!Nt,children:"接管"}):c.jsx("button",{type:"button",onClick:()=>void vt("agent"),children:"交还给 Agent"})]}),z?c.jsx("div",{className:"muted",style:{marginTop:8},children:z}):null,K==="user"?c.jsx("div",{className:"muted",style:{marginTop:8},children:"你已接管:点画面即点击,键盘输入直达页面。交还后 Agent 续跑前会先快照确认你的改动。"}):null,c.jsx("div",{style:{marginTop:8},children:h?c.jsx("img",{ref:F,src:h,alt:"浏览器实时画面",tabIndex:0,onClick:w=>void St(w),onKeyDown:w=>void jt(w),style:{width:"100%",maxWidth:1280,cursor:K==="user"?"crosshair":"default",border:"1px solid var(--border, #ccc)"}}):c.jsx("div",{className:"muted",children:Nt?"点“开始观看”接入画面流(接管后点画面即点击)":"浏览器会话未激活:任务本轮未开浏览器,或轮次已结束。"})})]})}function Ng(){const i=Kt(),[r,h]=d.useState("uncommittedChanges"),[f,p]=d.useState(""),[N,C]=d.useState(""),[j,z]=d.useState("");return c.jsxs(c.Fragment,{children:[c.jsx("h3",{children:"评审队列"}),c.jsxs("select",{"aria-label":"评审目标",value:r,onChange:R=>h(R.target.value),children:[c.jsx("option",{value:"uncommittedChanges",children:"未提交改动(工作区)"}),c.jsx("option",{value:"baseBranch",children:"与分支的差异"}),c.jsx("option",{value:"commit",children:"指定提交"}),c.jsx("option",{value:"custom",children:"自定义评审指令"})]}),c.jsx("input",{className:"stacked",placeholder:"与分支的差异用:分支名(如 main)",style:{width:"100%"},value:f,onChange:R=>p(R.target.value)}),c.jsx("input",{className:"stacked",placeholder:"指定提交用:提交 sha",style:{width:"100%"},value:N,onChange:R=>C(R.target.value)}),c.jsx("textarea",{placeholder:"自定义评审指令用:评审指令正文",value:j,onChange:R=>z(R.target.value)}),c.jsxs(Ht,{children:[c.jsx("button",{className:"primary",onClick:()=>void i.postReview({target_type:r,branch:f.trim()||void 0,sha:N.trim()||void 0,instructions:j.trim()||void 0}),children:"登记评审需求"}),c.jsx("button",{onClick:i.refreshReviews,children:"刷新队列"}),c.jsx("span",{className:"muted",children:i.reviewMsg})]}),c.jsx("div",{className:"muted",children:"本基线只登记评审需求(requested),不自动跑评审;评审是否执行、结果如何,以评审队列里的记录为准。"}),i.reviewError?c.jsx("div",{className:"muted",children:i.reviewError}):i.reviews.length===0?c.jsx("div",{className:"muted",children:"点任务行查看详情后,点“刷新队列”查看评审记录"}):c.jsx(cu,{children:i.reviews.map(R=>{let q=R.target_type||"";return R.branch&&(q+=":"+R.branch),R.sha&&(q+=":"+String(R.sha).slice(0,12)),R.instructions&&(q+=":"+String(R.instructions).slice(0,80)),c.jsxs("li",{children:[c.jsxs("span",{className:"row-main",children:[c.jsx("b",{children:"评审"})," ",Ae(R.id)," | ",q," | ",c.jsx("span",{className:"st-"+(R.status||""),children:R.status}),R.created_at?" | "+Kl(R.created_at):"",c.jsx("br",{}),R.findings?String(R.findings).slice(0,1e3):c.jsx("span",{className:"muted",children:"(暂无 findings:评审尚未执行或尚未写回)"})]}),c.jsx("button",{onClick:()=>i.submitFindings(R.id),children:"写 findings"})]},R.id)})})]})}function Cg(){const i=Kt();return i.currentTask?i.currentTask.coordinator_parts?i.coordChildren.length===0?c.jsx("div",{className:"muted",children:"(子任务尚未创建)"}):c.jsx(cu,{children:i.coordChildren.map(r=>c.jsx("li",{children:c.jsxs("span",{className:"row-main",children:["分工 ",c.jsx("b",{children:r.coordinator_part||""})," | 状态 ",c.jsx("span",{className:"st-"+r.status,children:r.status})," | ",Ae(r.id)," | ",String(r.prompt||"").slice(0,60)]})},r.id))}):c.jsx("div",{className:"muted",children:"(非协调员任务)"}):c.jsx("div",{className:"muted",children:"点任务行查看详情后,这里显示协调员的子任务"})}function Ag(){const i=Kt();if(!i.currentTask)return c.jsx("div",{className:"muted",children:"点任务行查看详情后,这里显示子代理链路"});if(i.agents.length===0)return c.jsxs("div",{className:"muted",children:["(暂无子代理)",i.agentsNote?c.jsx("span",{className:"err",children:i.agentsNote}):null]});const r={main:"主任务",delegate:"委派子任务",model:"模型子代理"},h={running:"进行中",completed:"已完成",failed:"失败",interrupted:"已中断",unknown:"未知"};return c.jsx(cu,{children:i.agents.map((f,p)=>c.jsx("li",{children:c.jsxs("span",{className:"row-main",children:[c.jsxs("b",{children:[r[f.kind||""]||f.kind||"",":"]}),f.name||""," ",c.jsx("span",{className:"st-"+(f.status||""),children:h[f.status||""]||f.status||"?"})," | 过程调用 "+(f.calls?f.calls.length:0)+" 条",f.updated_ts?" | 最近活动 "+Kl(f.updated_ts/1e3):"",f.prompt?c.jsxs(c.Fragment,{children:[c.jsx("br",{}),c.jsxs("span",{className:"muted",children:["任务:",f.prompt]})]}):null,f.calls&&f.calls.length?c.jsxs(c.Fragment,{children:[c.jsx("br",{}),f.calls.slice(-50).map((N,C)=>c.jsx("div",{children:go(N)},C))]}):c.jsxs(c.Fragment,{children:[c.jsx("br",{}),c.jsx("span",{className:"muted",children:"(暂无过程调用)"})]})]})},p))})}function Mg(){const i=Kt();return c.jsxs(c.Fragment,{children:[c.jsxs(Ht,{children:[c.jsx("button",{onClick:i.refreshApprovals,children:"刷新审批队列"}),c.jsx("span",{className:"muted",children:i.approvalMsg})]}),c.jsx("div",{className:"muted",children:"待审批项需人工点记录允许或记录拒绝;超时默认拒绝。当前版本只留回执,不拦截实时行为(实时请求的事前拦截待 host 接入后生效,在此之前实时行为不变)。"}),c.jsx(bl,{empty:"(暂无审批项)",items:i.approvals.map(r=>c.jsxs("span",{className:"row-main",children:["[",r.kind,"|",r.status,"] ",r.summary,["approved","denied","expired"].includes(r.status||"")?"|回执:"+(r.decision||r.status)+" by "+String(r.decided_by||"").slice(0,8)+" "+Kl(r.decided_at):"",r.status==="pending"?c.jsxs(c.Fragment,{children:[" ",c.jsx("button",{onClick:()=>i.decideApproval(r.id,"approved"),children:"记录允许"})," ",c.jsx("button",{onClick:()=>i.decideApproval(r.id,"denied"),children:"记录拒绝"})]}):null]},r.id))})]})}function Og(){const i=Kt(),[r,h]=d.useState(""),[f,p]=d.useState("");return c.jsxs(c.Fragment,{children:[c.jsx("input",{placeholder:"目标任务 id(与本任务同项目;对方运行中排队执行、已终态则续跑复活,绝不打断当前轮)",style:{width:"100%"},value:r,onChange:N=>h(N.target.value)}),c.jsx("textarea",{placeholder:"消息正文(以本任务名义发出;对方 agent 会看到发送方任务 id,可照此回消息)",value:f,onChange:N=>p(N.target.value)}),c.jsxs(Ht,{children:[c.jsx("button",{onClick:async()=>{await i.postPeer("queued",r,f),p("")},children:"排队发给对方"}),c.jsx("button",{className:"primary",onClick:async()=>{await i.postPeer("direct",r,f),p("")},children:"直接发送"}),c.jsx("span",{className:"muted",children:i.peerMsg})]}),c.jsx(bl,{empty:"(本任务尚未发出任务间消息)",items:i.peerMessages.map((N,C)=>c.jsxs("span",{className:"row-main",children:["[发给 ",String(N.task_id).slice(0,8),"|",N.mode,"|",N.status,"] ",String(N.body||"").slice(0,120)]},C))})]})}function zg(){const i=Kt(),[r,h]=d.useState(""),f=i.currentTask?["completed","failed","interrupted"].includes(i.currentTask.status):!1;return c.jsxs(c.Fragment,{children:[c.jsx("div",{className:"muted",children:i.threadText||"点任务行查看详情后,这里显示线程元数据"}),c.jsxs(Ht,{children:[c.jsx("input",{type:"text",placeholder:"新线程名",value:r,onChange:p=>h(p.target.value)}),c.jsx("button",{disabled:!f,onClick:()=>i.threadAction("rename",r),children:"重命名"}),c.jsx("button",{disabled:!f,onClick:()=>i.threadAction("archive",""),children:"归档"}),c.jsx("button",{disabled:!f,onClick:()=>i.threadAction("unarchive",""),children:"取消归档"}),c.jsx("button",{disabled:!f,onClick:()=>i.threadAction("compact",""),children:"压缩"}),c.jsx("span",{className:"muted",children:i.threadGateNote}),c.jsx("span",{className:"muted",children:i.threadMsg})]}),c.jsx(bl,{empty:"暂无其他线程",items:i.threadList.map((p,N)=>c.jsxs("span",{className:"row-main",children:[p.name||p.thread_name||p.id||""," [",p.id||"","]",p.archived?" [已归档]":"",p.cwd?" "+p.cwd:""]},N))}),c.jsx("div",{className:"muted",children:"限制:Docker/异机任务线程操作一律 409;merged/merge-failed 不可改;turn/steer 与 review/start 不在本批。"})]})}function Rg(i){return/503|Service Unavailable/.test(i)?"上游模型服务暂时不可用(503):模型一轮都没跑出来,不是任务逻辑失败;等上游恢复或换模型重试":/429|rate limit/i.test(i)?"上游限流(429):模型拒绝了本次调用,可稍后重试":/timeout|timed out/i.test(i)?"上游调用超时:模型响应未在限时内返回":/401|403|Invalid API key|unauthorized/i.test(i)?"上游鉴权失败:网关 key 或权限有问题":/ECONNREFUSED|ENOTFOUND|network|fetch failed/i.test(i)?"连不上上游模型服务:网络或网关地址有问题":"任务失败(原因见原始错误文本)"}function Dg(i){return i===null||i<0?"未知":i<60?Math.floor(i)+" 秒前":i<3600?Math.floor(i/60)+" 分钟前":(i/3600).toFixed(1)+" 小时前"}const kg=[["overview","概览"],["collab","协作"],["review","评审与审批"],["threads","线程"],["browser","浏览器"]];function wg(){const i=Kt(),r=i.currentTask,[h,f]=d.useState("overview");if(!r)return c.jsx(Ft,{id:"nav-detail",title:"任务详情",children:c.jsx("div",{className:"muted",children:"点任务行查看详情"})});const[p,N]=Ph(r),[C,j]=tv(r),z=ci(i.workers,i.workersSummary,i.workersError),R=kc(r.executor,r.status,z),q=Ih(r),g=i.costSummary,U=r.plan?"计划模式(只读)":r.goal_mode?r.no_time_limit?"goal 模式无敌版(不限时)":"goal 模式(不限轮次,模型宣告完成)":r.verify_command?"goal 模式(旧语义,验收命令)":null;return c.jsxs("div",{className:"detail-stack",id:"nav-detail",children:[c.jsx("nav",{className:"detail-tabs","aria-label":"任务详情页签",children:kg.map(([F,X])=>c.jsx("button",{type:"button",className:h===F?"active":"",onClick:()=>f(F),children:X},F))}),h==="overview"?c.jsxs(c.Fragment,{children:[c.jsxs(Ft,{title:"任务详情",children:[c.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[c.jsx("span",{className:"issue-first",title:"任务首字:"+q,"aria-hidden":"true",children:q}),c.jsxs("span",{children:["状态 ",c.jsx("b",{className:"st-"+r.status,children:r.status}),"(",Iy[r.status]||"未知状态",")"]}),c.jsxs("span",{className:"muted",children:["更新 ",Kl(r.updated_at)]})]}),c.jsxs("div",{className:"detail-cards",children:[c.jsxs("div",{className:"detail-card",title:N,children:[c.jsx("div",{className:"detail-card-label",children:"耗时"}),c.jsx("div",{className:"detail-card-value",children:p})]}),c.jsxs("div",{className:"detail-card",title:j,children:[c.jsx("div",{className:"detail-card-label",children:"token"}),c.jsx("div",{className:"detail-card-value",children:C})]}),c.jsxs("div",{className:"detail-card",title:"LLM 请求数 = GET /tasks/{id}/cost 的 llm_requests,即 cost_records 的 completions 之和:host.py 每收到一次 thread/tokenUsage/updated(一次上游补全)计 1,worker 每轮终局记账时累加;估算行(无真实用量)计 0;部署前已记账的老任务没有 completions 列,显示 0 而非回填数",children:[c.jsx("div",{className:"detail-card-label",children:"LLM 请求数"}),c.jsx("div",{className:"detail-card-value",children:g===void 0?"加载中…":g===null?"—":(g.llm_requests??0).toLocaleString()}),g===void 0?c.jsx("div",{className:"muted",children:"成本加载中…"}):g===null?c.jsx("div",{className:"muted",children:"成本明细加载失败,LLM 请求数不可用"}):c.jsxs("div",{className:"muted",children:["上游补全次数之和| 轮数 ",g.turns??0," | fallback ",g.fallback_count??0]})]}),c.jsxs("div",{className:"detail-card",title:"成本明细:GET /tasks/{id}/cost 聚合;美元只计有价行,无价 token 单列;缓存是输入 token 的子集,不另计",children:[c.jsx("div",{className:"detail-card-label",children:"成本"}),c.jsx("div",{className:"detail-card-value",children:g===void 0?"加载中…":g===null?"—":yg(g.usd_micros)}),g===void 0?c.jsx("div",{className:"muted",children:"成本加载中…"}):g===null?c.jsx("div",{className:"muted",children:"成本明细加载失败(“查成本”按钮仍可手动拉取原始 JSON)"}):c.jsxs("div",{className:"muted",children:["token ",(g.total_tokens??0).toLocaleString(),"(实际 ",(g.actual_tokens??0).toLocaleString()," / 估算 ",(g.estimated_tokens??0).toLocaleString(),",缓存 ",(g.cached_input_tokens??0).toLocaleString(),",无价 ",(g.unpriced_actual_tokens??0).toLocaleString(),")| fallback ",g.fallback_count??0]})]})]})]}),r.status==="running"||r.status==="queued"?(()=>{const F=Date.now()/1e3,X=r.last_heartbeat||r.updated_at||null,J=X?F-X:null;return c.jsxs(Ft,{title:"LLM 进度",children:[c.jsxs("div",{children:["状态 ",r.status==="running"?"进行中":"排队中"," · 已用 ",p,J!==null?" · 上次活动 "+Dg(J):""]}),R?c.jsx("div",{className:"muted",children:R}):null,J!==null&&J>180?c.jsx("div",{className:"muted",children:"超过 3 分钟无活动:可能在等上游模型响应(上游 503 时 worker 会重试后失败,失败后这里会显示失败原因;无活动不等于已断连,服务端没有“LLM 已连接”字段,不作此断言)"}):c.jsx("div",{className:"muted",children:"首轮无输出时属正常:模型第一轮回复到达前 trace 里没有消息;回复到达后会在会话区出现,成本里的 LLM 请求数随之增加"})]})})():null,r.error_text?c.jsxs(Ft,{title:"失败原因",children:[c.jsx("div",{children:Rg(r.error_text)}),c.jsx("pre",{style:{whiteSpace:"pre-wrap",overflowWrap:"anywhere"},children:r.error_text.slice(0,1e3)}),c.jsxs("div",{className:"muted",children:["完整错误文本仍在下方“操作与结果”里;模型 ",r.model||"默认"," · executor ",r.executor||""]})]}):null,c.jsx(Ft,{title:"基本信息",children:c.jsxs("dl",{className:"detail-kv",children:[c.jsxs("div",{children:[c.jsx("dt",{children:"任务"}),c.jsx("dd",{children:r.id})]}),c.jsxs("div",{children:[c.jsx("dt",{children:"turn"}),c.jsx("dd",{children:Ae(r.turn_id)})]}),c.jsxs("div",{children:[c.jsx("dt",{children:"root turn"}),c.jsx("dd",{children:Ae(r.root_turn_id)})]}),c.jsxs("div",{children:[c.jsx("dt",{children:"executor"}),c.jsxs("dd",{children:[r.executor||"",r.executor==="cloud-hypervisor"?"(实验执行器,默认关闭;仅展示,无 VM 控制按钮)":""]})]}),c.jsxs("div",{children:[c.jsx("dt",{children:"主机"}),c.jsx("dd",{children:r.worktree_host||""})]}),c.jsxs("div",{children:[c.jsx("dt",{children:"模型"}),c.jsx("dd",{children:r.model||"默认"})]}),c.jsxs("div",{children:[c.jsx("dt",{children:"强度"}),c.jsx("dd",{children:r.effort||"默认"})]}),r.child_model||r.child_effort?c.jsxs("div",{children:[c.jsx("dt",{children:"子任务默认"}),c.jsx("dd",{children:(r.child_model||"跟随本任务")+" / "+(r.child_effort||"跟随本任务")})]}):null,c.jsxs("div",{children:[c.jsx("dt",{children:"权限"}),c.jsx("dd",{children:r.approval||"默认"})]}),c.jsxs("div",{children:[c.jsx("dt",{children:"项目"}),c.jsx("dd",{children:Ae(r.project_id)})]}),U?c.jsxs("div",{children:[c.jsx("dt",{children:"模式"}),c.jsx("dd",{children:U})]}):null,r.forked_from?c.jsxs("div",{children:[c.jsx("dt",{children:"分支自"}),c.jsx("dd",{children:Ae(r.forked_from)})]}):null,r.coordinator_parts?c.jsxs("div",{children:[c.jsx("dt",{children:"协调员"}),c.jsxs("dd",{children:[r.coordinator_parts.length," 个子任务(",r.coordinator_parts.map(F=>F.name).join("、"),")"]})]}):null,r.parent_task_id?c.jsxs("div",{children:[c.jsx("dt",{children:"协调子任务"}),c.jsxs("dd",{children:["父任务 ",Ae(r.parent_task_id),r.coordinator_part?"("+r.coordinator_part+")":""]})]}):null,r.attachments&&r.attachments.length?c.jsxs("div",{children:[c.jsx("dt",{children:"附件"}),c.jsx("dd",{children:r.attachments.join("、")})]}):null,r.pr_url?c.jsxs("div",{children:[c.jsx("dt",{children:"PR"}),c.jsx("dd",{children:c.jsxs("a",{href:r.pr_url,target:"_blank",rel:"noopener noreferrer",children:["PR #",r.pr_number]})})]}):null]})}),c.jsx(Ft,{title:"prompt",children:c.jsx("div",{style:{whiteSpace:"pre-wrap",overflowWrap:"anywhere"},children:r.prompt||"(无 prompt)"})}),c.jsxs(Ft,{title:"操作与结果",children:[c.jsx(_g,{}),c.jsx("pre",{children:i.detailBody||"(暂无 result/error 输出)"}),i.costBox?c.jsx("pre",{children:i.costBox}):null]})]}):h==="collab"?c.jsxs(c.Fragment,{children:[c.jsx(Ft,{title:"协调子任务",children:c.jsx(Cg,{})}),c.jsx(Ft,{title:"子代理链路",children:c.jsx(Ag,{})}),c.jsx(Ft,{title:"给其他任务发消息",children:c.jsx(Og,{})})]}):h==="review"?c.jsxs(c.Fragment,{children:[c.jsx(Ft,{title:"评审队列",children:c.jsx(Ng,{})}),c.jsx(Ft,{title:"运行时审批队列",children:c.jsx(Mg,{})})]}):h==="browser"?c.jsx(c.Fragment,{children:c.jsx(Ft,{title:"浏览器远控",children:c.jsx(Eg,{})})}):c.jsx(c.Fragment,{children:c.jsx(Ft,{title:"线程",children:c.jsx(zg,{})})})]})}function Ug(){const i=Kt(),[r,h]=d.useState(""),[f,p]=d.useState(""),[N,C]=d.useState([]),[j,z]=d.useState(null),[R,q]=d.useState(!1),[g,U]=d.useState(""),[F,X]=d.useState(""),J=d.useCallback(async()=>{if(!i.keySaved){C([]);return}try{const K=await at(W.userKeys);C(K.keys||[]),X("")}catch(K){X(K instanceof Error?K.message:"加载 key 列表失败")}},[i.keySaved]);d.useEffect(()=>{J()},[J]);const V=async()=>{U(""),X(""),q(!1);try{const K=await at(W.userKeys,"POST",{label:f.trim()||"api-key"});z(K),p(""),U("新 key 已生成,只显示一次:复制后请妥善保存。"),await J()}catch(K){X(K instanceof Error?K.message:"生成 key 失败")}},vt=async()=>{if(j)try{await navigator.clipboard.writeText(j.api_key),q(!0)}catch{X("复制失败:请手动选中上方 key 文本复制")}},St=()=>{j&&(i.saveKeyInput(j.api_key),z(null),U("已存为当前浏览器 key(原有 key 未被撤销,需要时可单独撤销)。"))},jt=async K=>{const Nt=(()=>{try{return sl().slice(-4)}catch{return""}})(),w=K.id==="legacy"?"(主 key:撤销后当前浏览器保存的 key 也可能随之失效,需重新登录拿新 key)":K.hint&&K.hint===Nt?"(这就是本浏览器当前在用的 key,撤销后本页会立刻未连接)":"";if(window.confirm(`撤销 key「${K.label}」(后 4 位 ${K.hint||"无"})吗?${w}撤销不可恢复。`)){U(""),X("");try{await at(W.userKey(K.id),"DELETE"),U(`已撤销 key「${K.label}」。`),await J()}catch(Q){X(Q instanceof Error?Q.message:"撤销失败")}}};return c.jsxs(Ft,{id:"nav-key",title:"API Key",children:[c.jsxs(Ht,{children:[c.jsx("input",{type:"password",placeholder:"粘贴你的 X-API-Key(与调 API 用的同一个)",autoComplete:"off",value:r,onChange:K=>h(K.target.value)}),c.jsx("button",{className:"primary",onClick:()=>{i.saveKeyInput(r),h("")},children:"保存到本浏览器"}),c.jsx("button",{onClick:i.clearKeyInput,children:"清除"}),c.jsx("span",{className:"muted",children:i.keyState})]}),c.jsx("div",{children:c.jsx("span",{className:"muted",children:i.quotaText})}),!i.keySaved&&c.jsx("div",{children:c.jsx("span",{className:"muted",children:"先粘贴并保存已有 key,再生成或管理 key(生成接口本身也要用已有 key 鉴权)。"})}),i.keySaved&&c.jsxs(c.Fragment,{children:[c.jsxs(Ht,{children:[c.jsx("input",{placeholder:"新 key 备注(可选,默认 api-key,如 harness2-temp)",autoComplete:"off",value:f,onChange:K=>p(K.target.value)}),c.jsx("button",{className:"primary",onClick:()=>void V(),children:"生成新 key"}),c.jsx("button",{onClick:()=>void J(),children:"刷新列表"})]}),j&&c.jsxs("div",{children:[c.jsx("div",{children:c.jsxs("span",{className:"muted",children:["新 key(",j.label,",后 4 位 ",j.hint,"):只显示一次,关闭后无法再查看。"]})}),c.jsxs(Ht,{children:[c.jsx("input",{type:"text",readOnly:!0,value:j.api_key,onFocus:K=>K.target.select()}),c.jsx("button",{className:"primary",onClick:()=>void vt(),children:R?"已复制":"复制"}),c.jsx("button",{onClick:St,children:"存为当前浏览器 key"}),c.jsx("button",{onClick:()=>z(null),children:"关闭(不再显示)"})]})]}),g&&c.jsx("div",{children:c.jsx("span",{className:"muted",children:g})}),F&&c.jsx("div",{children:c.jsx("span",{children:F})}),c.jsxs("div",{className:"api-key-list",children:[N.length===0&&c.jsx("span",{className:"muted",children:"暂无 key(或列表加载失败,见上方报错)。"}),N.map(K=>c.jsxs("div",{children:[c.jsx("span",{children:K.label})," ",c.jsxs("span",{className:"muted",children:["后 4 位 ",K.hint||"无"," · 创建 ",Kl(K.created_at)," · 最近使用 ",K.last_used_at?Kl(K.last_used_at):"未使用过"]})," ",K.revoked_at?c.jsxs("span",{className:"muted",children:["已撤销(",Kl(K.revoked_at),")"]}):c.jsx("button",{onClick:()=>void jt(K),children:"撤销"})]},K.id))]})]})]})}function Hg(){const i=Kt();return c.jsxs(Ft,{id:"adminCard",title:c.jsxs(c.Fragment,{children:["管理区 ",c.jsx("span",{className:"muted",children:"(仅全局管理员)"})]}),children:[c.jsx("div",{className:"muted",children:i.adminGate}),i.adminBody?c.jsxs(c.Fragment,{children:[c.jsxs(Ht,{children:[c.jsx("input",{type:"text",placeholder:"要查配额的用户 id",value:i.adminUserId,onChange:r=>i.setAdminUserId(r.target.value)}),c.jsx("button",{onClick:i.adminQuota,children:"查该用户配额"})]}),c.jsxs(Ht,{children:[c.jsx("button",{onClick:i.adminSweep,children:"手动触发 sweep(清理陈旧 running 任务)"}),c.jsx("button",{onClick:i.adminBackup,children:"手动触发备份"})]}),c.jsx("div",{className:"muted",children:i.adminResult})]}):null]})}function Bg(){const i=Kt(),[r,h]=d.useState(""),[f,p]=d.useState(""),[N,C]=d.useState("");return c.jsxs(Ft,{id:"nav-projects",title:"项目",children:[c.jsxs(Ht,{children:[c.jsxs("select",{"aria-label":"项目",value:i.projectId,onChange:j=>i.selectProject(j.target.value),children:[i.projects.length===0?c.jsx("option",{value:"",children:"(先加载项目)"}):null,i.projects.map(j=>c.jsxs("option",{value:j.id,children:[j.name," (",Ae(j.id),")"]},j.id))]}),c.jsx("button",{onClick:i.refreshProjects,children:"刷新"})]}),c.jsxs(Ht,{children:[c.jsx("input",{type:"text",placeholder:"新项目名",value:r,onChange:j=>h(j.target.value)}),c.jsxs("select",{"aria-label":"从 GitHub 仓库选",value:f,onChange:j=>{p(j.target.value),j.target.value&&C(j.target.value)},children:[c.jsx("option",{value:"",children:"(从 GitHub App 安装仓库里选,或手动输入)"}),i.githubRepos.map(j=>c.jsx("option",{value:j.clone_url,children:j.repo_full_name},j.clone_url))]}),c.jsx("input",{type:"text",placeholder:"repo_url(可选)",value:N,onChange:j=>C(j.target.value)}),c.jsx("button",{onClick:async()=>{await i.createProject(r,N),h(""),C(""),p("")},children:"新建项目"})]}),c.jsx("span",{className:"muted",children:i.projectMsg})]})}function qg(){const i=Kt(),[r,h]=d.useState(""),[f,p]=d.useState("contributor"),N=i.projects.find(C=>C.id===i.projectId);return c.jsxs(Ft,{title:"项目成员",children:[N?c.jsxs("div",{className:"muted",children:[N.name," | ",N.id," | 仓库 ",N.repo_url||"未绑定"," | 我的角色 ",N.my_role||"未知"]}):c.jsx("div",{className:"muted",children:"先选项目"}),c.jsxs(Ht,{children:[c.jsx("input",{type:"text",placeholder:"成员 user_id 或用户名",value:r,onChange:C=>h(C.target.value)}),c.jsxs("select",{"aria-label":"邀请角色",value:f,onChange:C=>p(C.target.value),children:[c.jsx("option",{value:"viewer",children:"viewer(只读)"}),c.jsx("option",{value:"contributor",children:"contributor(可建任务)"}),c.jsx("option",{value:"maintainer",children:"maintainer(可批合并)"}),c.jsx("option",{value:"owner",children:"owner(可管项目)"})]}),c.jsx("button",{onClick:async()=>{await i.inviteMember(r,f),h("")},children:"邀请成员"}),c.jsx("button",{onClick:i.refreshMembers,children:"刷新成员"})]}),i.memberError?c.jsx("div",{className:"muted",children:i.memberError}):i.members.length===0?c.jsx("div",{className:"muted",children:"先选项目"}):c.jsx(cu,{children:i.members.map(C=>{const j=(C.username||"")+(C.username?"("+String(C.user_id).slice(0,8)+")":String(C.user_id).slice(0,8));return c.jsxs("li",{children:[c.jsx("span",{className:"row-main",children:j}),c.jsx("select",{"aria-label":"成员角色",value:C.role||"viewer",onChange:z=>void i.changeMemberRole(C.user_id,z.target.value),children:Py.map(z=>c.jsx("option",{value:z,children:z},z))})]},C.user_id)})}),c.jsx("span",{className:"muted",children:i.memberMsg})]})}function Yg(){const i=Kt(),[r,h]=d.useState(""),[f,p]=d.useState(""),[N,C]=d.useState(""),j=ci(i.workers,i.workersSummary,i.workersError);return c.jsxs(Ft,{id:"nav-env",title:c.jsxs(c.Fragment,{children:["环境 ",c.jsx("span",{className:"muted",children:"(选、建、管:当前项目的基准检出)"})]}),children:[c.jsxs(Ht,{children:[c.jsx("button",{onClick:i.refreshEnvs,children:"刷新"}),c.jsx("span",{className:"muted",children:i.envCount})]}),i.envs.length===0?c.jsx("div",{className:"muted",children:"先选项目"}):c.jsx(cu,{children:i.envs.map(z=>c.jsxs("li",{children:[c.jsxs("span",{className:"row-main",children:[z.name,"(",Ae(z.id),") / 分支 ",z.branch||"默认分支"," / 执行器 ",z.executor||"不限"," / 状态 ",z.status,z.status==="failed"&&z.error_text?" / 失败原因:"+z.error_text:""]}),z.status==="archived"?null:c.jsx("button",{onClick:()=>void i.archiveEnv(z.id),children:"归档"})]},z.id))}),c.jsxs(Ht,{children:[c.jsx("input",{type:"text",placeholder:"新环境名",value:r,onChange:z=>h(z.target.value)}),c.jsx("input",{type:"text",placeholder:"分支(可选,空=默认分支)",value:f,onChange:z=>p(z.target.value)}),c.jsxs("select",{"aria-label":"新环境执行器",value:N,onChange:z=>C(z.target.value),children:[c.jsx("option",{value:"",children:"执行器:不限"}),c.jsx("option",{value:"local",children:"local"}),c.jsx("option",{value:"docker",children:"docker"}),c.jsx("option",{value:"cloud-hypervisor",children:"cloud-hypervisor(实验,默认关闭)"})]}),c.jsx("button",{onClick:async()=>{await i.createEnv(r,f,N),h(""),p("")},children:"新建环境"})]}),N==="cloud-hypervisor"?c.jsxs("div",{className:"muted",children:["实验执行器,默认关闭:环境创建白名单同样允许 cloud-hypervisor(后端环境接口 allowlist:local / docker / cloud-hypervisor,不含 cloud);仍需后端有 KVM 宿主上的 CHV worker 才会认领,否则任务排队。前端只传值,不绕过后端白名单。",j.state==="known"?c.jsxs(c.Fragment,{children:[" CHV worker 在线 ",j.online," / 共 ",j.total,"(来自 /workers)",j.online===0?":当前无 CHV worker 在线,任务将一直排队。":""]}):" CHV worker 在线数未知:完整 worker 清单仅全局管理员可见。"]}):null,c.jsx("span",{className:"muted",children:i.envMsg})]})}function Gg(){const i=Kt(),[r,h]=d.useState("");return c.jsxs(Ft,{title:"仓库与环境上下文",children:[c.jsx("div",{className:"muted",children:i.envRepoLine||"先选项目"}),c.jsx("div",{className:"muted",children:i.envCount}),i.envRepoBar?c.jsxs(Ht,{children:[c.jsxs("select",{"aria-label":"补仓库",value:r,onChange:f=>h(f.target.value),children:[c.jsx("option",{value:"",children:"(从 GitHub App 安装仓库里选)"}),i.githubRepos.map(f=>c.jsx("option",{value:f.clone_url,children:f.repo_full_name},f.clone_url))]}),c.jsx("button",{onClick:()=>void i.saveEnvRepo(r),children:"保存仓库"})]}):null,c.jsx("span",{className:"muted",children:i.envRepoMsg})]})}function Qg(){const i=Kt(),r=ci(i.workers,i.workersSummary,i.workersError);return c.jsxs(Ft,{id:"nav-workers",title:"Workers",children:[c.jsx(Ht,{children:c.jsx("button",{onClick:i.refreshWorkers,children:"刷新"})}),c.jsx("div",{className:"muted",children:r.state==="known"?c.jsxs(c.Fragment,{children:["cloud-hypervisor worker 在线 ",r.online," / 共 ",r.total,"(实验,默认关闭)",r.online===0?":当前无 CHV worker 在线,CHV 任务将一直排队。":""]}):"cloud-hypervisor worker 在线数未知:完整 worker 清单仅全局管理员可见,非管理员只能看到聚合数,无法按执行器分项。"}),i.workersError?c.jsx("div",{className:"muted",children:i.workersError}):i.workers===null?c.jsx("div",{className:"muted",children:i.workersSummary||"未加载"}):i.workers.length===0?c.jsx("div",{className:"muted",children:"无 worker 注册"}):c.jsx(cu,{children:i.workers.map((h,f)=>{const p=h.running===void 0||h.running===null?"?":h.running,N=h.capacity===void 0||h.capacity===null?"?":h.capacity,C=xo(h),j=h.alive!==void 0&&h.alive!==null?!!h.alive:C.age!==null&&C.age<=120,z=j?" / 在线":" / 离线(占用为上次上报值)";return c.jsx("li",{className:j?"is-online":"is-offline",children:c.jsxs("span",{className:"row-main",children:[h.worker_id||h.id||"?"," / ",h.executor||""," / 占用 ",p,"/",N,z," / 最后心跳 ",C.text]})},f)})})]})}function Vg(){const i=Kt();return c.jsxs(Ft,{title:"健康与配额",children:[c.jsxs(Ht,{children:[c.jsx("span",{className:"muted",children:i.healthText}),c.jsx("button",{onClick:i.refreshHealth,children:"刷新"})]}),c.jsx("div",{children:c.jsx("span",{className:"muted",children:i.quotaText})}),c.jsx("div",{className:"muted",children:i.workersSummary})]})}function Xg(){const i=Kt();return c.jsxs(Ft,{id:"nav-skills",title:"技能与 MCP",children:[c.jsx("div",{className:"muted",children:"反映宿主租户 home,容器内 home 可能不同;重载只在 schema 枚举里,当前 pin 的二进制拒绝时原样 502;OAuth 只发起登录,不代表登录完成。"}),c.jsxs(Ht,{children:[c.jsx("button",{onClick:i.refreshSkills,children:"刷新"}),c.jsx("span",{className:"muted",children:i.skillMsg})]}),c.jsx("h3",{children:"技能"}),i.skillError?c.jsx("div",{className:"muted",children:i.skillError}):c.jsx(bl,{empty:"暂无技能",items:i.skills.map((r,h)=>c.jsxs("span",{className:"row-main",children:[r.name||r.id||"",r.description?" - "+r.description:"",r.detail?" - "+r.detail:""]},h))}),c.jsx("h3",{children:"应用"}),c.jsx(bl,{empty:"暂无应用",items:i.apps.map((r,h)=>c.jsxs("span",{className:"row-main",children:[r.name||r.id||"",r.description?" - "+r.description:""]},h))}),c.jsx("h3",{children:"MCP 服务"}),c.jsx(bl,{empty:"暂无 MCP 服务",items:i.mcpServers.map((r,h)=>{const f=r.detail||r.details||r.state||"";return c.jsxs("span",{className:"row-main",children:[r.name||r.id||""," | 状态 ",r.status||"",f?" | "+f:""]},h)})})]})}function Lg(){const i=Kt(),[r,h]=d.useState("");return c.jsxs(Ft,{title:"MCP 操作",children:[c.jsxs(Ht,{children:[c.jsx("button",{onClick:i.reloadMcp,children:"重载配置"}),c.jsx("span",{className:"muted",children:i.mcpMsg})]}),c.jsxs(Ht,{children:[c.jsx("input",{type:"text",placeholder:"MCP 服务名",value:r,onChange:f=>h(f.target.value)}),c.jsx("button",{onClick:()=>void i.mcpOauth(r),children:"发起 OAuth 登录"}),c.jsx("span",{className:"muted",children:i.mcpOauthMsg})]}),i.mcpOauthBox?c.jsx("pre",{children:i.mcpOauthBox}):null]})}const Kh=160;function Zg(i){return i?i%86400===0?"(每天)":i%3600===0?"(每小时)":i%60===0?"(每 "+i/60+" 分钟)":"":""}function Kg(){const i=Kt(),[r,h]=d.useState(""),[f,p]=d.useState(""),[N,C]=d.useState(""),[j,z]=d.useState(""),[R,q]=d.useState(""),[g,U]=d.useState(""),[F,X]=d.useState(""),[J,V]=d.useState(""),[vt,St]=d.useState(""),[jt,K]=d.useState({}),Nt=w=>{q(w.id),U(w.name||""),X(w.prompt||""),V(w.interval_seconds?String(w.interval_seconds):""),St(w.repo_url||"")};return c.jsxs(c.Fragment,{children:[c.jsx("input",{placeholder:"自动化名称(同项目内启用中的名称唯一)",style:{width:"100%"},value:r,onChange:w=>h(w.target.value)}),c.jsx("textarea",{placeholder:"定时任务内容(创建只登记定义与到期状态;执行由后续执行器经 claim 抢单后建任务,不另起调度器)",value:f,onChange:w=>p(w.target.value)}),c.jsxs(Ht,{children:[c.jsx("input",{placeholder:"间隔秒数(默认 86400,最小 60)",style:{width:"40%"},value:N,onChange:w=>C(w.target.value)}),c.jsx("input",{placeholder:"仓库(可选)",style:{width:"40%"},value:j,onChange:w=>z(w.target.value)}),c.jsx("button",{className:"primary",onClick:async()=>{await i.createAutomation(r,f,N,j),h(""),p(""),C(""),z("")},children:"创建自动化"}),c.jsx("button",{onClick:i.refreshAutomations,children:"刷新自动化"}),c.jsx("span",{className:"muted",children:i.autoMsg})]}),c.jsx(bl,{empty:"(暂无自动化)",items:i.automations.map(w=>{const Q=w.prompt||"",nt=!!jt[w.id],Dt=Q.length>Kh,qt=Q?nt||!Dt?Q:Q.slice(0,Kh)+"…":"(无内容)";return c.jsxs("div",{children:[c.jsxs("div",{className:"row-main",children:["[",w.status,w.due?"|到期":"",w.run_once?"|一次性":"","] ",w.name,"|每 ",w.interval_seconds," 秒",Zg(w.interval_seconds),"|上次 ",Kl(w.last_run_at)||"未运行","|下次 ",Kl(w.next_run_at),w.status==="active"?c.jsxs(c.Fragment,{children:[" ",c.jsx("button",{onClick:()=>Nt(w),children:"编辑"})," ",c.jsx("button",{onClick:()=>i.cancelAutomation(w.id),children:"取消"})]}):null]}),w.repo_url?c.jsxs("div",{className:"muted",children:["仓库:",w.repo_url]}):null,w.status==="cancelled"&&w.cancelled_at?c.jsxs("div",{className:"muted",children:["已取消于 ",Kl(w.cancelled_at)]}):null,c.jsxs("div",{className:"muted",style:{whiteSpace:"pre-wrap",overflowWrap:"anywhere"},children:[qt,Dt?c.jsx("button",{onClick:()=>K(dt=>({...dt,[w.id]:!dt[w.id]})),children:nt?"收起":"展开"}):null]}),R===w.id?c.jsxs("div",{children:[c.jsx("input",{placeholder:"自动化名称",style:{width:"100%"},value:g,onChange:dt=>U(dt.target.value)}),c.jsx("textarea",{placeholder:"定时任务内容",value:F,onChange:dt=>X(dt.target.value)}),c.jsxs(Ht,{children:[c.jsx("input",{placeholder:"间隔秒数(最小 60,留空保持原值)",style:{width:"40%"},value:J,onChange:dt=>V(dt.target.value)}),c.jsx("input",{placeholder:"仓库(可选,清空则清除)",style:{width:"40%"},value:vt,onChange:dt=>St(dt.target.value)}),c.jsx("button",{className:"primary",onClick:async()=>{const dt=i.automations.find(H=>H.id===R)||w;await i.updateAutomation(dt,g,F,J,vt)&&q("")},children:"保存"}),c.jsx("button",{onClick:()=>q(""),children:"取消"})]})]}):null]},w.id)})})]})}function Jg(){var h,f,p,N;const i=Kt(),r=i.triage;return i.triageError?c.jsx("div",{className:"muted",children:i.triageError}):r?c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:"data-group",children:[c.jsxs("b",{children:["到期自动化(",((h=r.counts)==null?void 0:h.automations_due)||0,")"]}),c.jsx(bl,{empty:"(无)",items:(r.automations_due||[]).map((C,j)=>c.jsxs("span",{className:"row-main",children:[C.name,"|每 ",C.interval_seconds," 秒"]},j))})]}),c.jsxs("div",{className:"data-group",children:[c.jsxs("b",{children:["失败/中断任务(",((f=r.counts)==null?void 0:f.failed_tasks)||0,")"]}),c.jsx(bl,{empty:"(无)",items:(r.failed_tasks||[]).map((C,j)=>c.jsxs("span",{className:"row-main",children:[Ae(C.id),"|",C.status,"|",String(C.prompt||"").slice(0,80)]},j))})]}),c.jsxs("div",{className:"data-group",children:[c.jsxs("b",{children:["待处理后续消息(",((p=r.counts)==null?void 0:p.pending_followups)||0,")"]}),c.jsx(bl,{empty:"(无)",items:(r.pending_followups||[]).map((C,j)=>c.jsxs("span",{className:"row-main",children:["任务 ",Ae(C.task_id),"|",String(C.body||"").slice(0,80)]},j))})]}),c.jsxs("div",{className:"data-group",children:[c.jsxs("b",{children:["最近自动化产出(",((N=r.counts)==null?void 0:N.recent_runs)||0,")"]}),c.jsx(bl,{empty:"(无)",items:(r.recent_runs||[]).map((C,j)=>c.jsxs("span",{className:"row-main",children:[Ae(C.automation_id),"|",C.status,C.task_id?"|任务 "+Ae(C.task_id):"",C.task_status?"|"+C.task_status:""]},j))})]})]}):c.jsx("div",{className:"muted",children:"(先选项目)"})}function Fg(){return c.jsx(Ft,{id:"nav-automations",title:"自动化",children:c.jsx(Kg,{})})}function Wg(){const i=Kt();return c.jsxs(Ft,{id:"nav-triage",title:"分诊收件箱",children:[c.jsx(Ht,{children:c.jsx("button",{onClick:i.refreshTriage,children:"刷新分诊"})}),c.jsx(Jg,{})]})}const yo=["pending","in_progress","blocked","completed","cancelled"],Dc={pending:"待办",in_progress:"进行中",blocked:"受阻",completed:"已完成",cancelled:"已取消"},$g=["low","medium","high"],Jh={low:"低",medium:"中",high:"高"};function Ig(i){return i?new Date(i*1e3).toLocaleString():""}function Pg(){const i=Kt(),[r,h]=d.useState([]),[f,p]=d.useState({}),[N,C]=d.useState(0),[j,z]=d.useState(""),[R,q]=d.useState(""),[g,U]=d.useState(""),[F,X]=d.useState(""),[J,V]=d.useState(""),[vt,St]=d.useState(""),[jt,K]=d.useState("medium"),[Nt,w]=d.useState(""),[Q,nt]=d.useState(""),Dt=d.useCallback(async()=>{if(!i.projectId){h([]),p({}),C(0);return}try{let H=W.projectTodos(i.projectId);const ot=new URLSearchParams;g&&ot.set("status",g),F.trim()&&ot.set("q",F.trim());const yt=ot.toString();yt&&(H+="?"+yt);const G=await at(H);h(G.todos||[]),p(G.counts||{}),C(G.total||0),q("")}catch(H){q("待办加载失败:"+((H==null?void 0:H.message)||String(H)))}},[i.projectId,g,F]);d.useEffect(()=>{Dt()},[Dt]);const qt=async()=>{if(!i.projectId){z("先选项目");return}if(!J.trim()){z("标题必填");return}z("");try{const H={title:J.trim(),description:vt,priority:jt};Nt.trim()&&(H.task_id=Nt.trim()),Q&&(H.due_at=Math.floor(new Date(Q).getTime()/1e3)),await at(W.projectTodos(i.projectId),"POST",H),V(""),St(""),w(""),nt(""),z("已创建"),await Dt()}catch(H){z("创建失败:"+((H==null?void 0:H.message)||String(H)))}},dt=async(H,ot)=>{if(i.projectId)try{await at(W.projectTodo(i.projectId,H.id),"PATCH",ot),await Dt()}catch(yt){q("待办更新失败:"+((yt==null?void 0:yt.message)||String(yt)))}},kt=async H=>{if(i.projectId&&window.confirm("删除待办“"+H.title+"”?删除后不可恢复(取消请改状态为已取消)。"))try{await at(W.projectTodo(i.projectId,H.id),"DELETE"),await Dt()}catch(ot){q("待办删除失败:"+((ot==null?void 0:ot.message)||String(ot)))}};return c.jsxs(Ft,{id:"nav-todos",title:"项目待办",children:[c.jsx("div",{className:"muted",children:i.projectId?"共 "+N+" 项 | "+yo.map(H=>Dc[H]+" "+(f[H]||0)).join(" | "):"先选项目"}),R?c.jsx("div",{className:"err",children:R}):null,c.jsxs(Ht,{children:[c.jsxs("select",{"aria-label":"状态筛选",value:g,onChange:H=>U(H.target.value),children:[c.jsx("option",{value:"",children:"状态:全部"}),yo.map(H=>c.jsx("option",{value:H,children:Dc[H]},H))]}),c.jsx("input",{type:"text",placeholder:"搜索标题/说明",value:F,onChange:H=>X(H.target.value)}),c.jsx("button",{onClick:()=>void Dt(),children:"刷新"})]}),c.jsx("input",{type:"text",placeholder:"待办标题(必填)",style:{width:"100%"},value:J,onChange:H=>V(H.target.value)}),c.jsx("textarea",{placeholder:"说明(可选)",value:vt,onChange:H=>St(H.target.value)}),c.jsxs(Ht,{children:[c.jsx("select",{"aria-label":"优先级",value:jt,onChange:H=>K(H.target.value),children:$g.map(H=>c.jsxs("option",{value:H,children:["优先级:",Jh[H]]},H))}),c.jsx("input",{type:"text",placeholder:"关联任务 id(可选)",value:Nt,onChange:H=>w(H.target.value)}),c.jsx("input",{type:"datetime-local","aria-label":"截止时间",value:Q,onChange:H=>nt(H.target.value)}),c.jsx("button",{className:"primary",onClick:()=>void qt(),children:"新建待办"}),c.jsx("span",{className:"muted",children:j})]}),c.jsx(bl,{empty:i.projectId?"(暂无待办)":"(先选项目)",items:r.map(H=>c.jsxs("span",{className:"row-main",children:[c.jsx("b",{children:H.title})," | ",c.jsx("span",{className:"st-"+H.status,children:Dc[H.status]||H.status})," | 优先级 "+(Jh[H.priority]||H.priority),H.task_id?" | 任务 "+Ae(H.task_id):"",H.due_at?" | 截止 "+Ig(H.due_at):"",H.completed_at?" | 完成于 "+new Date(H.completed_at*1e3).toLocaleString():"",H.description?c.jsxs(c.Fragment,{children:[c.jsx("br",{}),c.jsx("span",{className:"muted",children:H.description})]}):null,c.jsx("br",{}),c.jsx("select",{"aria-label":"改状态",value:H.status,onChange:ot=>void dt(H,{status:ot.target.value}),children:yo.map(ot=>c.jsx("option",{value:ot,children:Dc[ot]},ot))})," ",c.jsx("button",{onClick:()=>void dt(H,{status:"completed"}),children:"完成"})," ",c.jsx("button",{onClick:()=>void kt(H),children:"删除"})]},H.id))}),c.jsx("div",{className:"muted",children:"状态与 Muse 待办同款五态;完成时服务端记完成时间,改回其它状态时清掉;已取消是软关闭,删除才是硬删除。"})]})}const bo=[["tasks","任务工作台","任务"],["todos","项目待办","任务"],["projects","项目与成员","管理"],["resources","环境与 Workers","管理"],["automation","技能与自动化","管理"],["admin","系统与 Key","管理"]],tp=Object.fromEntries(bo.map(([i,r])=>[i,r]));function Fh(){const i=window.location.hash.replace("#view-","");return bo.some(([r])=>r===i)?i:"tasks"}function ep({view:i,setView:r}){return c.jsxs("aside",{className:"shell-side",children:[c.jsx("div",{className:"shell-brand",children:"Codex 命令台"}),c.jsx("nav",{className:"shell-nav","aria-label":"导航",children:["任务","管理"].map(h=>c.jsxs("div",{className:"shell-nav-group",children:[c.jsx("div",{className:"shell-nav-group-label",children:h}),bo.filter(([,,f])=>f===h).map(([f,p])=>c.jsx("button",{type:"button",className:i===f?"active":"",onClick:()=>r(f),children:p},f))]},h))}),c.jsxs("div",{className:"shell-stats",children:[c.jsx("div",{className:"shell-stats-label",children:"概览"}),c.jsx(dg,{})]})]})}function lp(){const i=Kt();return c.jsx("span",{className:i.keySaved?"dash-status ok":"dash-status off",children:i.keySaved?"● 已连接":"○ 未连接"})}function ap(){const i=fg();sg(j=>i.refreshAll(j),1e4),rg(()=>i.refreshSlow(),6e4);const[r,h]=d.useState(()=>Fh()),[f,p]=d.useState(!0),N=j=>{h(j);try{window.history.replaceState(null,"","#view-"+j)}catch{}};d.useEffect(()=>{const j=()=>h(Fh());return window.addEventListener("hashchange",j),()=>window.removeEventListener("hashchange",j)},[]);const C=()=>{N("tasks"),p(!0),setTimeout(()=>{var j;return(j=document.getElementById("sec-dispatch"))==null?void 0:j.scrollIntoView({behavior:"smooth"})},0)};return c.jsx($h.Provider,{value:i,children:c.jsxs("div",{className:"shell",children:[c.jsx(ep,{view:r,setView:N}),c.jsxs("div",{className:"shell-main",children:[c.jsxs("header",{className:"shell-header",children:[c.jsx("h1",{children:tp[r]}),c.jsx(lp,{}),c.jsx("span",{className:"muted shell-hide-sm",children:i.healthText}),c.jsx("span",{className:"muted shell-hide-sm",children:i.quotaText}),c.jsxs("span",{className:"muted shell-stamp",children:["自动刷新 · 每 10 秒 · 已刷新 ",i.refreshStamp]})]}),r==="tasks"?c.jsxs("div",{className:"workspace",children:[c.jsxs("section",{className:"workspace-rail","aria-label":"任务列表",children:[c.jsx("h2",{children:"任务列表"}),c.jsx(gg,{onNew:C})]}),c.jsxs("section",{className:"workspace-center",children:[c.jsx("div",{className:"composer-toggle",children:c.jsx("button",{type:"button",onClick:()=>p(!f),children:f?"收起新建任务":"新建任务"})}),f?c.jsx("section",{id:"sec-dispatch",children:c.jsx(vg,{})}):null,c.jsxs("section",{className:"card workspace-conv",children:[c.jsx("h2",{children:"对话"}),c.jsx(Tg,{})]})]}),c.jsx("section",{className:"workspace-inspector","aria-label":"任务详情",children:c.jsx(wg,{})})]}):c.jsx("main",{className:"page",children:r==="todos"?c.jsx(c.Fragment,{children:c.jsx(Pg,{})}):r==="projects"?c.jsxs(c.Fragment,{children:[c.jsx(Bg,{}),c.jsx(qg,{})]}):r==="resources"?c.jsxs(c.Fragment,{children:[c.jsx(Yg,{}),c.jsx(Gg,{}),c.jsx(Qg,{}),c.jsx(Vg,{})]}):r==="automation"?c.jsxs(c.Fragment,{children:[c.jsx(Xg,{}),c.jsx(Lg,{}),c.jsx(Fg,{}),c.jsx(Wg,{})]}):c.jsxs(c.Fragment,{children:[c.jsx(Hg,{}),c.jsx(Ug,{})]})})]})]})})}Wy.createRoot(document.getElementById("root")).render(c.jsx(Qy.StrictMode,{children:c.jsx(ap,{})})); +`)}}j("流已断开")}catch(nt){if(nt.name==="AbortError")return;R(nt instanceof Error?nt.message:String(nt)),j("未开始")}finally{g(!1)}},[r]),Q=d.useCallback(()=>{var w;(w=U.current)==null||w.abort(),g(!1),j("已停止")},[]),vt=d.useCallback(async w=>{if(r)try{const V=await at(W.taskBrowserControl(r.id),"POST",{holder:w});N(V),R("")}catch(V){R(V instanceof Error?V.message:String(V))}},[r]),St=d.useCallback(async w=>{if(!r||(p==null?void 0:p.holder)!=="user"||!F.current)return;const V=F.current.getBoundingClientRect(),nt=Math.round((w.clientX-V.left)/V.width*1280),Dt=Math.round((w.clientY-V.top)/V.height*800);try{await at(W.taskBrowserInput(r.id),"POST",{type:"mouse_click",x:nt,y:Dt})}catch(qt){R(qt instanceof Error?qt.message:String(qt))}},[r,p]),jt=d.useCallback(async w=>{if(!(!r||(p==null?void 0:p.holder)!=="user")){w.preventDefault();try{w.key.length===1?await at(W.taskBrowserInput(r.id),"POST",{type:"type",text:w.key}):await at(W.taskBrowserInput(r.id),"POST",{type:"key",key:w.key})}catch(V){R(V instanceof Error?V.message:String(V))}}},[r,p]);if(!r)return c.jsx("div",{className:"muted",children:"点任务行查看详情"});const K=(p==null?void 0:p.holder)||"—",Nt=p!==null;return c.jsxs("div",{children:[c.jsxs("div",{style:{display:"flex",gap:8,flexWrap:"wrap",alignItems:"center"},children:[c.jsxs("span",{children:["状态 ",c.jsx("b",{children:C})]}),c.jsxs("span",{className:"muted",children:["会话 ",Nt?"活跃":"未激活(轮次结束即消失)"]}),c.jsxs("span",{className:"muted",children:["控制权 ",K]}),p!=null&&p.url?c.jsx("span",{className:"muted",title:p.url,children:p.url.slice(0,80)}):null]}),c.jsxs("div",{style:{display:"flex",gap:8,flexWrap:"wrap",marginTop:8},children:[q?c.jsx("button",{type:"button",onClick:Q,children:"停止观看"}):c.jsx("button",{type:"button",onClick:()=>void J(),children:"开始观看"}),c.jsx("button",{type:"button",onClick:()=>void X(),children:"刷新状态"}),K!=="user"?c.jsx("button",{type:"button",onClick:()=>void vt("user"),disabled:!Nt,children:"接管"}):c.jsx("button",{type:"button",onClick:()=>void vt("agent"),children:"交还给 Agent"})]}),z?c.jsx("div",{className:"muted",style:{marginTop:8},children:z}):null,K==="user"?c.jsx("div",{className:"muted",style:{marginTop:8},children:"你已接管:点画面即点击,键盘输入直达页面。交还后 Agent 续跑前会先快照确认你的改动。"}):null,c.jsx("div",{style:{marginTop:8},children:h?c.jsx("img",{ref:F,src:h,alt:"浏览器实时画面",tabIndex:0,onClick:w=>void St(w),onKeyDown:w=>void jt(w),style:{width:"100%",maxWidth:1280,cursor:K==="user"?"crosshair":"default",border:"1px solid var(--border, #ccc)"}}):c.jsx("div",{className:"muted",children:Nt?"点“开始观看”接入画面流(接管后点画面即点击)":"浏览器会话未激活:任务本轮未开浏览器,或轮次已结束。"})})]})}function Ng(){const i=Kt(),[r,h]=d.useState("uncommittedChanges"),[f,p]=d.useState(""),[N,C]=d.useState(""),[j,z]=d.useState("");return c.jsxs(c.Fragment,{children:[c.jsx("h3",{children:"评审队列"}),c.jsxs("select",{"aria-label":"评审目标",value:r,onChange:R=>h(R.target.value),children:[c.jsx("option",{value:"uncommittedChanges",children:"未提交改动(工作区)"}),c.jsx("option",{value:"baseBranch",children:"与分支的差异"}),c.jsx("option",{value:"commit",children:"指定提交"}),c.jsx("option",{value:"custom",children:"自定义评审指令"})]}),c.jsx("input",{className:"stacked",placeholder:"与分支的差异用:分支名(如 main)",style:{width:"100%"},value:f,onChange:R=>p(R.target.value)}),c.jsx("input",{className:"stacked",placeholder:"指定提交用:提交 sha",style:{width:"100%"},value:N,onChange:R=>C(R.target.value)}),c.jsx("textarea",{placeholder:"自定义评审指令用:评审指令正文",value:j,onChange:R=>z(R.target.value)}),c.jsxs(Ht,{children:[c.jsx("button",{className:"primary",onClick:()=>void i.postReview({target_type:r,branch:f.trim()||void 0,sha:N.trim()||void 0,instructions:j.trim()||void 0}),children:"登记评审需求"}),c.jsx("button",{onClick:i.refreshReviews,children:"刷新队列"}),c.jsx("span",{className:"muted",children:i.reviewMsg})]}),c.jsx("div",{className:"muted",children:"本基线只登记评审需求(requested),不自动跑评审;评审是否执行、结果如何,以评审队列里的记录为准。"}),i.reviewError?c.jsx("div",{className:"muted",children:i.reviewError}):i.reviews.length===0?c.jsx("div",{className:"muted",children:"点任务行查看详情后,点“刷新队列”查看评审记录"}):c.jsx(cu,{children:i.reviews.map(R=>{let q=R.target_type||"";return R.branch&&(q+=":"+R.branch),R.sha&&(q+=":"+String(R.sha).slice(0,12)),R.instructions&&(q+=":"+String(R.instructions).slice(0,80)),c.jsxs("li",{children:[c.jsxs("span",{className:"row-main",children:[c.jsx("b",{children:"评审"})," ",Ae(R.id)," | ",q," | ",c.jsx("span",{className:"st-"+(R.status||""),children:R.status}),R.created_at?" | "+Kl(R.created_at):"",c.jsx("br",{}),R.findings?String(R.findings).slice(0,1e3):c.jsx("span",{className:"muted",children:"(暂无 findings:评审尚未执行或尚未写回)"})]}),c.jsx("button",{onClick:()=>i.submitFindings(R.id),children:"写 findings"})]},R.id)})})]})}function Cg(){const i=Kt();return i.currentTask?i.currentTask.coordinator_parts?i.coordChildren.length===0?c.jsx("div",{className:"muted",children:"(子任务尚未创建)"}):c.jsx(cu,{children:i.coordChildren.map(r=>c.jsx("li",{children:c.jsxs("span",{className:"row-main",children:["分工 ",c.jsx("b",{children:r.coordinator_part||""})," | 状态 ",c.jsx("span",{className:"st-"+r.status,children:r.status})," | ",Ae(r.id)," | ",String(r.prompt||"").slice(0,60)]})},r.id))}):c.jsx("div",{className:"muted",children:"(非协调员任务)"}):c.jsx("div",{className:"muted",children:"点任务行查看详情后,这里显示协调员的子任务"})}function Ag(){const i=Kt();if(!i.currentTask)return c.jsx("div",{className:"muted",children:"点任务行查看详情后,这里显示子代理链路"});if(i.agents.length===0)return c.jsxs("div",{className:"muted",children:["(暂无子代理)",i.agentsNote?c.jsx("span",{className:"err",children:i.agentsNote}):null]});const r={main:"主任务",delegate:"委派子任务",model:"模型子代理"},h={running:"进行中",completed:"已完成",failed:"失败",interrupted:"已中断",unknown:"未知"};return c.jsx(cu,{children:i.agents.map((f,p)=>c.jsx("li",{children:c.jsxs("span",{className:"row-main",children:[c.jsxs("b",{children:[r[f.kind||""]||f.kind||"",":"]}),f.name||""," ",c.jsx("span",{className:"st-"+(f.status||""),children:h[f.status||""]||f.status||"?"})," | 过程调用 "+(f.calls?f.calls.length:0)+" 条",f.updated_ts?" | 最近活动 "+Kl(f.updated_ts/1e3):"",f.prompt?c.jsxs(c.Fragment,{children:[c.jsx("br",{}),c.jsxs("span",{className:"muted",children:["任务:",f.prompt]})]}):null,f.calls&&f.calls.length?c.jsxs(c.Fragment,{children:[c.jsx("br",{}),f.calls.slice(-50).map((N,C)=>c.jsx("div",{children:go(N)},C))]}):c.jsxs(c.Fragment,{children:[c.jsx("br",{}),c.jsx("span",{className:"muted",children:"(暂无过程调用)"})]})]})},p))})}function Mg(){const i=Kt();return c.jsxs(c.Fragment,{children:[c.jsxs(Ht,{children:[c.jsx("button",{onClick:i.refreshApprovals,children:"刷新审批队列"}),c.jsx("span",{className:"muted",children:i.approvalMsg})]}),c.jsx("div",{className:"muted",children:"待审批项需人工点记录允许或记录拒绝;超时默认拒绝。当前版本只留回执,不拦截实时行为(实时请求的事前拦截待 host 接入后生效,在此之前实时行为不变)。"}),c.jsx(bl,{empty:"(暂无审批项)",items:i.approvals.map(r=>c.jsxs("span",{className:"row-main",children:["[",r.kind,"|",r.status,"] ",r.summary,["approved","denied","expired"].includes(r.status||"")?"|回执:"+(r.decision||r.status)+" by "+String(r.decided_by||"").slice(0,8)+" "+Kl(r.decided_at):"",r.status==="pending"?c.jsxs(c.Fragment,{children:[" ",c.jsx("button",{onClick:()=>i.decideApproval(r.id,"approved"),children:"记录允许"})," ",c.jsx("button",{onClick:()=>i.decideApproval(r.id,"denied"),children:"记录拒绝"})]}):null]},r.id))})]})}function Og(){const i=Kt(),[r,h]=d.useState(""),[f,p]=d.useState("");return c.jsxs(c.Fragment,{children:[c.jsx("input",{placeholder:"目标任务 id(与本任务同项目;对方运行中排队执行、已终态则续跑复活,绝不打断当前轮)",style:{width:"100%"},value:r,onChange:N=>h(N.target.value)}),c.jsx("textarea",{placeholder:"消息正文(以本任务名义发出;对方 agent 会看到发送方任务 id,可照此回消息)",value:f,onChange:N=>p(N.target.value)}),c.jsxs(Ht,{children:[c.jsx("button",{onClick:async()=>{await i.postPeer("queued",r,f),p("")},children:"排队发给对方"}),c.jsx("button",{className:"primary",onClick:async()=>{await i.postPeer("direct",r,f),p("")},children:"直接发送"}),c.jsx("span",{className:"muted",children:i.peerMsg})]}),c.jsx(bl,{empty:"(本任务尚未发出任务间消息)",items:i.peerMessages.map((N,C)=>c.jsxs("span",{className:"row-main",children:["[发给 ",String(N.task_id).slice(0,8),"|",N.mode,"|",N.status,"] ",String(N.body||"").slice(0,120)]},C))})]})}function zg(){const i=Kt(),[r,h]=d.useState(""),f=i.currentTask?["completed","failed","interrupted"].includes(i.currentTask.status):!1;return c.jsxs(c.Fragment,{children:[c.jsx("div",{className:"muted",children:i.threadText||"点任务行查看详情后,这里显示线程元数据"}),c.jsxs(Ht,{children:[c.jsx("input",{type:"text",placeholder:"新线程名",value:r,onChange:p=>h(p.target.value)}),c.jsx("button",{disabled:!f,onClick:()=>i.threadAction("rename",r),children:"重命名"}),c.jsx("button",{disabled:!f,onClick:()=>i.threadAction("archive",""),children:"归档"}),c.jsx("button",{disabled:!f,onClick:()=>i.threadAction("unarchive",""),children:"取消归档"}),c.jsx("button",{disabled:!f,onClick:()=>i.threadAction("compact",""),children:"压缩"}),c.jsx("span",{className:"muted",children:i.threadGateNote}),c.jsx("span",{className:"muted",children:i.threadMsg})]}),c.jsx(bl,{empty:"暂无其他线程",items:i.threadList.map((p,N)=>c.jsxs("span",{className:"row-main",children:[p.name||p.thread_name||p.id||""," [",p.id||"","]",p.archived?" [已归档]":"",p.cwd?" "+p.cwd:""]},N))}),c.jsx("div",{className:"muted",children:"限制:Docker/异机任务线程操作一律 409;merged/merge-failed 不可改;turn/steer 与 review/start 不在本批。"})]})}function Rg(i){return/503|Service Unavailable/.test(i)?"上游模型服务暂时不可用(503):模型一轮都没跑出来,不是任务逻辑失败;等上游恢复或换模型重试":/429|rate limit/i.test(i)?"上游限流(429):模型拒绝了本次调用,可稍后重试":/timeout|timed out/i.test(i)?"上游调用超时:模型响应未在限时内返回":/401|403|Invalid API key|unauthorized/i.test(i)?"上游鉴权失败:网关 key 或权限有问题":/ECONNREFUSED|ENOTFOUND|network|fetch failed/i.test(i)?"连不上上游模型服务:网络或网关地址有问题":"任务失败(原因见原始错误文本)"}function Dg(i){return i===null||i<0?"未知":i<60?Math.floor(i)+" 秒前":i<3600?Math.floor(i/60)+" 分钟前":(i/3600).toFixed(1)+" 小时前"}const kg=[["overview","概览"],["collab","协作"],["review","评审与审批"],["threads","线程"],["browser","浏览器"]];function wg(){const i=Kt(),r=i.currentTask,[h,f]=d.useState("overview");if(!r)return c.jsx(Ft,{id:"nav-detail",title:"任务详情",children:c.jsx("div",{className:"muted",children:"点任务行查看详情"})});const[p,N]=Ph(r),[C,j]=tv(r),z=ci(i.workers,i.workersSummary,i.workersError),R=kc(r.executor,r.status,z),q=Ih(r),g=i.costSummary,U=r.plan?"计划模式(只读)":r.goal_mode?r.no_time_limit?"goal 模式无敌版(不限时)":"goal 模式(不限轮次,模型宣告完成)":r.verify_command?"goal 模式(旧语义,验收命令)":null;return c.jsxs("div",{className:"detail-stack",id:"nav-detail",children:[c.jsx("nav",{className:"detail-tabs","aria-label":"任务详情页签",children:kg.map(([F,X])=>c.jsx("button",{type:"button",className:h===F?"active":"",onClick:()=>f(F),children:X},F))}),h==="overview"?c.jsxs(c.Fragment,{children:[c.jsxs(Ft,{title:"任务详情",children:[c.jsxs("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"},children:[c.jsx("span",{className:"issue-first",title:"任务首字:"+q,"aria-hidden":"true",children:q}),c.jsxs("span",{children:["状态 ",c.jsx("b",{className:"st-"+r.status,children:r.status}),"(",Iy[r.status]||"未知状态",")"]}),c.jsxs("span",{className:"muted",children:["更新 ",Kl(r.updated_at)]})]}),c.jsxs("div",{className:"detail-cards",children:[c.jsxs("div",{className:"detail-card",title:N,children:[c.jsx("div",{className:"detail-card-label",children:"耗时"}),c.jsx("div",{className:"detail-card-value",children:p})]}),c.jsxs("div",{className:"detail-card",title:j,children:[c.jsx("div",{className:"detail-card-label",children:"token"}),c.jsx("div",{className:"detail-card-value",children:C})]}),c.jsxs("div",{className:"detail-card",title:"LLM 请求数 = GET /tasks/{id}/cost 的 llm_requests,即 cost_records 的 completions 之和:host.py 每收到一次 thread/tokenUsage/updated(一次上游补全)计 1,worker 每轮终局记账时累加;估算行(无真实用量)计 0;部署前已记账的老任务没有 completions 列,显示 0 而非回填数",children:[c.jsx("div",{className:"detail-card-label",children:"LLM 请求数"}),c.jsx("div",{className:"detail-card-value",children:g===void 0?"加载中…":g===null?"—":(g.llm_requests??0).toLocaleString()}),g===void 0?c.jsx("div",{className:"muted",children:"成本加载中…"}):g===null?c.jsx("div",{className:"muted",children:"成本明细加载失败,LLM 请求数不可用"}):c.jsxs("div",{className:"muted",children:["上游补全次数之和| 轮数 ",g.turns??0," | fallback ",g.fallback_count??0]})]}),c.jsxs("div",{className:"detail-card",title:"成本明细:GET /tasks/{id}/cost 聚合;美元只计有价行,无价 token 单列;缓存是输入 token 的子集,不另计",children:[c.jsx("div",{className:"detail-card-label",children:"成本"}),c.jsx("div",{className:"detail-card-value",children:g===void 0?"加载中…":g===null?"—":yg(g.usd_micros)}),g===void 0?c.jsx("div",{className:"muted",children:"成本加载中…"}):g===null?c.jsx("div",{className:"muted",children:"成本明细加载失败(“查成本”按钮仍可手动拉取原始 JSON)"}):c.jsxs("div",{className:"muted",children:["token ",(g.total_tokens??0).toLocaleString(),"(实际 ",(g.actual_tokens??0).toLocaleString()," / 估算 ",(g.estimated_tokens??0).toLocaleString(),",缓存 ",(g.cached_input_tokens??0).toLocaleString(),",无价 ",(g.unpriced_actual_tokens??0).toLocaleString(),")| fallback ",g.fallback_count??0]})]})]})]}),r.status==="running"||r.status==="queued"?(()=>{const F=Date.now()/1e3,X=r.last_heartbeat||r.updated_at||null,J=X?F-X:null;return c.jsxs(Ft,{title:"LLM 进度",children:[c.jsxs("div",{children:["状态 ",r.status==="running"?"进行中":"排队中"," · 已用 ",p,J!==null?" · 上次活动 "+Dg(J):""]}),R?c.jsx("div",{className:"muted",children:R}):null,J!==null&&J>180?c.jsx("div",{className:"muted",children:"超过 3 分钟无活动:可能在等上游模型响应(上游 503 时 worker 会重试后失败,失败后这里会显示失败原因;无活动不等于已断连,服务端没有“LLM 已连接”字段,不作此断言)"}):c.jsx("div",{className:"muted",children:"首轮无输出时属正常:模型第一轮回复到达前 trace 里没有消息;回复到达后会在会话区出现,成本里的 LLM 请求数随之增加"})]})})():null,r.error_text?c.jsxs(Ft,{title:"失败原因",children:[c.jsx("div",{children:Rg(r.error_text)}),c.jsx("pre",{style:{whiteSpace:"pre-wrap",overflowWrap:"anywhere"},children:r.error_text.slice(0,1e3)}),c.jsxs("div",{className:"muted",children:["完整错误文本仍在下方“操作与结果”里;模型 ",r.model||"默认"," · executor ",r.executor||""]})]}):null,c.jsx(Ft,{title:"基本信息",children:c.jsxs("dl",{className:"detail-kv",children:[c.jsxs("div",{children:[c.jsx("dt",{children:"任务"}),c.jsx("dd",{children:r.id})]}),c.jsxs("div",{children:[c.jsx("dt",{children:"turn"}),c.jsx("dd",{children:Ae(r.turn_id)})]}),c.jsxs("div",{children:[c.jsx("dt",{children:"root turn"}),c.jsx("dd",{children:Ae(r.root_turn_id)})]}),c.jsxs("div",{children:[c.jsx("dt",{children:"executor"}),c.jsxs("dd",{children:[r.executor||"",r.executor==="cloud-hypervisor"?"(实验执行器,默认关闭;仅展示,无 VM 控制按钮)":""]})]}),r.chv_vm_dead?c.jsxs("div",{children:[c.jsx("dt",{children:"VM 状态"}),c.jsx("dd",{children:"VM 中途死亡,任务未完成、断点可续跑(后端只读字段 chv_vm_dead;无该字段时不代表 VM 活着)"})]}):null,c.jsxs("div",{children:[c.jsx("dt",{children:"主机"}),c.jsx("dd",{children:r.worktree_host||""})]}),c.jsxs("div",{children:[c.jsx("dt",{children:"模型"}),c.jsx("dd",{children:r.model||"默认"})]}),c.jsxs("div",{children:[c.jsx("dt",{children:"强度"}),c.jsx("dd",{children:r.effort||"默认"})]}),r.child_model||r.child_effort?c.jsxs("div",{children:[c.jsx("dt",{children:"子任务默认"}),c.jsx("dd",{children:(r.child_model||"跟随本任务")+" / "+(r.child_effort||"跟随本任务")})]}):null,c.jsxs("div",{children:[c.jsx("dt",{children:"权限"}),c.jsx("dd",{children:r.approval||"默认"})]}),c.jsxs("div",{children:[c.jsx("dt",{children:"项目"}),c.jsx("dd",{children:Ae(r.project_id)})]}),U?c.jsxs("div",{children:[c.jsx("dt",{children:"模式"}),c.jsx("dd",{children:U})]}):null,r.forked_from?c.jsxs("div",{children:[c.jsx("dt",{children:"分支自"}),c.jsx("dd",{children:Ae(r.forked_from)})]}):null,r.coordinator_parts?c.jsxs("div",{children:[c.jsx("dt",{children:"协调员"}),c.jsxs("dd",{children:[r.coordinator_parts.length," 个子任务(",r.coordinator_parts.map(F=>F.name).join("、"),")"]})]}):null,r.parent_task_id?c.jsxs("div",{children:[c.jsx("dt",{children:"协调子任务"}),c.jsxs("dd",{children:["父任务 ",Ae(r.parent_task_id),r.coordinator_part?"("+r.coordinator_part+")":""]})]}):null,r.attachments&&r.attachments.length?c.jsxs("div",{children:[c.jsx("dt",{children:"附件"}),c.jsx("dd",{children:r.attachments.join("、")})]}):null,r.pr_url?c.jsxs("div",{children:[c.jsx("dt",{children:"PR"}),c.jsx("dd",{children:c.jsxs("a",{href:r.pr_url,target:"_blank",rel:"noopener noreferrer",children:["PR #",r.pr_number]})})]}):null]})}),c.jsx(Ft,{title:"prompt",children:c.jsx("div",{style:{whiteSpace:"pre-wrap",overflowWrap:"anywhere"},children:r.prompt||"(无 prompt)"})}),c.jsxs(Ft,{title:"操作与结果",children:[c.jsx(_g,{}),c.jsx("pre",{children:i.detailBody||"(暂无 result/error 输出)"}),i.costBox?c.jsx("pre",{children:i.costBox}):null]})]}):h==="collab"?c.jsxs(c.Fragment,{children:[c.jsx(Ft,{title:"协调子任务",children:c.jsx(Cg,{})}),c.jsx(Ft,{title:"子代理链路",children:c.jsx(Ag,{})}),c.jsx(Ft,{title:"给其他任务发消息",children:c.jsx(Og,{})})]}):h==="review"?c.jsxs(c.Fragment,{children:[c.jsx(Ft,{title:"评审队列",children:c.jsx(Ng,{})}),c.jsx(Ft,{title:"运行时审批队列",children:c.jsx(Mg,{})})]}):h==="browser"?c.jsx(c.Fragment,{children:c.jsx(Ft,{title:"浏览器远控",children:c.jsx(Eg,{})})}):c.jsx(c.Fragment,{children:c.jsx(Ft,{title:"线程",children:c.jsx(zg,{})})})]})}function Ug(){const i=Kt(),[r,h]=d.useState(""),[f,p]=d.useState(""),[N,C]=d.useState([]),[j,z]=d.useState(null),[R,q]=d.useState(!1),[g,U]=d.useState(""),[F,X]=d.useState(""),J=d.useCallback(async()=>{if(!i.keySaved){C([]);return}try{const K=await at(W.userKeys);C(K.keys||[]),X("")}catch(K){X(K instanceof Error?K.message:"加载 key 列表失败")}},[i.keySaved]);d.useEffect(()=>{J()},[J]);const Q=async()=>{U(""),X(""),q(!1);try{const K=await at(W.userKeys,"POST",{label:f.trim()||"api-key"});z(K),p(""),U("新 key 已生成,只显示一次:复制后请妥善保存。"),await J()}catch(K){X(K instanceof Error?K.message:"生成 key 失败")}},vt=async()=>{if(j)try{await navigator.clipboard.writeText(j.api_key),q(!0)}catch{X("复制失败:请手动选中上方 key 文本复制")}},St=()=>{j&&(i.saveKeyInput(j.api_key),z(null),U("已存为当前浏览器 key(原有 key 未被撤销,需要时可单独撤销)。"))},jt=async K=>{const Nt=(()=>{try{return sl().slice(-4)}catch{return""}})(),w=K.id==="legacy"?"(主 key:撤销后当前浏览器保存的 key 也可能随之失效,需重新登录拿新 key)":K.hint&&K.hint===Nt?"(这就是本浏览器当前在用的 key,撤销后本页会立刻未连接)":"";if(window.confirm(`撤销 key「${K.label}」(后 4 位 ${K.hint||"无"})吗?${w}撤销不可恢复。`)){U(""),X("");try{await at(W.userKey(K.id),"DELETE"),U(`已撤销 key「${K.label}」。`),await J()}catch(V){X(V instanceof Error?V.message:"撤销失败")}}};return c.jsxs(Ft,{id:"nav-key",title:"API Key",children:[c.jsxs(Ht,{children:[c.jsx("input",{type:"password",placeholder:"粘贴你的 X-API-Key(与调 API 用的同一个)",autoComplete:"off",value:r,onChange:K=>h(K.target.value)}),c.jsx("button",{className:"primary",onClick:()=>{i.saveKeyInput(r),h("")},children:"保存到本浏览器"}),c.jsx("button",{onClick:i.clearKeyInput,children:"清除"}),c.jsx("span",{className:"muted",children:i.keyState})]}),c.jsx("div",{children:c.jsx("span",{className:"muted",children:i.quotaText})}),!i.keySaved&&c.jsx("div",{children:c.jsx("span",{className:"muted",children:"先粘贴并保存已有 key,再生成或管理 key(生成接口本身也要用已有 key 鉴权)。"})}),i.keySaved&&c.jsxs(c.Fragment,{children:[c.jsxs(Ht,{children:[c.jsx("input",{placeholder:"新 key 备注(可选,默认 api-key,如 harness2-temp)",autoComplete:"off",value:f,onChange:K=>p(K.target.value)}),c.jsx("button",{className:"primary",onClick:()=>void Q(),children:"生成新 key"}),c.jsx("button",{onClick:()=>void J(),children:"刷新列表"})]}),j&&c.jsxs("div",{children:[c.jsx("div",{children:c.jsxs("span",{className:"muted",children:["新 key(",j.label,",后 4 位 ",j.hint,"):只显示一次,关闭后无法再查看。"]})}),c.jsxs(Ht,{children:[c.jsx("input",{type:"text",readOnly:!0,value:j.api_key,onFocus:K=>K.target.select()}),c.jsx("button",{className:"primary",onClick:()=>void vt(),children:R?"已复制":"复制"}),c.jsx("button",{onClick:St,children:"存为当前浏览器 key"}),c.jsx("button",{onClick:()=>z(null),children:"关闭(不再显示)"})]})]}),g&&c.jsx("div",{children:c.jsx("span",{className:"muted",children:g})}),F&&c.jsx("div",{children:c.jsx("span",{children:F})}),c.jsxs("div",{className:"api-key-list",children:[N.length===0&&c.jsx("span",{className:"muted",children:"暂无 key(或列表加载失败,见上方报错)。"}),N.map(K=>c.jsxs("div",{children:[c.jsx("span",{children:K.label})," ",c.jsxs("span",{className:"muted",children:["后 4 位 ",K.hint||"无"," · 创建 ",Kl(K.created_at)," · 最近使用 ",K.last_used_at?Kl(K.last_used_at):"未使用过"]})," ",K.revoked_at?c.jsxs("span",{className:"muted",children:["已撤销(",Kl(K.revoked_at),")"]}):c.jsx("button",{onClick:()=>void jt(K),children:"撤销"})]},K.id))]})]})]})}function Hg(){const i=Kt();return c.jsxs(Ft,{id:"adminCard",title:c.jsxs(c.Fragment,{children:["管理区 ",c.jsx("span",{className:"muted",children:"(仅全局管理员)"})]}),children:[c.jsx("div",{className:"muted",children:i.adminGate}),i.adminBody?c.jsxs(c.Fragment,{children:[c.jsxs(Ht,{children:[c.jsx("input",{type:"text",placeholder:"要查配额的用户 id",value:i.adminUserId,onChange:r=>i.setAdminUserId(r.target.value)}),c.jsx("button",{onClick:i.adminQuota,children:"查该用户配额"})]}),c.jsxs(Ht,{children:[c.jsx("button",{onClick:i.adminSweep,children:"手动触发 sweep(清理陈旧 running 任务)"}),c.jsx("button",{onClick:i.adminBackup,children:"手动触发备份"})]}),c.jsx("div",{className:"muted",children:i.adminResult})]}):null]})}function Bg(){const i=Kt(),[r,h]=d.useState(""),[f,p]=d.useState(""),[N,C]=d.useState("");return c.jsxs(Ft,{id:"nav-projects",title:"项目",children:[c.jsxs(Ht,{children:[c.jsxs("select",{"aria-label":"项目",value:i.projectId,onChange:j=>i.selectProject(j.target.value),children:[i.projects.length===0?c.jsx("option",{value:"",children:"(先加载项目)"}):null,i.projects.map(j=>c.jsxs("option",{value:j.id,children:[j.name," (",Ae(j.id),")"]},j.id))]}),c.jsx("button",{onClick:i.refreshProjects,children:"刷新"})]}),c.jsxs(Ht,{children:[c.jsx("input",{type:"text",placeholder:"新项目名",value:r,onChange:j=>h(j.target.value)}),c.jsxs("select",{"aria-label":"从 GitHub 仓库选",value:f,onChange:j=>{p(j.target.value),j.target.value&&C(j.target.value)},children:[c.jsx("option",{value:"",children:"(从 GitHub App 安装仓库里选,或手动输入)"}),i.githubRepos.map(j=>c.jsx("option",{value:j.clone_url,children:j.repo_full_name},j.clone_url))]}),c.jsx("input",{type:"text",placeholder:"repo_url(可选)",value:N,onChange:j=>C(j.target.value)}),c.jsx("button",{onClick:async()=>{await i.createProject(r,N),h(""),C(""),p("")},children:"新建项目"})]}),c.jsx("span",{className:"muted",children:i.projectMsg})]})}function qg(){const i=Kt(),[r,h]=d.useState(""),[f,p]=d.useState("contributor"),N=i.projects.find(C=>C.id===i.projectId);return c.jsxs(Ft,{title:"项目成员",children:[N?c.jsxs("div",{className:"muted",children:[N.name," | ",N.id," | 仓库 ",N.repo_url||"未绑定"," | 我的角色 ",N.my_role||"未知"]}):c.jsx("div",{className:"muted",children:"先选项目"}),c.jsxs(Ht,{children:[c.jsx("input",{type:"text",placeholder:"成员 user_id 或用户名",value:r,onChange:C=>h(C.target.value)}),c.jsxs("select",{"aria-label":"邀请角色",value:f,onChange:C=>p(C.target.value),children:[c.jsx("option",{value:"viewer",children:"viewer(只读)"}),c.jsx("option",{value:"contributor",children:"contributor(可建任务)"}),c.jsx("option",{value:"maintainer",children:"maintainer(可批合并)"}),c.jsx("option",{value:"owner",children:"owner(可管项目)"})]}),c.jsx("button",{onClick:async()=>{await i.inviteMember(r,f),h("")},children:"邀请成员"}),c.jsx("button",{onClick:i.refreshMembers,children:"刷新成员"})]}),i.memberError?c.jsx("div",{className:"muted",children:i.memberError}):i.members.length===0?c.jsx("div",{className:"muted",children:"先选项目"}):c.jsx(cu,{children:i.members.map(C=>{const j=(C.username||"")+(C.username?"("+String(C.user_id).slice(0,8)+")":String(C.user_id).slice(0,8));return c.jsxs("li",{children:[c.jsx("span",{className:"row-main",children:j}),c.jsx("select",{"aria-label":"成员角色",value:C.role||"viewer",onChange:z=>void i.changeMemberRole(C.user_id,z.target.value),children:Py.map(z=>c.jsx("option",{value:z,children:z},z))})]},C.user_id)})}),c.jsx("span",{className:"muted",children:i.memberMsg})]})}function Yg(){const i=Kt(),[r,h]=d.useState(""),[f,p]=d.useState(""),[N,C]=d.useState(""),j=ci(i.workers,i.workersSummary,i.workersError);return c.jsxs(Ft,{id:"nav-env",title:c.jsxs(c.Fragment,{children:["环境 ",c.jsx("span",{className:"muted",children:"(选、建、管:当前项目的基准检出)"})]}),children:[c.jsxs(Ht,{children:[c.jsx("button",{onClick:i.refreshEnvs,children:"刷新"}),c.jsx("span",{className:"muted",children:i.envCount})]}),i.envs.length===0?c.jsx("div",{className:"muted",children:"先选项目"}):c.jsx(cu,{children:i.envs.map(z=>c.jsxs("li",{children:[c.jsxs("span",{className:"row-main",children:[z.name,"(",Ae(z.id),") / 分支 ",z.branch||"默认分支"," / 执行器 ",z.executor||"不限"," / 状态 ",z.status,z.status==="failed"&&z.error_text?" / 失败原因:"+z.error_text:""]}),z.status==="archived"?null:c.jsx("button",{onClick:()=>void i.archiveEnv(z.id),children:"归档"})]},z.id))}),c.jsxs(Ht,{children:[c.jsx("input",{type:"text",placeholder:"新环境名",value:r,onChange:z=>h(z.target.value)}),c.jsx("input",{type:"text",placeholder:"分支(可选,空=默认分支)",value:f,onChange:z=>p(z.target.value)}),c.jsxs("select",{"aria-label":"新环境执行器",value:N,onChange:z=>C(z.target.value),children:[c.jsx("option",{value:"",children:"执行器:不限"}),c.jsx("option",{value:"local",children:"local"}),c.jsx("option",{value:"docker",children:"docker"}),c.jsx("option",{value:"cloud-hypervisor",children:"cloud-hypervisor(实验,默认关闭)"})]}),c.jsx("button",{onClick:async()=>{await i.createEnv(r,f,N),h(""),p("")},children:"新建环境"})]}),N==="cloud-hypervisor"?c.jsxs("div",{className:"muted",children:["实验执行器,默认关闭:环境创建白名单同样允许 cloud-hypervisor(后端环境接口 allowlist:local / docker / cloud-hypervisor,不含 cloud);仍需后端有 KVM 宿主上的 CHV worker 才会认领,否则任务排队。前端只传值,不绕过后端白名单。",j.state==="known"?c.jsxs(c.Fragment,{children:[" CHV worker 在线 ",j.online," / 共 ",j.total,"(来自 /workers)",j.online===0?":当前无 CHV worker 在线,任务将一直排队。":""]}):" CHV worker 在线数未知:完整 worker 清单仅全局管理员可见。"]}):null,c.jsx("span",{className:"muted",children:i.envMsg})]})}function Gg(){const i=Kt(),[r,h]=d.useState("");return c.jsxs(Ft,{title:"仓库与环境上下文",children:[c.jsx("div",{className:"muted",children:i.envRepoLine||"先选项目"}),c.jsx("div",{className:"muted",children:i.envCount}),i.envRepoBar?c.jsxs(Ht,{children:[c.jsxs("select",{"aria-label":"补仓库",value:r,onChange:f=>h(f.target.value),children:[c.jsx("option",{value:"",children:"(从 GitHub App 安装仓库里选)"}),i.githubRepos.map(f=>c.jsx("option",{value:f.clone_url,children:f.repo_full_name},f.clone_url))]}),c.jsx("button",{onClick:()=>void i.saveEnvRepo(r),children:"保存仓库"})]}):null,c.jsx("span",{className:"muted",children:i.envRepoMsg})]})}function Vg(){const i=Kt(),r=ci(i.workers,i.workersSummary,i.workersError);return c.jsxs(Ft,{id:"nav-workers",title:"Workers",children:[c.jsx(Ht,{children:c.jsx("button",{onClick:i.refreshWorkers,children:"刷新"})}),c.jsx("div",{className:"muted",children:r.state==="known"?c.jsxs(c.Fragment,{children:["cloud-hypervisor worker 在线 ",r.online," / 共 ",r.total,"(实验,默认关闭)",r.online===0?":当前无 CHV worker 在线,CHV 任务将一直排队。":""]}):"cloud-hypervisor worker 在线数未知:完整 worker 清单仅全局管理员可见,非管理员只能看到聚合数,无法按执行器分项。"}),i.workersError?c.jsx("div",{className:"muted",children:i.workersError}):i.workers===null?c.jsx("div",{className:"muted",children:i.workersSummary||"未加载"}):i.workers.length===0?c.jsx("div",{className:"muted",children:"无 worker 注册"}):c.jsx(cu,{children:i.workers.map((h,f)=>{const p=h.running===void 0||h.running===null?"?":h.running,N=h.capacity===void 0||h.capacity===null?"?":h.capacity,C=xo(h),j=h.alive!==void 0&&h.alive!==null?!!h.alive:C.age!==null&&C.age<=120,z=j?" / 在线":" / 离线(占用为上次上报值)";return c.jsx("li",{className:j?"is-online":"is-offline",children:c.jsxs("span",{className:"row-main",children:[h.worker_id||h.id||"?"," / ",h.executor||""," / 占用 ",p,"/",N,z," / 最后心跳 ",C.text]})},f)})})]})}function Qg(){const i=Kt();return c.jsxs(Ft,{title:"健康与配额",children:[c.jsxs(Ht,{children:[c.jsx("span",{className:"muted",children:i.healthText}),c.jsx("button",{onClick:i.refreshHealth,children:"刷新"})]}),c.jsx("div",{children:c.jsx("span",{className:"muted",children:i.quotaText})}),c.jsx("div",{className:"muted",children:i.workersSummary})]})}function Xg(){const i=Kt();return c.jsxs(Ft,{id:"nav-skills",title:"技能与 MCP",children:[c.jsx("div",{className:"muted",children:"反映宿主租户 home,容器内 home 可能不同;重载只在 schema 枚举里,当前 pin 的二进制拒绝时原样 502;OAuth 只发起登录,不代表登录完成。"}),c.jsxs(Ht,{children:[c.jsx("button",{onClick:i.refreshSkills,children:"刷新"}),c.jsx("span",{className:"muted",children:i.skillMsg})]}),c.jsx("h3",{children:"技能"}),i.skillError?c.jsx("div",{className:"muted",children:i.skillError}):c.jsx(bl,{empty:"暂无技能",items:i.skills.map((r,h)=>c.jsxs("span",{className:"row-main",children:[r.name||r.id||"",r.description?" - "+r.description:"",r.detail?" - "+r.detail:""]},h))}),c.jsx("h3",{children:"应用"}),c.jsx(bl,{empty:"暂无应用",items:i.apps.map((r,h)=>c.jsxs("span",{className:"row-main",children:[r.name||r.id||"",r.description?" - "+r.description:""]},h))}),c.jsx("h3",{children:"MCP 服务"}),c.jsx(bl,{empty:"暂无 MCP 服务",items:i.mcpServers.map((r,h)=>{const f=r.detail||r.details||r.state||"";return c.jsxs("span",{className:"row-main",children:[r.name||r.id||""," | 状态 ",r.status||"",f?" | "+f:""]},h)})})]})}function Lg(){const i=Kt(),[r,h]=d.useState("");return c.jsxs(Ft,{title:"MCP 操作",children:[c.jsxs(Ht,{children:[c.jsx("button",{onClick:i.reloadMcp,children:"重载配置"}),c.jsx("span",{className:"muted",children:i.mcpMsg})]}),c.jsxs(Ht,{children:[c.jsx("input",{type:"text",placeholder:"MCP 服务名",value:r,onChange:f=>h(f.target.value)}),c.jsx("button",{onClick:()=>void i.mcpOauth(r),children:"发起 OAuth 登录"}),c.jsx("span",{className:"muted",children:i.mcpOauthMsg})]}),i.mcpOauthBox?c.jsx("pre",{children:i.mcpOauthBox}):null]})}const Kh=160;function Zg(i){return i?i%86400===0?"(每天)":i%3600===0?"(每小时)":i%60===0?"(每 "+i/60+" 分钟)":"":""}function Kg(){const i=Kt(),[r,h]=d.useState(""),[f,p]=d.useState(""),[N,C]=d.useState(""),[j,z]=d.useState(""),[R,q]=d.useState(""),[g,U]=d.useState(""),[F,X]=d.useState(""),[J,Q]=d.useState(""),[vt,St]=d.useState(""),[jt,K]=d.useState({}),Nt=w=>{q(w.id),U(w.name||""),X(w.prompt||""),Q(w.interval_seconds?String(w.interval_seconds):""),St(w.repo_url||"")};return c.jsxs(c.Fragment,{children:[c.jsx("input",{placeholder:"自动化名称(同项目内启用中的名称唯一)",style:{width:"100%"},value:r,onChange:w=>h(w.target.value)}),c.jsx("textarea",{placeholder:"定时任务内容(创建只登记定义与到期状态;执行由后续执行器经 claim 抢单后建任务,不另起调度器)",value:f,onChange:w=>p(w.target.value)}),c.jsxs(Ht,{children:[c.jsx("input",{placeholder:"间隔秒数(默认 86400,最小 60)",style:{width:"40%"},value:N,onChange:w=>C(w.target.value)}),c.jsx("input",{placeholder:"仓库(可选)",style:{width:"40%"},value:j,onChange:w=>z(w.target.value)}),c.jsx("button",{className:"primary",onClick:async()=>{await i.createAutomation(r,f,N,j),h(""),p(""),C(""),z("")},children:"创建自动化"}),c.jsx("button",{onClick:i.refreshAutomations,children:"刷新自动化"}),c.jsx("span",{className:"muted",children:i.autoMsg})]}),c.jsx(bl,{empty:"(暂无自动化)",items:i.automations.map(w=>{const V=w.prompt||"",nt=!!jt[w.id],Dt=V.length>Kh,qt=V?nt||!Dt?V:V.slice(0,Kh)+"…":"(无内容)";return c.jsxs("div",{children:[c.jsxs("div",{className:"row-main",children:["[",w.status,w.due?"|到期":"",w.run_once?"|一次性":"","] ",w.name,"|每 ",w.interval_seconds," 秒",Zg(w.interval_seconds),"|上次 ",Kl(w.last_run_at)||"未运行","|下次 ",Kl(w.next_run_at),w.status==="active"?c.jsxs(c.Fragment,{children:[" ",c.jsx("button",{onClick:()=>Nt(w),children:"编辑"})," ",c.jsx("button",{onClick:()=>i.cancelAutomation(w.id),children:"取消"})]}):null]}),w.repo_url?c.jsxs("div",{className:"muted",children:["仓库:",w.repo_url]}):null,w.status==="cancelled"&&w.cancelled_at?c.jsxs("div",{className:"muted",children:["已取消于 ",Kl(w.cancelled_at)]}):null,c.jsxs("div",{className:"muted",style:{whiteSpace:"pre-wrap",overflowWrap:"anywhere"},children:[qt,Dt?c.jsx("button",{onClick:()=>K(dt=>({...dt,[w.id]:!dt[w.id]})),children:nt?"收起":"展开"}):null]}),R===w.id?c.jsxs("div",{children:[c.jsx("input",{placeholder:"自动化名称",style:{width:"100%"},value:g,onChange:dt=>U(dt.target.value)}),c.jsx("textarea",{placeholder:"定时任务内容",value:F,onChange:dt=>X(dt.target.value)}),c.jsxs(Ht,{children:[c.jsx("input",{placeholder:"间隔秒数(最小 60,留空保持原值)",style:{width:"40%"},value:J,onChange:dt=>Q(dt.target.value)}),c.jsx("input",{placeholder:"仓库(可选,清空则清除)",style:{width:"40%"},value:vt,onChange:dt=>St(dt.target.value)}),c.jsx("button",{className:"primary",onClick:async()=>{const dt=i.automations.find(H=>H.id===R)||w;await i.updateAutomation(dt,g,F,J,vt)&&q("")},children:"保存"}),c.jsx("button",{onClick:()=>q(""),children:"取消"})]})]}):null]},w.id)})})]})}function Jg(){var h,f,p,N;const i=Kt(),r=i.triage;return i.triageError?c.jsx("div",{className:"muted",children:i.triageError}):r?c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:"data-group",children:[c.jsxs("b",{children:["到期自动化(",((h=r.counts)==null?void 0:h.automations_due)||0,")"]}),c.jsx(bl,{empty:"(无)",items:(r.automations_due||[]).map((C,j)=>c.jsxs("span",{className:"row-main",children:[C.name,"|每 ",C.interval_seconds," 秒"]},j))})]}),c.jsxs("div",{className:"data-group",children:[c.jsxs("b",{children:["失败/中断任务(",((f=r.counts)==null?void 0:f.failed_tasks)||0,")"]}),c.jsx(bl,{empty:"(无)",items:(r.failed_tasks||[]).map((C,j)=>c.jsxs("span",{className:"row-main",children:[Ae(C.id),"|",C.status,"|",String(C.prompt||"").slice(0,80)]},j))})]}),c.jsxs("div",{className:"data-group",children:[c.jsxs("b",{children:["待处理后续消息(",((p=r.counts)==null?void 0:p.pending_followups)||0,")"]}),c.jsx(bl,{empty:"(无)",items:(r.pending_followups||[]).map((C,j)=>c.jsxs("span",{className:"row-main",children:["任务 ",Ae(C.task_id),"|",String(C.body||"").slice(0,80)]},j))})]}),c.jsxs("div",{className:"data-group",children:[c.jsxs("b",{children:["最近自动化产出(",((N=r.counts)==null?void 0:N.recent_runs)||0,")"]}),c.jsx(bl,{empty:"(无)",items:(r.recent_runs||[]).map((C,j)=>c.jsxs("span",{className:"row-main",children:[Ae(C.automation_id),"|",C.status,C.task_id?"|任务 "+Ae(C.task_id):"",C.task_status?"|"+C.task_status:""]},j))})]})]}):c.jsx("div",{className:"muted",children:"(先选项目)"})}function Fg(){return c.jsx(Ft,{id:"nav-automations",title:"自动化",children:c.jsx(Kg,{})})}function Wg(){const i=Kt();return c.jsxs(Ft,{id:"nav-triage",title:"分诊收件箱",children:[c.jsx(Ht,{children:c.jsx("button",{onClick:i.refreshTriage,children:"刷新分诊"})}),c.jsx(Jg,{})]})}const yo=["pending","in_progress","blocked","completed","cancelled"],Dc={pending:"待办",in_progress:"进行中",blocked:"受阻",completed:"已完成",cancelled:"已取消"},$g=["low","medium","high"],Jh={low:"低",medium:"中",high:"高"};function Ig(i){return i?new Date(i*1e3).toLocaleString():""}function Pg(){const i=Kt(),[r,h]=d.useState([]),[f,p]=d.useState({}),[N,C]=d.useState(0),[j,z]=d.useState(""),[R,q]=d.useState(""),[g,U]=d.useState(""),[F,X]=d.useState(""),[J,Q]=d.useState(""),[vt,St]=d.useState(""),[jt,K]=d.useState("medium"),[Nt,w]=d.useState(""),[V,nt]=d.useState(""),Dt=d.useCallback(async()=>{if(!i.projectId){h([]),p({}),C(0);return}try{let H=W.projectTodos(i.projectId);const ot=new URLSearchParams;g&&ot.set("status",g),F.trim()&&ot.set("q",F.trim());const yt=ot.toString();yt&&(H+="?"+yt);const G=await at(H);h(G.todos||[]),p(G.counts||{}),C(G.total||0),q("")}catch(H){q("待办加载失败:"+((H==null?void 0:H.message)||String(H)))}},[i.projectId,g,F]);d.useEffect(()=>{Dt()},[Dt]);const qt=async()=>{if(!i.projectId){z("先选项目");return}if(!J.trim()){z("标题必填");return}z("");try{const H={title:J.trim(),description:vt,priority:jt};Nt.trim()&&(H.task_id=Nt.trim()),V&&(H.due_at=Math.floor(new Date(V).getTime()/1e3)),await at(W.projectTodos(i.projectId),"POST",H),Q(""),St(""),w(""),nt(""),z("已创建"),await Dt()}catch(H){z("创建失败:"+((H==null?void 0:H.message)||String(H)))}},dt=async(H,ot)=>{if(i.projectId)try{await at(W.projectTodo(i.projectId,H.id),"PATCH",ot),await Dt()}catch(yt){q("待办更新失败:"+((yt==null?void 0:yt.message)||String(yt)))}},kt=async H=>{if(i.projectId&&window.confirm("删除待办“"+H.title+"”?删除后不可恢复(取消请改状态为已取消)。"))try{await at(W.projectTodo(i.projectId,H.id),"DELETE"),await Dt()}catch(ot){q("待办删除失败:"+((ot==null?void 0:ot.message)||String(ot)))}};return c.jsxs(Ft,{id:"nav-todos",title:"项目待办",children:[c.jsx("div",{className:"muted",children:i.projectId?"共 "+N+" 项 | "+yo.map(H=>Dc[H]+" "+(f[H]||0)).join(" | "):"先选项目"}),R?c.jsx("div",{className:"err",children:R}):null,c.jsxs(Ht,{children:[c.jsxs("select",{"aria-label":"状态筛选",value:g,onChange:H=>U(H.target.value),children:[c.jsx("option",{value:"",children:"状态:全部"}),yo.map(H=>c.jsx("option",{value:H,children:Dc[H]},H))]}),c.jsx("input",{type:"text",placeholder:"搜索标题/说明",value:F,onChange:H=>X(H.target.value)}),c.jsx("button",{onClick:()=>void Dt(),children:"刷新"})]}),c.jsx("input",{type:"text",placeholder:"待办标题(必填)",style:{width:"100%"},value:J,onChange:H=>Q(H.target.value)}),c.jsx("textarea",{placeholder:"说明(可选)",value:vt,onChange:H=>St(H.target.value)}),c.jsxs(Ht,{children:[c.jsx("select",{"aria-label":"优先级",value:jt,onChange:H=>K(H.target.value),children:$g.map(H=>c.jsxs("option",{value:H,children:["优先级:",Jh[H]]},H))}),c.jsx("input",{type:"text",placeholder:"关联任务 id(可选)",value:Nt,onChange:H=>w(H.target.value)}),c.jsx("input",{type:"datetime-local","aria-label":"截止时间",value:V,onChange:H=>nt(H.target.value)}),c.jsx("button",{className:"primary",onClick:()=>void qt(),children:"新建待办"}),c.jsx("span",{className:"muted",children:j})]}),c.jsx(bl,{empty:i.projectId?"(暂无待办)":"(先选项目)",items:r.map(H=>c.jsxs("span",{className:"row-main",children:[c.jsx("b",{children:H.title})," | ",c.jsx("span",{className:"st-"+H.status,children:Dc[H.status]||H.status})," | 优先级 "+(Jh[H.priority]||H.priority),H.task_id?" | 任务 "+Ae(H.task_id):"",H.due_at?" | 截止 "+Ig(H.due_at):"",H.completed_at?" | 完成于 "+new Date(H.completed_at*1e3).toLocaleString():"",H.description?c.jsxs(c.Fragment,{children:[c.jsx("br",{}),c.jsx("span",{className:"muted",children:H.description})]}):null,c.jsx("br",{}),c.jsx("select",{"aria-label":"改状态",value:H.status,onChange:ot=>void dt(H,{status:ot.target.value}),children:yo.map(ot=>c.jsx("option",{value:ot,children:Dc[ot]},ot))})," ",c.jsx("button",{onClick:()=>void dt(H,{status:"completed"}),children:"完成"})," ",c.jsx("button",{onClick:()=>void kt(H),children:"删除"})]},H.id))}),c.jsx("div",{className:"muted",children:"状态与 Muse 待办同款五态;完成时服务端记完成时间,改回其它状态时清掉;已取消是软关闭,删除才是硬删除。"})]})}const bo=[["tasks","任务工作台","任务"],["todos","项目待办","任务"],["projects","项目与成员","管理"],["resources","环境与 Workers","管理"],["automation","技能与自动化","管理"],["admin","系统与 Key","管理"]],tp=Object.fromEntries(bo.map(([i,r])=>[i,r]));function Fh(){const i=window.location.hash.replace("#view-","");return bo.some(([r])=>r===i)?i:"tasks"}function ep({view:i,setView:r}){return c.jsxs("aside",{className:"shell-side",children:[c.jsx("div",{className:"shell-brand",children:"Codex 命令台"}),c.jsx("nav",{className:"shell-nav","aria-label":"导航",children:["任务","管理"].map(h=>c.jsxs("div",{className:"shell-nav-group",children:[c.jsx("div",{className:"shell-nav-group-label",children:h}),bo.filter(([,,f])=>f===h).map(([f,p])=>c.jsx("button",{type:"button",className:i===f?"active":"",onClick:()=>r(f),children:p},f))]},h))}),c.jsxs("div",{className:"shell-stats",children:[c.jsx("div",{className:"shell-stats-label",children:"概览"}),c.jsx(dg,{})]})]})}function lp(){const i=Kt();return c.jsx("span",{className:i.keySaved?"dash-status ok":"dash-status off",children:i.keySaved?"● 已连接":"○ 未连接"})}function ap(){const i=fg();sg(j=>i.refreshAll(j),1e4),rg(()=>i.refreshSlow(),6e4);const[r,h]=d.useState(()=>Fh()),[f,p]=d.useState(!0),N=j=>{h(j);try{window.history.replaceState(null,"","#view-"+j)}catch{}};d.useEffect(()=>{const j=()=>h(Fh());return window.addEventListener("hashchange",j),()=>window.removeEventListener("hashchange",j)},[]);const C=()=>{N("tasks"),p(!0),setTimeout(()=>{var j;return(j=document.getElementById("sec-dispatch"))==null?void 0:j.scrollIntoView({behavior:"smooth"})},0)};return c.jsx($h.Provider,{value:i,children:c.jsxs("div",{className:"shell",children:[c.jsx(ep,{view:r,setView:N}),c.jsxs("div",{className:"shell-main",children:[c.jsxs("header",{className:"shell-header",children:[c.jsx("h1",{children:tp[r]}),c.jsx(lp,{}),c.jsx("span",{className:"muted shell-hide-sm",children:i.healthText}),c.jsx("span",{className:"muted shell-hide-sm",children:i.quotaText}),c.jsxs("span",{className:"muted shell-stamp",children:["自动刷新 · 每 10 秒 · 已刷新 ",i.refreshStamp]})]}),r==="tasks"?c.jsxs("div",{className:"workspace",children:[c.jsxs("section",{className:"workspace-rail","aria-label":"任务列表",children:[c.jsx("h2",{children:"任务列表"}),c.jsx(gg,{onNew:C})]}),c.jsxs("section",{className:"workspace-center",children:[c.jsx("div",{className:"composer-toggle",children:c.jsx("button",{type:"button",onClick:()=>p(!f),children:f?"收起新建任务":"新建任务"})}),f?c.jsx("section",{id:"sec-dispatch",children:c.jsx(vg,{})}):null,c.jsxs("section",{className:"card workspace-conv",children:[c.jsx("h2",{children:"对话"}),c.jsx(Tg,{})]})]}),c.jsx("section",{className:"workspace-inspector","aria-label":"任务详情",children:c.jsx(wg,{})})]}):c.jsx("main",{className:"page",children:r==="todos"?c.jsx(c.Fragment,{children:c.jsx(Pg,{})}):r==="projects"?c.jsxs(c.Fragment,{children:[c.jsx(Bg,{}),c.jsx(qg,{})]}):r==="resources"?c.jsxs(c.Fragment,{children:[c.jsx(Yg,{}),c.jsx(Gg,{}),c.jsx(Vg,{}),c.jsx(Qg,{})]}):r==="automation"?c.jsxs(c.Fragment,{children:[c.jsx(Xg,{}),c.jsx(Lg,{}),c.jsx(Fg,{}),c.jsx(Wg,{})]}):c.jsxs(c.Fragment,{children:[c.jsx(Hg,{}),c.jsx(Ug,{})]})})]})]})})}Wy.createRoot(document.getElementById("root")).render(c.jsx(Vy.StrictMode,{children:c.jsx(ap,{})})); diff --git a/cloud/server.py b/cloud/server.py index a2289d3..ab48819 100644 --- a/cloud/server.py +++ b/cloud/server.py @@ -1826,6 +1826,16 @@ def _task_to_dict(row, attachments=None, total_tokens=None): # 列表接口由 _task_tokens_map 一次聚合传入;其它调用方 # 不传时为 None(前端显示占位),绝不把“未算”写成 0。 "total_tokens": total_tokens, + # feat-chv-arch95plus 只读字段:仅当 CHV 任务且 worker + # 已把 VM 死亡写进 interrupted_reason 时才出现,值为 + # True;其它情形一律不返回该字段(绝不把“未报告死亡” + # 写成“VM 活着”,live VM 状态不在 server 进程里, + # server 没有 VM 登记表,编不出来也不编)。 + **({"chv_vm_dead": True} + if _row_get(row, "executor") == "cloud-hypervisor" + and _row_get(row, "interrupted_reason") + and "VM 中途死亡" in str(_row_get(row, "interrupted_reason")) + else {}), } diff --git a/cloud/tests/test_chv_arch95plus.py b/cloud/tests/test_chv_arch95plus.py new file mode 100644 index 0000000..f47b59c --- /dev/null +++ b/cloud/tests/test_chv_arch95plus.py @@ -0,0 +1,179 @@ +"""CHV 架构 9.5+ 提升轨测试(feat-chv-arch95plus,堆叠于 #252)。 + +只用标准库与 mock,绝不启动真实 VM;测试里只用 FAKE +占位密钥,绝无真实密钥。覆盖:长函数提取后的行为不变、 +WarmPool facade 不调实验接口、#215 对齐接口形状、审计 +失败计数不中断、只读字段 chv_vm_dead 的派生规则、 +lockfile 校验脚本的通过/漂移/空清单。 +""" + +import json +import logging +import os +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +try: + from cloud.chv.compat215 import resolve_inside, tmpfs_isolated + from cloud.chv.hardening import validate_snapshot_url + from cloud.chv.pool import reconcile_orphans_for_executor + from cloud.sandbox import CloudHypervisorExecutor + from cloud.server import _task_to_dict +except ImportError: + sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + from cloud.chv.compat215 import resolve_inside, tmpfs_isolated + from cloud.chv.hardening import validate_snapshot_url + from cloud.chv.pool import reconcile_orphans_for_executor + from cloud.sandbox import CloudHypervisorExecutor + from cloud.server import _task_to_dict + + +def _row(**kw): + base = { + "id": "t1", "project_id": "p1", "prompt": "x", + "status": "interrupted", "idempotency_key": None, + "depends_on": "[]", "priority": 0, "created_by": "u", + "created_at": 0, "updated_at": 0, "last_heartbeat": 0, + } + base.update(kw) + return base + + +class FakeExecutor: + def __init__(self): + self.acquire_called = False + self.prewarm_called = False + + def shutdown_vm(self, vm): + return True + + def _emit(self, *args, **kwargs): + pass + + +class Compat215Test(unittest.TestCase): + def test_resolve_inside_blocks_escape_and_symlink(self): + with tempfile.TemporaryDirectory() as d: + inside = os.path.join(d, "a.txt") + with open(inside, "w") as fh: + fh.write("x") + self.assertEqual(resolve_inside(d, "a.txt"), inside) + self.assertIsNone(resolve_inside(d, "../outside")) + self.assertIsNone(resolve_inside(d, "/etc/passwd")) + self.assertEqual(resolve_inside(d, ""), d) + + def test_tmpfs_isolated_chv_and_unknown_fail_closed(self): + isolated, _ = tmpfs_isolated("cloud-hypervisor", "/tmp") + self.assertFalse(isolated) + isolated, _ = tmpfs_isolated("unknown-kind", "/tmp") + self.assertFalse(isolated) + + def test_snapshot_url_uses_resolve_inside(self): + with tempfile.TemporaryDirectory() as d: + state = os.path.join(d, "state") + os.makedirs(state) + path = os.path.join(state, "s.raw") + with open(path, "w") as fh: + fh.write("x") + self.assertEqual( + validate_snapshot_url("file://" + path, state), path) + with self.assertRaises(Exception): + validate_snapshot_url( + "file:///etc/passwd", state) + + def test_no_timeout_or_quota_map(self): + import cloud.chv.compat215 as m + self.assertFalse(hasattr(m, "resolve_module_timeout")) + self.assertFalse(hasattr(m, "decide")) + + +class PoolFacadeTest(unittest.TestCase): + def test_facade_only_reconciles_never_acquires(self): + # facade 内部构造 WarmPool:用假执行器 + 空 registry, + # 只要它不调 acquire/prewarm 就返回 [] 且不炸。 + ex = FakeExecutor() + # WarmPool 构造需要 state_dir 属性;没有则 registry 为空 + ex.state_dir = tempfile.mkdtemp() + ex.warm_pool_size = 0 + ex.snapshot_url = None + result = reconcile_orphans_for_executor(ex) + self.assertEqual(result, []) + self.assertFalse(ex.acquire_called) + self.assertFalse(ex.prewarm_called) + + +class AuditCounterTest(unittest.TestCase): + def test_emit_failure_counts_and_does_not_raise(self): + ex = CloudHypervisorExecutor.__new__(CloudHypervisorExecutor) + ex.audit_callback = lambda *a, **k: (_ for _ in ()).throw( + RuntimeError("db down")) + ex.audit_failure_count = 0 + # _emit 不抛异常(任务不因此中断),计数 +1 + ex._emit("vm_boot", {"id": "t1"}, "vm=chv-t1") + self.assertEqual(ex.audit_failure_count, 1) + ex._emit("vm_boot", {"id": "t1"}, "vm=chv-t1") + self.assertEqual(ex.audit_failure_count, 2) + + +class ChvVmDeadFieldTest(unittest.TestCase): + def test_only_chv_with_vm_death_reason_returns_true(self): + row = _row(executor="cloud-hypervisor", + interrupted_reason="VM 中途死亡,任务未完成") + d = _task_to_dict(row) + self.assertTrue(d.get("chv_vm_dead")) + row2 = _row(executor="cloud-hypervisor", + interrupted_reason="quota") + self.assertNotIn("chv_vm_dead", _task_to_dict(row2)) + row3 = _row(executor="docker", + interrupted_reason="VM 中途死亡,任务未完成") + self.assertNotIn("chv_vm_dead", _task_to_dict(row3)) + row4 = _row(executor="cloud-hypervisor", + interrupted_reason=None) + self.assertNotIn("chv_vm_dead", _task_to_dict(row4)) + + +class LockfileScriptTest(unittest.TestCase): + def _run(self, data, extra=None): + with tempfile.TemporaryDirectory() as d: + p = os.path.join(d, "BUILDINFO.json") + with open(p, "w") as fh: + json.dump(data, fh) + script = str(Path(__file__).resolve().parents[2] + / "chv-guest" / "check_lockfile.py") + cmd = [sys.executable, script, p] + (extra or []) + return subprocess.run(cmd, capture_output=True, text=True) + + def test_pass_and_empty_and_drift(self): + good = {"tag": "t", "versions": {"dpkg": ["a 1"], + "pip": ["b==2"]}} + self.assertEqual(self._run(good, ["--tag", "t"]).returncode, 0) + bad = {"tag": "t", "versions": {"dpkg": [], "pip": []}} + self.assertNotEqual(self._run(bad).returncode, 0) + with tempfile.TemporaryDirectory() as d: + frozen = os.path.join(d, "frozen.json") + with open(frozen, "w") as fh: + json.dump({"dpkg": ["missing 9"], "pip": []}, fh) + self.assertNotEqual( + self._run(good, ["--frozen", frozen]).returncode, 0) + + +class LongFunctionTest(unittest.TestCase): + def test_no_chv_function_over_50_lines(self): + import ast + base = Path(__file__).resolve().parents[2] / "cloud" / "chv" + over = [] + for f in base.glob("*.py"): + tree = ast.parse(f.read_text()) + for node in ast.walk(tree): + if isinstance(node, ast.FunctionDef): + n = node.end_lineno - node.lineno + 1 + if n > 50: + over.append("%s:%s:%d" % (f.name, node.name, n)) + self.assertEqual(over, []) + + +if __name__ == "__main__": + unittest.main() diff --git a/cloud/worker.py b/cloud/worker.py index 7973672..8a8ef37 100644 --- a/cloud/worker.py +++ b/cloud/worker.py @@ -6368,6 +6368,17 @@ class Worker: final_written = self._set_status_if_running(task_id, final) if quota_stop and final_written and final == "interrupted": self._mark_interrupted_reason(task_id, "quota") + # feat-chv-arch95plus:VM 死亡写回——执行器 _run_with_vm + # 已把 vm_dead/interrupted_reason 放进 result,本处把它 + # 写进 tasks.interrupted_reason 列,server 的只读字段 + # chv_vm_dead 才能据此触发;否则该字段在生产里永远 + # 不出现,前端 VM 死亡横幅就是死代码。只写原因文本, + # 不改状态本身(_mark 只在 status='interrupted' 时写)。 + if final_written and final == "interrupted" \ + and (result or {}).get("vm_dead"): + self._mark_interrupted_reason( + task_id, (result or {}).get("interrupted_reason") + or "VM 中途死亡") finished_at = time.time() # P0 安全批后:CAS 没改到行(任务已被取消/回收)时 # 绝不写回结果,否则迟到的旧进程会覆盖新一轮的结果 diff --git a/docs/chv-architecture.md b/docs/chv-architecture.md index a216f75..90930f2 100644 --- a/docs/chv-architecture.md +++ b/docs/chv-architecture.md @@ -72,9 +72,67 @@ - 现状:#215(timeouts/gates/quota/sandbox_policy)未合并, 本链 base 里无其模块,本轨不复制其代码。 -- 接口对齐点(#215 合并后机械替换,非重写):超时类 - api/ready/task 解析 → timeouts;门禁类 preflight/容量/ - 磁盘配额/egress fail-closed → gates;配额类 max_vms/disk_gb - (任务成本仍走 worker quota)→ quota;隔离类单任务单 VM/ - 磁盘 copy/egress 代理/SSH 钉死/池一次性与孤儿清理 → - sandbox_policy。替换时须重跑本链 163 项测试,不得改语义。 +- 纠正(feat-chv-arch95plus,核对过 #215 远程分支真实 + 函数后):上一轨 ADR-7 的“超时→timeouts、配额→quota” + 泛化映射是错的。#215 的 timeouts 收敛的是审批/队列 + 超时(None/0=不过期),quota 是按月美元配额,与 CHV + 的 api/ready 秒数(0 无效)和 VM 数/磁盘 GB/快照数 + 单位与语义都不同,硬套会改行为,故这两项无映射。 +- 真实映射(已落代码,见 cloud/chv/compat215.py): + gates.resolve_inside(base, rel) 目录门 → CHV 快照/磁盘 + 路径白名单已在 hardening 里使用,合并后改为 import; + sandbox_policy.tmpfs_isolated → CHV 客机 /tmp 落客机 + 磁盘、一律不隔离(fail-closed),合并后改为 import。 + 其余 #215 函数(审批、控制门、合并状态门、worker 寻址) + 与 CHV 无交集,不映射。替换时须重跑本链测试,不得改语义。 + +## ADR-8 WarmPool 生产隔离:facade 而非类导入 + +- 决策:生产路径(worker 启动/关闭)只调 + cloud/chv/pool.py 的 reconcile_orphans_for_executor() + facade,该函数内部才构造 WarmPool 并只调 + reconcile_orphans();生产 dispatch 绝不调 prewarm/ + acquire/release,executor.py 也不 import pool。 + WarmPool 类与 sandbox re-export 保留仅为兼容(旧测试 + import 路径),新代码不得经类入口进实验接口。 +- 测试钉住:facade 测试断言 acquire/prewarm 绝不被调; + executor 不 import pool 由模块依赖检查覆盖。 + +## ADR-9 审计失败取舍:记日志 + 计数,不中断任务 + +- 选项 A(失败即中断任务):审计完整性最强,但 DB 短暂 + 不可用会把一次审计失败放大成任务失败,且与 worker + 全局 best-effort 语义冲突,CHV 单独硬中断是不一致的。 +- 选项 B(失败记日志 + 计数 + 不中断任务):已选。 + executor._emit 失败时记宿主 error 日志并累加 + audit_failure_count(宿主可观测,非任务状态),任务 + 继续;审计完整性本身没有保证,文档与就绪清单明写。 +- 边界:本决策只针对 VM 生命周期审计;VM 启动/关机等 + 门禁失败仍按原有分类失败处理,不受本条影响。 + +## ADR-10 长函数抽不动的耦合点(本轨后已无 >50 行函数) + +- 本轨后 cloud/chv/ 全部函数均 ≤50 行,上一轨的 7 个 + 长函数全部拆完:stop_vm(进程清理/文件清理/脏标记三 + helper)、wait_guest_ready(provisioned 守卫/不匹配检查)、 + restore_vm(校验/启动段)、cleanup_stale_secrets(单 VM + 清理/活 VM 判定)、cleanup_stale_vms(死 socket 扫描/ + 登记清理)、_unix_socket_api(读头/解析长度/读体/解析 + 响应)、validate_net_entries(归一化/去重/冲突检查)、 + build_chv_config(组装段纯移动)。 +- 残留耦合点(已拆但仍需注意):stop_vm 的三块清理共享 + 同一脏标记,任一块失败即整 VM 标记脏(有意,不拆成 + 三个标记);_unix_socket_api 的读满语义仍由四个 helper + 共同保证,改任一 helper 须重跑截断/超时测试。 + +## ADR-11 只读 VM 字段边界:chv_vm_dead 不是 live 状态 + +- 决策:server 任务序列化只在 CHV 任务且 worker 已把 + VM 死亡写进 interrupted_reason 列时返回 chv_vm_dead=True, + 其它情形字段不存在;worker 终局时把执行器的 vm_dead + 原因写回该列(见 worker.py 终局块),否则该字段在 + 生产里永远不出现。 +- 边界:该字段是“已报告死亡”,不是“VM 活着/死了”的 + live 状态——server 进程没有 VM 登记表,live 状态编不 + 出来;前端无该字段时不显示 VM 状态,不猜测。VM 控制 + 按钮仍不做(后端无控制端点且安全审计 No-Go)。 diff --git a/docs/chv-production-readiness.md b/docs/chv-production-readiness.md index b85fce5..122afa5 100644 --- a/docs/chv-production-readiness.md +++ b/docs/chv-production-readiness.md @@ -426,3 +426,36 @@ - 真机动态渗透、压测(无 KVM,做不了,只列清单); - WarmPool dispatch 前查池接线(已定案不做,见 ADR-5;重启须先 实现任务盘重供给并补不变量测试)。 + +## 12. 架构 9.5+ 提升轨 feat-chv-arch95plus(堆叠于 #252 之上) + +> 验证方式标注(本轨新增,逐条不混淆):静态 = 代码 + mock +> 测试已覆盖;真机 = 须 KVM 宿主验证;压测 = 须并发负载验证。 +> 凡写“代码已做”而未标真机/压测的,一律只代表静态通过。 + +- 长函数:cloud/chv/ 全部函数 ≤50 行(静态,mock 测试覆盖 + 提取后的 stop/wait/restore/清理/接收/校验/组装段)。 +- WarmPool 生产隔离:生产路径只调 reconcile facade(静态); + 预热收益仍是实验性,真机收益未验证(真机)。 +- #215 对齐:目录门与 tmpfs 口径已落代码(静态);超时与 + 美元配额无映射,#215 合并后替换为 import 仍须重跑测试 + (合并后,静态)。 +- 审计失败:记日志 + 计数 + 不中断(静态);审计完整性在 + DB 故障下的实际表现仍须真机/故障注入验证(真机)。 +- 供应链:BUILDINFO 事后 lockfile 校验脚本已落(静态); + 事前 apt/pip 精确 lockfile 仍缺(须构建宿主,外部)。 +- 只读 VM 字段:chv_vm_dead 派生自 worker 已写回的 + interrupted_reason(静态,mock 测试覆盖);live VM 状态 + 仍无字段,VM 控制按钮仍不做(后端无控制端点 + 安全 No-Go)。 +- 合并前 diff 复核清单(合并时逐项打勾,不在本轨内做): + server 白名单、worker 断点分支、sandbox re-export、 + console.html 构建产物与 src 一致、#215 合并后的 import + 替换点。 +- 真机验证清单(沿用 §11.5,另加本轨项):VM 死亡横幅在 + 真任务中断后是否出现、审计失败计数在 DB 故障时是否 + 累加且任务不中断、快照白名单目录门在符号链接逃逸时 + 是否拒绝。 +- 灰度启用清单(启用时逐项打勾):CI 变绿 → 按 #216→#252→ + 本 PR 顺序合并 → KVM 宿主 + 防火墙清单 → 镜像构建发布 + → §11.5 + 本节真机清单全过 → 默认关闭下单租户/受信项目 + 灰度 → 全量多租户(须另行决策,不在本轨内默认开启)。