Skip to content

Commit a7732eb

Browse files
authored
Merge pull request #426 from thevibeworks/feat/trace-ui-425
feat(trace): publish live UI to host, banner before TUI, poll-then-open
2 parents fd93509 + 592b119 commit a7732eb

7 files changed

Lines changed: 105 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3131
containers pin old images; a CLI bump now creates a distinct container
3232
instead of silently attaching a stale CLI to the same config home.
3333
Label-less images keep the bare agent name (#420)
34+
- Traced sessions publish the cctrace live UI to the host loopback:
35+
free port probed from 9317, `Trace UI: http://127.0.0.1:<port>` printed
36+
before the TUI starts, and the host browser opens the moment the UI
37+
answers (poll-then-open; `DEVA_TRACE_OPEN=0` disables). Attaching to a
38+
persistent container created without the port warns instead (#425)
3439

3540
### Changed
3641
- cctrace pin bumped 0.4.0 -> 0.11.0 (client profiles, shape-first

agents/claude.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ agent_prepare() {
5151
# into the container system trust store (#414)
5252
DOCKER_ARGS+=("-e" "DEVA_TRACE=1")
5353
DEVA_TRACE_ACTIVE=true
54+
setup_trace_ui_port
5455
AGENT_COMMAND=("cctrace" "--no-open" "--")
5556
if [ "$has_dangerously" = false ]; then
5657
AGENT_COMMAND+=("--dangerously-skip-permissions")

agents/codex.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ agent_prepare() {
9494
# DEVA_TRACE=1 installs the MITM CA into the container store (#414).
9595
DOCKER_ARGS+=("-e" "DEVA_TRACE=1")
9696
DEVA_TRACE_ACTIVE=true
97+
setup_trace_ui_port
9798
AGENT_COMMAND=("cctrace" "codex" "--no-open" "--" "${AGENT_COMMAND[@]:1}")
9899
fi
99100

agents/grok.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ agent_prepare() {
4242
# DEVA_TRACE=1 installs the MITM CA into the container store (#414).
4343
DOCKER_ARGS+=("-e" "DEVA_TRACE=1")
4444
DEVA_TRACE_ACTIVE=true
45+
setup_trace_ui_port
4546
AGENT_COMMAND=("cctrace" "grok" "--no-open" "--" "${AGENT_COMMAND[@]:1}")
4647
fi
4748

agents/shared_auth.sh

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,84 @@ is_file_path() {
226226
[[ "$arg" == /* ]] || [[ "$arg" == ~* ]] || [[ "$arg" == ./* ]] || [[ "$arg" == ../* ]] || [[ "$arg" == *.json ]]
227227
}
228228

229+
# Publish the cctrace live UI (container port 9317, binds 0.0.0.0) to the
230+
# host loopback so the browser can reach it. Probe host ports from 9317 so
231+
# concurrent traced containers land on predictable neighbors (#425).
232+
DEVA_TRACE_UI_URL=""
233+
setup_trace_ui_port() {
234+
local port=9317
235+
local tries=0
236+
local free_port=""
237+
while [ "$tries" -lt 12 ]; do
238+
if ! (exec 3<>"/dev/tcp/127.0.0.1/$port") 2>/dev/null; then
239+
free_port="$port"
240+
break
241+
fi
242+
port=$((port + 1))
243+
tries=$((tries + 1))
244+
done
245+
246+
if [ -z "$free_port" ]; then
247+
echo "warning: no free host port in 9317-9328; trace UI will not be reachable from the host" >&2
248+
return 0
249+
fi
250+
251+
DOCKER_ARGS+=("-p" "127.0.0.1:${free_port}:9317")
252+
DEVA_TRACE_UI_URL="http://127.0.0.1:${free_port}"
253+
}
254+
255+
# Print the trace UI URL before the TUI takes the screen, and open the host
256+
# browser the moment the UI answers. "new" trusts DEVA_TRACE_UI_URL (the
257+
# container doesn't exist yet); "existing" asks docker for the live mapping.
258+
announce_trace_ui() {
259+
[ "${DEVA_TRACE_ACTIVE:-false}" = true ] || return 0
260+
[ "${DRY_RUN:-false}" = true ] && return 0
261+
262+
local url="$DEVA_TRACE_UI_URL"
263+
if [ "${1:-new}" = "existing" ]; then
264+
# docker port exits non-zero for unpublished mappings; under
265+
# set -euo pipefail that must not kill the launch.
266+
local mapping
267+
mapping=$(docker port "$CONTAINER_NAME" 9317/tcp 2>/dev/null | head -1 || true)
268+
if [ -z "$mapping" ]; then
269+
echo "warning: trace UI not reachable — container was created without the trace port; recreate it (deva rm) or use --rm" >&2
270+
return 0
271+
fi
272+
url="http://127.0.0.1:${mapping##*:}"
273+
fi
274+
[ -n "$url" ] || return 0
275+
276+
echo "Trace UI: $url (live once the agent starts)" >&2
277+
maybe_open_trace_ui "$url"
278+
}
279+
280+
# Poll-then-open in the background: the browser opens exactly when the UI
281+
# is up, never against a connection-refused page. DEVA_TRACE_OPEN=0 disables.
282+
maybe_open_trace_ui() {
283+
local url="$1"
284+
[ "${DEVA_TRACE_OPEN:-1}" = "1" ] || return 0
285+
286+
local opener=""
287+
if command -v open >/dev/null 2>&1; then
288+
opener="open"
289+
elif command -v xdg-open >/dev/null 2>&1; then
290+
opener="xdg-open"
291+
fi
292+
[ -n "$opener" ] || return 0
293+
294+
(
295+
local i=0
296+
while [ "$i" -lt 60 ]; do
297+
if curl -sf -o /dev/null --max-time 1 "$url/" 2>/dev/null; then
298+
"$opener" "$url" >/dev/null 2>&1 || true
299+
exit 0
300+
fi
301+
sleep 0.5
302+
i=$((i + 1))
303+
done
304+
) >/dev/null 2>&1 &
305+
}
306+
229307
AUTH_PROVISION_MODE=false
230308

231309
expand_auth_file_tilde() {

deva.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3732,6 +3732,10 @@ if [ "$EPHEMERAL_MODE" = false ]; then
37323732
_trace_env="DEVA_TRACE=0"
37333733
[ "${DEVA_TRACE_ACTIVE:-false}" = true ] && _trace_env="DEVA_TRACE=1"
37343734
3735+
# Trace UI reachability is fixed at container create (port publish);
3736+
# attaching to a container created without it cannot gain the mapping.
3737+
announce_trace_ui existing
3738+
37353739
if [ "$AUTH_PROVISION_MODE" = true ]; then
37363740
docker exec -e "$_trace_env" "${DOCKER_TERMINAL_ARGS[@]}" "$CONTAINER_NAME" /usr/local/bin/docker-entrypoint.sh "${AGENT_COMMAND[@]}" || true
37373741
finish_auth_provision
@@ -3741,6 +3745,7 @@ if [ "$EPHEMERAL_MODE" = false ]; then
37413745
else
37423746
echo "Launching ${ACTIVE_AGENT} (ephemeral mode) via $(docker_image_ref)"
37433747
write_session_file
3748+
announce_trace_ui new
37443749
if [ "$AUTH_PROVISION_MODE" = true ]; then
37453750
docker "${DOCKER_ARGS[@]}" "${AGENT_COMMAND[@]}" || true
37463751
finish_auth_provision

docs/advanced-usage.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,20 @@ tools that ignore `NODE_EXTRA_CA_CERTS`/`SSL_CERT_FILE` still verify. The
181181
container is the blast radius: trust never touches the host store and is
182182
removed at the next non-traced start.
183183

184+
The live trace UI (cctrace's web view, port 9317 in the container) is
185+
published to the host loopback. deva prints the URL before the TUI takes
186+
the screen and opens your host browser the moment the UI answers:
187+
188+
```text
189+
Trace UI: http://127.0.0.1:<port> (live once the agent starts)
190+
```
191+
192+
Set `DEVA_TRACE_OPEN=0` to keep the banner but skip the auto-open.
193+
Concurrent traced containers get neighboring host ports (9318, ...).
194+
Port publishing is fixed at container create: attaching to a persistent
195+
container created without `--trace` prints a warning instead — recreate
196+
it (`deva rm`) or use `--rm` for traced sessions.
197+
184198
Traces land in `.cctrace/` inside your workspace, so they survive the
185199
container. Each run writes a JSONL log plus a self-contained HTML snapshot you
186200
can open in a browser on the host. Credentials are redacted before anything

0 commit comments

Comments
 (0)