Skip to content

Commit c60acc0

Browse files
authored
Merge pull request #24 from nextlevelbuilder/dev
release: promote dev to main (traces CLI contract)
2 parents 7757628 + ed46934 commit c60acc0

27 files changed

Lines changed: 1369 additions & 193 deletions

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
3232
- `GOCLAW_PROFILE` — per-command profile selection precedence between `--profile` and active config.
3333
- `goclaw sessions compact <key>` — invokes WS RPC `sessions.compact` behind destructive confirmation.
3434
- `goclaw health` — uses WS RPC `health` when authenticated, retaining unauthenticated HTTP `/health` fallback.
35-
- `goclaw traces list --since --agent --status --root-only --limit`expanded filters for automation-friendly trace search.
35+
- `goclaw traces list --agent --user --session-key --status --channel --limit --offset`server-aligned filters for paginated trace listing.
3636

3737
**P4 — UX polish**
3838
- `goclaw codex-pool activity --agent=<id>|--provider=<id>` — unified Codex pool activity lookup; legacy agent/provider commands remain as deprecated aliases.
@@ -48,6 +48,7 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
4848

4949
**P6 — Backend-unblocked surfaces (gateway `v3.12.0-beta.20`+)**
5050
- `goclaw traces follow --session-key|--agent [--since RFC3339] [--limit N]` — one-shot incremental trace polling (`GET /v1/traces/follow`). Re-invoke with returned cursor to advance; no WS stream, no watch loop.
51+
- `goclaw traces timeline <run-id> [--session-key K] [--limit N] [--offset N]` — read archived run timeline items (`GET /v1/runs/{runID}/timeline`) without replaying or mutating a run.
5152
- `goclaw providers reconnect <provider-id>` — hot-reconnect a provider, bumping the registry without touching credentials (`POST /v1/providers/{id}/reconnect`).
5253
- `goclaw sessions branch <session-key> --up-to-index N [--new-session-key K] [--label L] [--metadata k=v ...]` — branch a chat session at a 1-based message index into a new session (`POST /v1/chat/sessions/{key}/branch`). `--up-to-index=0` is preserved on the wire.
5354
- `goclaw sessions follow <session-key> [--cursor N] [--limit N]` — one-shot cursor-based history poll (`GET /v1/chat/sessions/{key}/history/follow`). Not a stream; `--cursor=0` is preserved literally in the query string.
@@ -57,7 +58,10 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5758

5859
### Fixed
5960

60-
- `goclaw traces get <id>` — TTY mode now renders a human-readable summary (header card + span tree + events list) instead of dumping raw JSON. JSON-mode payload unchanged. Decode failures surface as wrapped errors instead of an empty `{}`. Trace ids are validated against `^[A-Za-z0-9._-]+$` and reserved tokens (`.`, `..`) are rejected before any HTTP call. Distinct exit codes per failure: not-found → 3, permission-denied → 2, malformed-id → 4, server-failure → 5. Latent retry-body bug in `internal/client/http.go` fixed: the final 5xx/429 response body is now preserved so the typed `APIError` reaches the caller (previously collapsed to exit 1). Closes #17.
61+
- `goclaw traces list` now decodes the current server payload `{traces,total,limit,offset}`. JSON/YAML mode preserves that envelope; table mode renders rows from `traces` using `id`, `total_input_tokens`, `total_output_tokens`, and `total_cost`.
62+
- `goclaw traces get <id>` — TTY mode now renders a human-readable summary (header card + span tree) instead of dumping raw JSON. JSON-mode payload unchanged. Decode failures surface as wrapped errors instead of an empty `{}`. Trace ids are validated against `^[A-Za-z0-9._-]+$` and reserved tokens (`.`, `..`) are rejected before any HTTP call. Distinct exit codes per failure: not-found → 3, permission-denied → 2, malformed-id → 4, server-failure → 5. Latent retry-body bug in `internal/client/http.go` fixed: the final 5xx/429 response body is now preserved so the typed `APIError` reaches the caller (previously collapsed to exit 1). Closes #17.
63+
- `goclaw traces get <id>` now handles the current server detail payload `{trace,spans}` while preserving the server envelope in JSON/YAML mode.
64+
- `goclaw traces export <id>` now validates trace IDs and path-escapes the export route before making the HTTP request.
6165

6266
### Notes
6367
- All new commands honor the AI-first ergonomics contract: `--output=json` envelope, central error handler, `--yes` for destructive ops, `--quiet` for CI.

README.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,21 @@ echo "Analyze this log" | goclaw chat myagent
9393

