feat: add official Codex skill and harden task execution
Install and update artemis-use with task diagnostics and plugin guidance. Preserve cross-device file receipts, review canonical reports, guard stage transitions and reduce small history summaries. Improve exploration lifecycle, request recovery and browser live connections.
This commit is contained in:
1 parent
2328a4df0c
commit
e1ea099592
128 files changed
+4944
-700
No files matched your search
@@ -0,0 +1,147 @@
|
||||
"""Bounded, read-only task diagnostics shared by MCP and HTTP clients."""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from apps.admin_console.database.repositories.session_repository import session_repo
|
||||
from apps.admin_console.services import evidence_service, report_export
|
||||
from apps.admin_console.services.automation_store import identifier, store
|
||||
from apps.admin_console.services.request_diagnostics import request_diagnostics
|
||||
|
||||
|
||||
_SECRET = re.compile(
|
||||
r"(?i)^(api[_-]?key|access[_-]?token|refresh[_-]?token|authorization|password|secret|client_secret|token_key)$"
|
||||
)
|
||||
|
||||
|
||||
def public_value(value, *, max_chars: int = 60000):
|
||||
"""Remove common credential forms and bound text without inventing missing evidence."""
|
||||
remaining = max_chars
|
||||
remaining_nodes = 2000
|
||||
truncated = False
|
||||
secrets = [
|
||||
v
|
||||
for k, v in os.environ.items()
|
||||
if len(v) >= 8 and re.search(r"(?i)(api_?key|password|secret|token)$", k)
|
||||
]
|
||||
|
||||
def clean(item, depth=0):
|
||||
nonlocal remaining, remaining_nodes, truncated
|
||||
remaining_nodes -= 1
|
||||
if depth > 12:
|
||||
truncated = True
|
||||
return "[嵌套内容已截断]"
|
||||
if isinstance(item, dict):
|
||||
result = {}
|
||||
for k, v in item.items():
|
||||
if remaining_nodes <= 0 or len(result) >= 100:
|
||||
truncated = True
|
||||
break
|
||||
key = clean(str(k), depth + 1)
|
||||
result[key] = "[REDACTED]" if _SECRET.fullmatch(str(k)) else clean(v, depth + 1)
|
||||
return result
|
||||
if isinstance(item, list):
|
||||
result = []
|
||||
for v in item:
|
||||
if remaining_nodes <= 0 or len(result) >= 100:
|
||||
truncated = True
|
||||
break
|
||||
result.append(clean(v, depth + 1))
|
||||
return result
|
||||
if not isinstance(item, str):
|
||||
return item
|
||||
for secret in secrets:
|
||||
item = item.replace(secret, "[REDACTED]")
|
||||
item = re.sub(r"(?i)\bBearer\s+[^\s\"',;]+", "Bearer [REDACTED]", item)
|
||||
item = re.sub(r"\bsk-[A-Za-z0-9_-]+", "[REDACTED]", item)
|
||||
item = re.sub(r"(https?://)[^\s/@]+:[^\s/@]+@", r"\1[REDACTED]@", item)
|
||||
item = re.sub(
|
||||
r"""(?ix)((?:api[_-]?key|access[_-]?token|refresh[_-]?token|authorization|password|secret|token_key)["']?\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s&,;]+)""",
|
||||
r"\1[REDACTED]",
|
||||
item,
|
||||
)
|
||||
allowance = min(8000, remaining)
|
||||
if len(item) > allowance:
|
||||
truncated = True
|
||||
item = item[:allowance] + "…[已截断;按步骤或关键词缩小范围]"
|
||||
remaining = max(0, remaining - min(len(item), allowance))
|
||||
return item
|
||||
|
||||
result = clean(value)
|
||||
return {**result, "text_truncated": truncated, "content_truncated": truncated}
|
||||
|
||||
|
||||
def page(items: list, offset: int, limit: int) -> dict:
|
||||
return {
|
||||
"items": items[offset : offset + limit],
|
||||
"total": len(items),
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
"next_offset": offset + limit if offset + limit < len(items) else None,
|
||||
}
|
||||
|
||||
|
||||
def inspect_task(session_id: str, view: str, *, offset: int = 0, limit: int = 20) -> dict:
|
||||
identifier(session_id)
|
||||
if offset < 0 or not 1 <= limit <= 100:
|
||||
raise ValueError("offset 必须非负,limit 必须在 1–100 之间")
|
||||
if not session_repo.get_session_by_id(session_id) and not store.job(session_id):
|
||||
raise LookupError("任务记录不存在")
|
||||
if view == "usage":
|
||||
result = session_repo.get_session_usage(session_id)
|
||||
result["cache_hit_rate"] = (
|
||||
result["cached_tokens"] / result["prompt_tokens"] if result["prompt_tokens"] else None
|
||||
)
|
||||
result["note"] = (
|
||||
"缓存率=已记录 cached_tokens / prompt_tokens;无用量不等于零消耗,未上报的请求无法补推。"
|
||||
)
|
||||
elif view == "requests":
|
||||
result = request_diagnostics(session_id, limit=limit, offset=offset)
|
||||
elif view == "logs":
|
||||
root = evidence_service.task_root(session_id)
|
||||
result = {"logs": []}
|
||||
for name in ("stdout.log", "stderr.log"):
|
||||
path = evidence_service.evidence_file(root, name)
|
||||
if path is None:
|
||||
result["logs"].append({"name": name, "available": False})
|
||||
continue
|
||||
with path.open("rb") as stream:
|
||||
size = path.stat().st_size
|
||||
start = max(0, size - 128 * 1024)
|
||||
stream.seek(start)
|
||||
raw = stream.read(128 * 1024)
|
||||
lines = raw.decode("utf-8", errors="replace").splitlines()
|
||||
if start and lines:
|
||||
lines = lines[1:]
|
||||
# Offset counts backward from the newest line within the bounded tail.
|
||||
end = max(0, len(lines) - offset)
|
||||
result["logs"].append(
|
||||
{
|
||||
"name": name,
|
||||
"available": True,
|
||||
"bytes": size,
|
||||
"lines": lines[max(0, end - limit) : end],
|
||||
"offset": offset,
|
||||
"next_offset": offset + limit if end > limit else None,
|
||||
"older_bytes_omitted": start,
|
||||
"order": "oldest_to_newest",
|
||||
}
|
||||
)
|
||||
elif view in {"report", "evidence"}:
|
||||
report = report_export.session_report(session_id)
|
||||
if view == "report":
|
||||
result = {**report, "steps": page(report.get("steps", []), offset, limit)}
|
||||
else:
|
||||
index = evidence_service.export_index(
|
||||
session_id, report, report_export.session_videos(report)
|
||||
)
|
||||
result = {
|
||||
**index,
|
||||
**{
|
||||
key: page(index.get(key, []), offset, limit)
|
||||
for key in ("steps", "calls", "recordings", "android_videos", "files")
|
||||
},
|
||||
}
|
||||
else:
|
||||
raise ValueError("view 需为 report / usage / requests / logs / evidence")
|
||||
return public_value({"session_id": session_id, **result})
|
||||
Reference in new issue
Block a user