From 59e6a728e5c103d4fd28e0bab4cf87f904e7ccb4 Mon Sep 17 00:00:00 2001 From: "fangyaozheng@bytedance.com" Date: Tue, 16 Jun 2026 17:14:49 +0800 Subject: [PATCH 1/2] fix(harness): install codex deps correctly (prerelease + Volcengine mirror) The harness Dockerfile appended openai-codex to the main install line, which fails: openai-codex (and its bundled openai-codex-cli-bin) are pre-release only, so uv rejects them without --prerelease=allow. Split into a dedicated step with --prerelease=allow and the Volcengine pypi mirror (carries the pre-release wheels, fastest from the build infra). Verified: codex runtime builds and --runtime codex gains local file access. Co-Authored-By: Claude Opus 4.8 --- agentkit/toolkit/executors/init_executor.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/agentkit/toolkit/executors/init_executor.py b/agentkit/toolkit/executors/init_executor.py index 6509f55..b24a95f 100644 --- a/agentkit/toolkit/executors/init_executor.py +++ b/agentkit/toolkit/executors/init_executor.py @@ -143,8 +143,10 @@ # Container image for the harness server. The base image's apt mirror is an # unreachable internal host, so apt is repointed at aliyun; the source branch is # cloned via the ghfast proxy with a github fallback; uv installs from aliyun. -# `openai-codex` is installed alongside veadk so the `codex` runtime works -# (it bundles the Codex CLI binary); without it `--runtime codex` fails. +# `openai-codex` is installed so the `codex` runtime works (it bundles the Codex +# CLI binary); without it `--runtime codex` fails. It is pre-release only, so it +# needs --prerelease=allow and is pulled from the Volcengine mirror (which +# carries the pre-release wheels and is fastest from the build infra). _HARNESS_DOCKERFILE = """\ FROM agentkit-cn-beijing.cr.volces.com/base/py-simple:python3.12-bookworm-slim-latest ENV PYTHONUNBUFFERED=1 @@ -168,7 +170,13 @@ done; \\ test -d src/veadk RUN uv pip install --system --index-url https://mirrors.aliyun.com/pypi/simple/ \\ - ./src fastapi "uvicorn[standard]" openai-codex + ./src fastapi "uvicorn[standard]" +# Codex runtime (RUNTIME=codex): openai-codex + its bundled CLI binary are +# pre-release only, so --prerelease=allow is required; pulled from the +# Volcengine mirror which carries the pre-release wheels. +RUN uv pip install --system --prerelease=allow \\ + --index-url https://mirrors.volces.com/pypi/simple/ \\ + openai-codex EXPOSE 8000 CMD ["python", "-m", "uvicorn", "veadk.cloud.harness_app.app:app", "--host", "0.0.0.0", "--port", "8000"] """ From fff91087936dc94f90fa6817b5858a46e66913d7 Mon Sep 17 00:00:00 2001 From: "fangyaozheng@bytedance.com" Date: Wed, 17 Jun 2026 18:10:33 +0800 Subject: [PATCH 2/2] feat(logs): add `agentkit logs --harness ` for harness runtime logs Query a deployed harness runtime's logs from APMPlus / TLS (VE Sign auth): - Resolve the runtime by name (or RuntimeId) and verify the deploy-time harness tag (agentkit:agenttype=harness); reject non-harness apps with a clear message. - GetLogModuleConfig (APMPlus, signed via _sigv4.sign_headers) yields the TLS LogTopicId; SearchLogs (api-version 0.3.0) runs via the official volcengine.tls SDK, which handles TLS-specific signing. - Default filter service:.; flags: --query, --since (1h/30m/2d), --start/--end (epoch ms), --limit (default 200), --sort, --tls-endpoint, --output (write to file), --raw. Co-Authored-By: Claude Opus 4.8 --- agentkit/toolkit/cli/cli.py | 2 + agentkit/toolkit/cli/cli_logs.py | 253 ++++++++++++++++++++ agentkit/toolkit/volcengine/apmplus_logs.py | 184 ++++++++++++++ tests/toolkit/cli/test_cli_logs.py | 223 +++++++++++++++++ 4 files changed, 662 insertions(+) create mode 100644 agentkit/toolkit/cli/cli_logs.py create mode 100644 agentkit/toolkit/volcengine/apmplus_logs.py create mode 100644 tests/toolkit/cli/test_cli_logs.py diff --git a/agentkit/toolkit/cli/cli.py b/agentkit/toolkit/cli/cli.py index 25261c2..57a7bac 100644 --- a/agentkit/toolkit/cli/cli.py +++ b/agentkit/toolkit/cli/cli.py @@ -47,6 +47,7 @@ from agentkit.toolkit.cli.cli_add import add_app from agentkit.toolkit.cli.cli_list import list_app from agentkit.toolkit.cli.cli_delete import delete_app +from agentkit.toolkit.cli.cli_logs import logs_command # Note: Avoid importing heavy packages at the top to keep CLI startup fast @@ -109,6 +110,7 @@ def main( app.command(name="launch")(launch_command) app.command(name="status")(status_command) app.command(name="destroy")(destroy_command) +app.command(name="logs")(logs_command) # Auth: top-level convenience commands + an `auth` group for profiles. app.command(name="login")(login_command) diff --git a/agentkit/toolkit/cli/cli_logs.py b/agentkit/toolkit/cli/cli_logs.py new file mode 100644 index 0000000..6a75911 --- /dev/null +++ b/agentkit/toolkit/cli/cli_logs.py @@ -0,0 +1,253 @@ +# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""AgentKit CLI - ``logs`` command. + +Query a deployed harness runtime's logs from APMPlus / TLS. The ``--harness`` +value names a runtime; it must carry the harness tag stamped at deploy time +(``agentkit:agenttype=harness``) — otherwise it is a regular agent app whose logs +this command cannot query. +""" + +import datetime +import re +import time +from pathlib import Path +from typing import Optional + +import typer +from rich.console import Console + +console = Console() + +_DURATION_UNITS_MS = {"s": 1000, "m": 60_000, "h": 3_600_000, "d": 86_400_000} +_DURATION_RE = re.compile(r"(\d+)([smhd])") + + +def _parse_since(since: str) -> int: + """Parse a relative duration like ``1h`` / ``30m`` / ``1h30m`` / ``2d`` to ms. + + Raises: + ValueError: when the string has no recognizable ```` token. + """ + matches = _DURATION_RE.findall(since.strip().lower()) + if not matches or "".join(n + u for n, u in matches) != since.strip().lower(): + raise ValueError( + f"无法解析 --since '{since}',请使用形如 1h / 30m / 2d / 1h30m 的格式。" + ) + return sum(int(n) * _DURATION_UNITS_MS[u] for n, u in matches) + + +def _format_timestamp(ts) -> str: + """Format a millisecond epoch timestamp to local time; pass through on failure.""" + try: + return datetime.datetime.fromtimestamp(int(ts) / 1000).strftime( + "%Y-%m-%d %H:%M:%S" + ) + except (ValueError, TypeError, OSError): + return str(ts) + + +def _render_line(entry: dict) -> str: + """Render one log entry as a plain ``