9494
### Backend-Unblocked Surfaces (P6)
9595

96-
Seven one-shot subcommands wired to backend PRs `#37` and `#44`:
96+
Backend-unblocked one-shot subcommands wired to backend PRs `#37` and `#44`
97+
plus the run timeline archive endpoint:
9798

9899
```bash
100+
# Paginated trace listing with server-supported filters
101+
goclaw traces list [--agent <id>] [--user <id>] [--session-key <key>] \
102+
[--status <status>] [--channel <name>] [--limit <n>] [--offset <n>]
103+
99104
# Incremental trace polling (one shot; rerun with returned cursor)
100105
goclaw traces follow --session-key <key> [--since <RFC3339>] [--limit <n>]
101106
goclaw traces follow --agent <id> [--since <RFC3339>] [--limit <n>]
102107

108+
# Archived run timeline (read-only)
109+
goclaw traces timeline <run-id> [--session-key <key>] [--limit <n>] [--offset <n>]
110+
103111
# Provider hot-reconnect (bumps registry without recreating credentials)
104112
goclaw providers reconnect <provider-id>
105113

@@ -127,11 +135,14 @@ All are one-shot HTTP — no watch loops or WS streams. `logs aggregate` is admi
127135
### Reading a Trace by ID
128136

129137
```bash
130-
# Human-readable: header + span tree + events
138+
# Human-readable: header + span tree
131139
goclaw traces get <trace-id>
132140

