diff --git a/CHANGELOG.md b/CHANGELOG.md index 48aac87..9fb04ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Stable trace URL via portless (vercel-labs): when the `portless` CLI + is on the host, `--trace` registers/refreshes the `cctrace` alias + for the published UI port and announces the routed URL — the live + UI and `/dashboard` are always at `https://cctrace.localhost` + regardless of which port the run landed on. Best-effort no-op + without portless; `DEVA_TRACE_PORTLESS=0` disables. A host `PORT` + env is also honored as the publish port (portless-wrapped runs), + and `DEVA_TRACE_URL` still overrides the announced URL outright. + `DEVA_TRACE_UI_URL` (the host-reachable UI URL) is now exported + into every traced container on create and reattach — contract for + the claude-code-statusline trace chip, which can't derive the host + port from container-side env (#547) - KIMI_WEBBRIDGE_VERSION as a managed pin: `make versions-up` and `make versions-pin` resolve Kimi WebBridge from cdn.kimi.com/webbridge/latest/version.json (no npm package; same @@ -36,6 +48,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 identity as a launch flag, official CLIs stock, zero workflow tax (#536) +### Fixed +- `--trace` UI dead on arrival with cctrace >= 0.36: its default port + moved 9317 -> 8722, so the container bound 8722 while deva published + 9317 and the poll-then-open never connected. All four traced agent + commands now pin `cctrace --port 9317`, which also keeps existing + containers' 9317 mappings valid (#547) +- False "created without the trace port" warning on every traced + reattach under `HOST_NET=true`: host networking makes `-p` a docker + no-op and `docker port` permanently empty. Trace plumbing now + detects host networking (skips the publish, announces + `http://127.0.0.1:9317` directly, checks `NetworkMode` on reattach); + the real-mismatch warning now names the container and the exact + `deva rm` command (#547) + ## [0.18.2] - 2026-08-04 ### Changed diff --git a/DEV-LOGS.md b/DEV-LOGS.md index 44661cb..14e68d0 100644 --- a/DEV-LOGS.md +++ b/DEV-LOGS.md @@ -13,6 +13,11 @@ - Minimal markdown markers, no unnecessary formatting, minimal emojis. - Reference issue numbers in the format `#` for easy linking. +# [2026-08-09] Dev Log: --trace port pin, HOST_NET awareness, portless URL #547 +- Why: `--trace` was broken two ways — cctrace >= 0.36 binds 8722 while deva publishes 9317 (browser poll never connects), and HOST_NET=true makes `docker port` permanently empty (false "created without the trace port" warning on every reattach). +- What: `cctrace --port 9317` pinned in all four traced agents; setup_trace_ui_port/announce_trace_ui detect host networking (skip -p, loopback URL, NetworkMode check on reattach); host PORT env honored (portless-style); portless `cctrace` alias registered per traced launch so the UI/dashboard live at a stable https://cctrace.localhost route (DEVA_TRACE_PORTLESS=0 opts out, DEVA_TRACE_URL overrides); DEVA_TRACE_UI_URL exported into the container on create AND reattach for the statusline trace chip. +- Result: `deva claude --trace` opens the UI again under both port-publish and HOST_NET; dashboard has one bookmarkable URL. Statusline chip contract: prefer DEVA_TRACE_UI_URL, fallback http://localhost:$CCTRACE_SERVER_PORT (patch proposed against statusline PR #9). Open: multi-session cctrace in one published container walks to 9318+ unpublished. + # [2026-08-07] Dev Log: KIMI_WEBBRIDGE_VERSION joins pin management #544 - Why: #543 (WebBridge browser tools for all agents) needs a pinned daemon+extension version, and pins added outside the flow rot — the round-trip guard exists because the heredoc is a second copy of the layout. Land the pin machinery first so #543 only adds consumers. - What: WebBridge has no npm package; the CDN's version-first layout publishes latest/version.json ({"version":"vX.Y.Z", binaries with per-arch sha256}) — shared _webbridge_cdn_latest in release-utils.sh parses that. versions.env + VERSION_PIN_VARS + heredoc get KIMI_WEBBRIDGE_VERSION (v1.11.5). version-upgrade.sh resolves it outside the tool registry exactly like the cloakbrowser wrapper: CLI override → CDN → pin fallback, staleness gates the nothing-to-upgrade exit, --only kimi-webbridge, manifest display, pin-what-was-resolved. update-version-pins.sh gets a webbridge fetch type; versions-pr.sh a label; Makefile forwards the override. Tests: version.json fixture in both working curl fakes (tripwire hard-errors unknown URLs), pin-write assertion v1.12.0; outage + round-trip unchanged. diff --git a/agents/claude.sh b/agents/claude.sh index e669808..d063035 100644 --- a/agents/claude.sh +++ b/agents/claude.sh @@ -65,7 +65,7 @@ agent_prepare() { DOCKER_ARGS+=("-e" "DEVA_TRACE=1") DEVA_TRACE_ACTIVE=true setup_trace_ui_port - AGENT_COMMAND=("cctrace" "--no-open" "--") + AGENT_COMMAND=("cctrace" "--no-open" "--port" "9317" "--") if [ "$has_dangerously" = false ]; then AGENT_COMMAND+=("--dangerously-skip-permissions") fi diff --git a/agents/codex.sh b/agents/codex.sh index fab2322..d2630f5 100644 --- a/agents/codex.sh +++ b/agents/codex.sh @@ -84,7 +84,7 @@ agent_prepare() { DOCKER_ARGS+=("-e" "DEVA_TRACE=1") DEVA_TRACE_ACTIVE=true setup_trace_ui_port - AGENT_COMMAND=("cctrace" "codex" "--no-open" "--" "${AGENT_COMMAND[@]:1}") + AGENT_COMMAND=("cctrace" "codex" "--no-open" "--port" "9317" "--" "${AGENT_COMMAND[@]:1}") fi DOCKER_ARGS+=("-p" "127.0.0.1:1455:1455") diff --git a/agents/grok.sh b/agents/grok.sh index db2e736..9d36883 100644 --- a/agents/grok.sh +++ b/agents/grok.sh @@ -32,7 +32,7 @@ agent_prepare() { DOCKER_ARGS+=("-e" "DEVA_TRACE=1") DEVA_TRACE_ACTIVE=true setup_trace_ui_port - AGENT_COMMAND=("cctrace" "grok" "--no-open" "--" "${AGENT_COMMAND[@]:1}") + AGENT_COMMAND=("cctrace" "grok" "--no-open" "--port" "9317" "--" "${AGENT_COMMAND[@]:1}") fi setup_grok_auth "$AUTH_METHOD" diff --git a/agents/kimi.sh b/agents/kimi.sh index fd45f9f..7e25343 100644 --- a/agents/kimi.sh +++ b/agents/kimi.sh @@ -34,7 +34,7 @@ agent_prepare() { DOCKER_ARGS+=("-e" "DEVA_TRACE=1") DEVA_TRACE_ACTIVE=true setup_trace_ui_port - AGENT_COMMAND=("cctrace" "kimi" "--no-open" "--" "${AGENT_COMMAND[@]:1}") + AGENT_COMMAND=("cctrace" "kimi" "--no-open" "--port" "9317" "--" "${AGENT_COMMAND[@]:1}") fi setup_kimi_auth "$AUTH_METHOD" diff --git a/agents/shared_auth.sh b/agents/shared_auth.sh index a23a155..40b2c21 100644 --- a/agents/shared_auth.sh +++ b/agents/shared_auth.sh @@ -249,22 +249,74 @@ filter_trace_flag() { done } -# Publish the cctrace live UI (container port 9317, binds 0.0.0.0) to the -# host loopback so the browser can reach it. Probe host ports from 9317 so +# Publish the cctrace live UI to the host loopback so the browser can reach +# it. cctrace is pinned to container port 9317 via --port (its 0.36+ default +# moved to 8722; 9317 keeps existing containers' mappings valid). Host side: +# honor $PORT when a portless-style router set it (portless assigns the port +# and routes https://.localhost to it), else probe from 9317 so # concurrent traced containers land on predictable neighbors (#425). +# DEVA_TRACE_URL overrides the announced URL (e.g. the portless route name). DEVA_TRACE_UI_URL="" + +# Host networking makes -p a docker no-op and `docker port` permanently +# empty — the container binds the host loopback directly, so the UI lives +# at cctrace's pinned port with no publish at all. +_trace_host_network_args() { + local joined=" ${DOCKER_ARGS[*]+"${DOCKER_ARGS[*]}"} ${EXTRA_DOCKER_ARGS[*]+"${EXTRA_DOCKER_ARGS[*]}"} " + case "$joined" in + *" --net host "* | *" --network host "* | *" --net=host "* | *" --network=host "*) return 0 ;; + esac + return 1 +} + +# Stable trace URL via portless (vercel-labs): register/refresh the +# `cctrace` alias for the host-reachable UI port so the dashboard is +# always at the same named URL (e.g. https://cctrace.localhost) no +# matter which port this run landed on. Best-effort: silent no-op when +# the portless CLI is absent or the proxy is down. Prints the routed +# URL on success. DEVA_TRACE_PORTLESS=0 disables. +_trace_portless_url() { + local port="$1" + [ "${DEVA_TRACE_PORTLESS:-1}" = "1" ] || return 1 + command -v portless >/dev/null 2>&1 || return 1 + portless alias cctrace "$port" >/dev/null 2>&1 || return 1 + local url + url=$(portless get cctrace 2>/dev/null | head -1) + [ -n "$url" ] || return 1 + printf '%s' "$url" +} + +# Resolve the announced/exported UI URL for a host-reachable port. +# Precedence: explicit DEVA_TRACE_URL > portless route > raw loopback. +_trace_resolve_ui_url() { + local port="$1" + local routed="" + routed=$(_trace_portless_url "$port") || routed="" + printf '%s' "${DEVA_TRACE_URL:-${routed:-http://127.0.0.1:${port}}}" +} + setup_trace_ui_port() { - local port=9317 - local tries=0 + if _trace_host_network_args; then + DEVA_TRACE_UI_URL="$(_trace_resolve_ui_url 9317)" + DOCKER_ARGS+=("-e" "DEVA_TRACE_UI_URL=${DEVA_TRACE_UI_URL}") + return 0 + fi + local free_port="" - while [ "$tries" -lt 12 ]; do - if ! (exec 3<>"/dev/tcp/127.0.0.1/$port") 2>/dev/null; then - free_port="$port" - break - fi - port=$((port + 1)) - tries=$((tries + 1)) - done + if [ -n "${PORT:-}" ] && [[ "${PORT}" =~ ^[0-9]{2,5}$ ]]; then + free_port="$PORT" + else + local port=9317 + local tries=0 + while [ "$tries" -lt 12 ]; do + if ! (exec 3<>"/dev/tcp/127.0.0.1/$port") 2>/dev/null; then + free_port="$port" + break + fi + port=$((port + 1)) + tries=$((tries + 1)) + done + fi if [ -z "$free_port" ]; then echo "warning: no free host port in 9317-9328; trace UI will not be reachable from the host" >&2 @@ -272,7 +324,11 @@ setup_trace_ui_port() { fi DOCKER_ARGS+=("-p" "127.0.0.1:${free_port}:9317") - DEVA_TRACE_UI_URL="http://127.0.0.1:${free_port}" + DEVA_TRACE_UI_URL="$(_trace_resolve_ui_url "$free_port")" + # Host-reachable URL for in-container tooling (statusline trace chip): + # the container-side CCTRACE_SERVER_PORT is not the port the host + # browser can reach, this is. + DOCKER_ARGS+=("-e" "DEVA_TRACE_UI_URL=${DEVA_TRACE_UI_URL}") } # Print the trace UI URL before the TUI takes the screen, and open the host @@ -284,15 +340,27 @@ announce_trace_ui() { local url="$DEVA_TRACE_UI_URL" if [ "${1:-new}" = "existing" ]; then - # docker port exits non-zero for unpublished mappings; under - # set -euo pipefail that must not kill the launch. - local mapping - mapping=$(docker port "$CONTAINER_NAME" 9317/tcp 2>/dev/null | head -1 || true) - if [ -z "$mapping" ]; then - echo "warning: trace UI not reachable — container was created without the trace port; recreate it (deva rm) or use --rm" >&2 - return 0 + # Host-network containers never have port mappings — the UI binds + # the host loopback directly at the pinned cctrace port. + local netmode + netmode=$(docker inspect -f '{{.HostConfig.NetworkMode}}' "$CONTAINER_NAME" 2>/dev/null || true) + if [ "$netmode" = "host" ]; then + url="$(_trace_resolve_ui_url 9317)" + else + # docker port exits non-zero for unpublished mappings; under + # set -euo pipefail that must not kill the launch. + local mapping + mapping=$(docker port "$CONTAINER_NAME" 9317/tcp 2>/dev/null | head -1 || true) + if [ -z "$mapping" ]; then + echo "warning: trace UI not reachable — $CONTAINER_NAME was created without the trace port; recreate it (deva rm $CONTAINER_NAME) or use --rm" >&2 + DEVA_TRACE_UI_URL="" # don't hand a dead URL to in-container tooling + return 0 + fi + url="$(_trace_resolve_ui_url "${mapping##*:}")" fi - url="http://127.0.0.1:${mapping##*:}" + # Live value wins over the create-time guess; the exec env carries + # it to in-container tooling (statusline trace chip). + DEVA_TRACE_UI_URL="$url" fi [ -n "$url" ] || return 0 @@ -317,7 +385,9 @@ maybe_open_trace_ui() { ( local i=0 while [ "$i" -lt 60 ]; do - if curl -sf -o /dev/null --max-time 1 "$url/" 2>/dev/null; then + # -k: portless https routes use a local CA curl may not trust; + # this is a loopback readiness probe, not a trust decision. + if curl -skf -o /dev/null --max-time 1 "$url/" 2>/dev/null; then "$opener" "$url" >/dev/null 2>&1 || true exit 0 fi diff --git a/deva.sh b/deva.sh index 39067e1..1212d33 100755 --- a/deva.sh +++ b/deva.sh @@ -4370,15 +4370,21 @@ if [ "$EPHEMERAL_MODE" = false ]; then # Trace UI reachability is fixed at container create (port publish); # attaching to a container created without it cannot gain the mapping. + # announce_trace_ui refreshes DEVA_TRACE_UI_URL from the live mapping, + # so it must run before the exec env is assembled. announce_trace_ui existing announce_cloak_browser existing announce_cloak_vnc existing + _trace_exec_env=(-e "$_trace_env") + [ "${DEVA_TRACE_ACTIVE:-false}" = true ] && [ -n "${DEVA_TRACE_UI_URL:-}" ] && \ + _trace_exec_env+=(-e "DEVA_TRACE_UI_URL=${DEVA_TRACE_UI_URL}") + if [ "$AUTH_PROVISION_MODE" = true ]; then - docker exec -e "$_trace_env" "${DOCKER_TERMINAL_ARGS[@]}" "$CONTAINER_NAME" /usr/local/bin/docker-entrypoint.sh "${AGENT_COMMAND[@]}" || true + docker exec "${_trace_exec_env[@]}" "${DOCKER_TERMINAL_ARGS[@]}" "$CONTAINER_NAME" /usr/local/bin/docker-entrypoint.sh "${AGENT_COMMAND[@]}" || true finish_auth_provision else - exec docker exec -e "$_trace_env" "${DOCKER_TERMINAL_ARGS[@]}" "$CONTAINER_NAME" /usr/local/bin/docker-entrypoint.sh "${AGENT_COMMAND[@]}" + exec docker exec "${_trace_exec_env[@]}" "${DOCKER_TERMINAL_ARGS[@]}" "$CONTAINER_NAME" /usr/local/bin/docker-entrypoint.sh "${AGENT_COMMAND[@]}" fi else echo "Launching ${ACTIVE_AGENT} (ephemeral mode) via $(docker_image_ref)"