133141
# Machine-readable JSON (also auto-selected when stdout is piped)
134142
goclaw traces get <trace-id> -o json
143+
144+
# Export gzipped trace tree
145+
goclaw traces export <trace-id> --output trace.json.gz
135146
```
136147

137148
Exit codes for `traces get`: `0` on success, `2` on permission denied, `3` on not-found, `4` on malformed id (rejected before any HTTP call — allowlist `^[A-Za-z0-9._-]+$`), `5` on upstream server failure, `6` on rate-limit / network-resource exhaustion.
@@ -475,7 +486,7 @@ One-shot profile override:
475486

476487
```bash
477488
goclaw --profile staging agents list
478-
GOCLAW_PROFILE=staging goclaw traces list --since=1h --root-only -o json
489+
GOCLAW_PROFILE=staging goclaw traces list --session-key=session-1 --channel=telegram -o json
479490
```
480491

481492
## Claude Code Skill

cmd/p3_commands_test.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,26 +86,37 @@ func TestTracesListAddsP3Filters(t *testing.T) {
8686
return
8787
}
8888
q := r.URL.Query()
89-
if q.Get("agent_id") != "agent-1" || q.Get("status") != "error" ||
90-
q.Get("since") != "1h" || q.Get("root_only") != "true" || q.Get("limit") != "5" {
89+
if q.Get("agent_id") != "agent-1" || q.Get("user_id") != "user-1" ||
90+
q.Get("session_key") != "session-1" || q.Get("status") != "error" ||
91+
q.Get("channel") != "telegram" || q.Get("limit") != "5" ||
92+
q.Get("offset") != "10" {
9193
t.Fatalf("unexpected query: %s", r.URL.RawQuery)
9294
}
93-
okJSON(t, w, []map[string]any{{"trace_id": "trace-1"}})
95+
rawJSON(t, w, map[string]any{
96+
"traces": []map[string]any{{"id": "trace-1"}},
97+
"total": 1,
98+
"limit": 5,
99+
"offset": 10,
100+
})
94101
}))
95102
defer srv.Close()
96103
setupP3CommandTest(srv.URL)
97104

98105
_ = tracesListCmd.Flags().Set("agent", "agent-1")
106+
_ = tracesListCmd.Flags().Set("user", "user-1")
107+
_ = tracesListCmd.Flags().Set("session-key", "session-1")
99108
_ = tracesListCmd.Flags().Set("status", "error")
100-
_ = tracesListCmd.Flags().Set("since", "1h")
101-
_ = tracesListCmd.Flags().Set("root-only", "true")
109+
_ = tracesListCmd.Flags().Set("channel", "telegram")
102110
_ = tracesListCmd.Flags().Set("limit", "5")
111+
_ = tracesListCmd.Flags().Set("offset", "10")
103112
t.Cleanup(func() {
104113
_ = tracesListCmd.Flags().Set("agent", "")
114+
_ = tracesListCmd.Flags().Set("user", "")
115+
_ = tracesListCmd.Flags().Set("session-key", "")
105116
_ = tracesListCmd.Flags().Set("status", "")
106-
_ = tracesListCmd.Flags().Set("since", "")
107-
_ = tracesListCmd.Flags().Set("root-only", "false")
117+
_ = tracesListCmd.Flags().Set("channel", "")
108118
_ = tracesListCmd.Flags().Set("limit", "20")
119+
_ = tracesListCmd.Flags().Set("offset", "0")
109120
})
110121

111122
if err := tracesListCmd.RunE(tracesListCmd, nil); err != nil {

cmd/testdata/trace_detail_get.json

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,57 @@
11
{
2-
"_TODO_refresh": "stub fixture derived from traces follow payload shape; refresh against goclaw.zuey.me before merge per phase-03 reviewer gate",
3-
"trace_id": "trace_FIXTURE_001",
4-
"agent_id": "agent_FIXTURE_001",
5-
"session_key": "session_FIXTURE_001",
6-
"user_id": "user_REDACTED",
7-
"tenant_id": "tenant_REDACTED",
8-
"status": "success",
9-
"started_at": "2026-05-28T10:00:00Z",
10-
"ended_at": "2026-05-28T10:00:02Z",
11-
"duration_ms": 2000,
12-
"input_tokens": 120,
13-
"output_tokens": 80,
14-
"cost": "0.0042",
2+
"trace": {
3+
"id": "trace_FIXTURE_001",
4+
"agent_id": "agent_FIXTURE_001",
5+
"session_key": "session_FIXTURE_001",
6+
"run_id": "run_FIXTURE_001",
7+
"user_id": "user_REDACTED",
8+
"status": "completed",
9+
"start_time": "2026-05-28T10:00:00Z",
10+
"end_time": "2026-05-28T10:00:02Z",
11+
"duration_ms": 2000,
12+
"total_input_tokens": 120,
13+
"total_output_tokens": 80,
14+
"total_cost": 0.0042,
15+
"span_count": 3,
16+
"llm_call_count": 1,
17+
"tool_call_count": 1
18+
},
1519
"spans": [
1620
{
17-
"span_id": "span_001",
21+
"id": "span_001",
22+
"trace_id": "trace_FIXTURE_001",
1823
"parent_span_id": null,
24+
"span_type": "agent",
1925
"name": "agent.run",
20-
"kind": "agent",
21-
"started_at": "2026-05-28T10:00:00Z",
22-
"ended_at": "2026-05-28T10:00:02Z",
26+
"start_time": "2026-05-28T10:00:00Z",
27+
"end_time": "2026-05-28T10:00:02Z",
2328
"duration_ms": 2000,
24-
"status": "success"
29+
"status": "completed"
2530
},
2631
{
27-
"span_id": "span_002",
32+
"id": "span_002",
33+
"trace_id": "trace_FIXTURE_001",
2834
"parent_span_id": "span_001",
35+
"span_type": "llm",
2936
"name": "llm.call",
30-
"kind": "llm",
31-
"started_at": "2026-05-28T10:00:00Z",
32-
"ended_at": "2026-05-28T10:00:01Z",
37+
"start_time": "2026-05-28T10:00:00Z",
38+
"end_time": "2026-05-28T10:00:01Z",
3339
"duration_ms": 1500,
34-
"status": "success",
40+
"status": "completed",
3541
"input_tokens": 120,
3642
"output_tokens": 80
3743
},
3844
{
39-
"span_id": "span_003",
45+
"id": "span_003",
46+
"trace_id": "trace_FIXTURE_001",
4047
"parent_span_id": "span_001",
48+
"span_type": "tool",
4149
"name": "tool.call",
42-
"kind": "tool",
43-
"started_at": "2026-05-28T10:00:01Z",
44-
"ended_at": "2026-05-28T10:00:02Z",
50+
"tool_name": "web_fetch",
51+
"start_time": "2026-05-28T10:00:01Z",
52+
"end_time": "2026-05-28T10:00:02Z",
4553
"duration_ms": 400,
46-
"status": "success"
54+
"status": "completed"
4755
}
48-
],
49-
"events": [
50-
{"event_id": "ev_001", "span_id": "span_002", "type": "llm.prompt", "timestamp": "2026-05-28T10:00:00Z"},
51-
{"event_id": "ev_002", "span_id": "span_002", "type": "llm.completion", "timestamp": "2026-05-28T10:00:01Z"},
52-
{"event_id": "ev_003", "span_id": "span_003", "type": "tool.invoke", "timestamp": "2026-05-28T10:00:01Z"}
5356
]
5457
}

0 commit comments

Comments
 (0)