Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ thread JSON files.
| Visual Studio Copilot | `%LOCALAPPDATA%\\Temp\\VSGitHubCopilotLogs\\traces\\` (Windows), `~/Library/Caches/VSGitHubCopilotLogs/traces/` (macOS), `~/.cache/VSGitHubCopilotLogs/traces/` (Linux) |
| Windsurf | `~/Library/Application Support/Windsurf/User/` (macOS), `~/.config/Windsurf/User/` (Linux), `%APPDATA%\\Windsurf\\User\\` (Windows) |
| Trae | `%APPDATA%\\Trae\\User\\` (Windows), `~/Library/Application Support/Trae/User/` (macOS), `~/.config/Trae/User/` (Linux) |
| TraeX (TRAE CLI) | `~/.trae/cli/sessions/`, `~/.trae/cli/archived_sessions/` |
| Warp | `~/.warp/` (platform-dependent) |
| WorkBuddy | `~/.workbuddy/projects/` |
| ZCode | `~/.zcode/cli/db/`, `~/.zcode/cli/` |
Expand Down
23 changes: 23 additions & 0 deletions cmd/agentsview/live_activity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,29 @@ func TestCollectLiveActivityTargetsUsesOnlyConfiguredHintProviders(t *testing.T)
}, targets[0].Sources)
}

// TestCollectLiveActivityTargetsIncludesTraeX pins the TraeX hint wiring:
// TRAE CLI writes history.jsonl at the same position relative to its sessions
// root as Codex, so it reaches the poller with its own traex: ID prefix.
func TestCollectLiveActivityTargetsIncludesTraeX(t *testing.T) {
base := filepath.Join(t.TempDir(), ".trae", "cli")
cfg := config.Config{
LocalMachineName: "local",
AgentDirs: map[parser.AgentType][]string{
parser.AgentTraeX: {filepath.Join(base, "sessions")},
},
}

targets, err := collectLiveActivityTargets(t.Context(), cfg)

require.NoError(t, err)
require.Len(t, targets, 1)
assert.Equal(t, parser.AgentTraeX, targets[0].Provider.Definition().Type)
assert.Equal(t, "traex:", targets[0].Provider.Definition().IDPrefix)
assert.Equal(t, []parser.ActivityHintSource{
{Path: filepath.Join(base, "history.jsonl")},
}, targets[0].Sources)
}

func TestCollectLiveActivityTargetsDoesNotRequireExistingRoots(t *testing.T) {
base := t.TempDir()
missing := filepath.Join(base, "missing", "sessions")
Expand Down
12 changes: 7 additions & 5 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ can still be parsed.
| VS Code Copilot | (platform-specific, see below) | JSON / JSONL per session |
| Windsurf | (platform-specific, see below) | SQLite `workspaceStorage/<hash>/state.vscdb` workspace chat data |
| Trae | (platform-specific, see below) | Legacy inline chat data in SQLite `workspaceStorage/<hash>/state.vscdb` and `globalStorage/state.vscdb`; modern encrypted layouts are detected as unsupported |
| TraeX (TRAE CLI) | `~/.trae/cli/sessions/` and `~/.trae/cli/archived_sessions/` | Codex-compatible rollout JSONL per session |
| Warp | (platform-specific, see below) | SQLite database |
| WorkBuddy | `~/.workbuddy/projects/` | JSONL per session |
| ZCode | `~/.zcode/cli/db/` or `~/.zcode/cli/` | SQLite database (`db.sqlite`) with usage rows |
Expand Down Expand Up @@ -670,6 +671,7 @@ export REASONIX_DIR=~/custom/reasonix
export ROOCODE_DIR=~/custom/roocode
export SHELLEY_DIR=~/custom/shelley
export TRAE_DIR=~/custom/trae/User
export TRAEX_SESSIONS_DIR=~/custom/trae/cli/sessions
export VISUALSTUDIO_COPILOT_DIR=~/custom/visualstudio-copilot/traces
export VSCODE_COPILOT_DIR=~/custom/vscode
export WINDSURF_DIR=~/custom/windsurf/User
Expand Down Expand Up @@ -707,11 +709,11 @@ The corresponding fields are `aider_dirs`, `amp_dirs`, `antigravity_dirs`,
`opencode_dirs`, `openhands_dirs`, `pi_dirs`, `prime_agent_dirs`, `piebald_dirs`,
`posit_assistant_dirs`, `positron_dirs`, `qclaw_dirs`, `qoder_project_dirs`,
`qwen_project_dirs`, `qwenpaw_dirs`, `reasonix_dirs`, `roocode_dirs`,
`shelley_dirs`, `visualstudio_copilot_dirs`, `vscode_copilot_dirs`,
`windsurf_dirs`, `warp_dirs`, `workbuddy_project_dirs`, `zcode_dirs`,
`zed_dirs`, and `zencoder_dirs`. Each accepts an array of paths. When set,
these take precedence over the single-directory environment variable and the
default path.
`shelley_dirs`, `traex_sessions_dirs`, `visualstudio_copilot_dirs`,
`vscode_copilot_dirs`, `windsurf_dirs`, `warp_dirs`,
`workbuddy_project_dirs`, `zcode_dirs`, `zed_dirs`, and `zencoder_dirs`. Each
accepts an array of paths. When set, these take precedence over the
single-directory environment variable and the default path.

All listed directories are discovered, watched, and synced independently.

Expand Down
112 changes: 71 additions & 41 deletions docs/internal/session-format-sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,17 @@ Grok section and remove the explicit registry exception in the coverage test.
persists verbatim inside the stored usage object. Verified 2026-07-30
against two local web-search sessions: when the search is driven by the
**CLI's** `WebSearch` tool, every assistant record carries
`server_tool_use: {"web_search_requests": 0, "web_fetch_requests": 0}` —
the search itself runs in an out-of-band side call that is **not written to
the transcript at all**. The only surviving evidence is the tool-result
record's `toolUseResult` object
(`{query, results, durationSeconds, searchCount}`), whose `searchCount`
matched the wire-billed `web_search_requests` (1 == 1) in both sessions.
Agentsview therefore credits the assistant message that issued the
`WebSearch` `tool_use` with its linked result's `searchCount`, and uses the
message's own counter instead whenever that counter is nonzero (which is
what sessions driving the API directly report), so a search is never
counted twice. **Known undercount:** the side call's own token usage — tens
of thousands of input tokens on `claude-haiku-4-5` per search — is not
`server_tool_use: {"web_search_requests": 0, "web_fetch_requests": 0}` — the
search itself runs in an out-of-band side call that is **not written to the
transcript at all**. The only surviving evidence is the tool-result record's
`toolUseResult` object (`{query, results, durationSeconds, searchCount}`),
whose `searchCount` matched the wire-billed `web_search_requests` (1 == 1)
in both sessions. Agentsview therefore credits the assistant message that
issued the `WebSearch` `tool_use` with its linked result's `searchCount`,
and uses the message's own counter instead whenever that counter is nonzero
(which is what sessions driving the API directly report), so a search is
never counted twice. **Known undercount:** the side call's own token usage —
tens of thousands of input tokens on `claude-haiku-4-5` per search — is not
persisted anywhere in the transcript and is not recoverable, so it is
neither recorded nor estimated. `web_fetch_requests` is recorded when
present but is not priced. Data version 82 reparses existing Claude archives
Expand All @@ -131,37 +130,38 @@ Grok section and remove the explicit registry exception in the coverage test.
still traversed only to discover nested subagents. Verified 2026-07-30
against wire-captured billing for three local Claude Code sessions: parents
with subagents under-reported cost by 45-77% before the presentation-time
rollup. Reverified 2026-07-30 with Claude Code 2.1.220: a streaming tool turn
can persist several assistant records with one `(message.id, requestId)` pair
while `usage.output_tokens` grows from an early partial count to the final
billed count (observed examples included `5` then `631` and `6` then `798`).
Usage reporting therefore keeps the greatest output-token snapshot for each
message/request identity across the included sessions, attributes it to the
earliest transcript, and then applies cross-session replay deduplication.
Session-owned dimensions and display metadata also come from that earliest
transcript. Numeric-string token values remain accepted as compatibility
input and are normalized before snapshot comparison on every backend. The
SQLite and PostgreSQL read the exact top-level token path and nested
server-tool path even in supported malformed legacy JSON. Reverified
2026-08-06 with end-truncated objects containing earlier nested decoy keys;
PostgreSQL and its Cockroach-compatible helper repair the truncated object
before extracting the requested path, while irreparable input contributes no
counter rather than an ambiguously scoped value.
Equal snapshots are selected deterministically by timestamp, session id, and
message ordinal; equivalent RFC3339 spellings use the semantic tie-breakers
rather than raw timestamp text. Reverified 2026-08-04 against the
cross-backend stored-usage fixtures.
rollup. Reverified 2026-07-30 with Claude Code 2.1.220: a streaming tool
turn can persist several assistant records with one
`(message.id, requestId)` pair while `usage.output_tokens` grows from an
early partial count to the final billed count (observed examples included
`5` then `631` and `6` then `798`). Usage reporting therefore keeps the
greatest output-token snapshot for each message/request identity across the
included sessions, attributes it to the earliest transcript, and then
applies cross-session replay deduplication. Session-owned dimensions and
display metadata also come from that earliest transcript. Numeric-string
token values remain accepted as compatibility input and are normalized
before snapshot comparison on every backend. The SQLite and PostgreSQL read
the exact top-level token path and nested server-tool path even in supported
malformed legacy JSON. Reverified 2026-08-06 with end-truncated objects
containing earlier nested decoy keys; PostgreSQL and its
Cockroach-compatible helper repair the truncated object before extracting
the requested path, while irreparable input contributes no counter rather
than an ambiguously scoped value. Equal snapshots are selected
deterministically by timestamp, session id, and message ordinal; equivalent
RFC3339 spellings use the semantic tie-breakers rather than raw timestamp
text. Reverified 2026-08-04 against the cross-backend stored-usage fixtures.
Replaying the three captured sessions after this correction matched all
transcript-visible output; each full-wire total remained 15 output tokens
higher because Claude Code's separate session-title request is not persisted.
Reverified 2026-08-06 that session-summary export loads matching snapshots
across excluded sessions and pagination before applying the same snapshot,
attribution, web-search, and generic deduplication rules. These accounting
semantics are exposed by usage, activity, and session-summary schema version
5 and reporting schema version 2; reporting version 1 retains its frozen
first-seen, token-only semantics. Reverified 2026-08-06 that DuckDB records a
web-search-only flat fee as computed pricing provenance, so combining it with
a provider-reported cost is labeled `mixed` like the SQLite archive.
higher because Claude Code's separate session-title request is not
persisted. Reverified 2026-08-06 that session-summary export loads matching
snapshots across excluded sessions and pagination before applying the same
snapshot, attribution, web-search, and generic deduplication rules. These
accounting semantics are exposed by usage, activity, and session-summary
schema version 5 and reporting schema version 2; reporting version 1 retains
its frozen first-seen, token-only semantics. Reverified 2026-08-06 that
DuckDB records a web-search-only flat fee as computed pricing provenance, so
combining it with a provider-reported cost is labeled `mixed` like the
SQLite archive.
- **Agentsview:** `internal/parser/claude.go` and
`internal/parser/claude_provider.go`; local observations and fixtures are
the implementation evidence for fields not documented upstream. Reverified
Expand Down Expand Up @@ -276,6 +276,36 @@ Grok section and remove the explicit registry exception in the coverage test.
autonomous run whose last prompt falls outside those bounds, the rollout
relies on those fallbacks until its next prompt.

## TraeX (`traex`)

- **Format:** Codex-compatible rollout JSONL under a dated `YYYY/MM/DD` tree,
written by TRAE CLI 2.0, plus the flat `archived_sessions/` directory that
`traex archive <id>` moves a rollout into. The sibling `history.jsonl`
carries the same `session_id`/Unix-seconds `ts`/prompt `text` records, and
agentsview consumes it as the same live-activity hint. No
`session_index.jsonl` sidecar is produced, so titles come from the rollout
head alone.
- **Evidence:** `no-public-source`.
- **Upstream:** TRAE CLI 2.0 ships only as a closed-source binary; the observed
builds report themselves as `traecli 0.200.x`. Trae's first-party
[product site](https://www.trae.ai/) and the official
`https://github.com/Trae-AI/Trae.git` repository were searched 2026-08-04
and publish neither the producer nor a session schema. The equivalence to
Codex rests on locally observed rollouts whose `session_meta`, `event_msg`,
`response_item`, and `token_count` records are field-for-field the Codex
shape -- including `source.subagent.thread_spawn.parent_thread_id` and an
`originator` of `codex-tui` -- which identifies it as a fork of the
evidenced codex-rs recorder rather than an independent format. A
de-identified rollout is retained as a fixture.
- **Usage and cost:** `token_count` records carry the Codex fields, so
normalization and catalog pricing follow the Codex entry above exactly,
including the same cache-write and reasoning-output omissions.
- **Agentsview:** `internal/parser/traex.go` relabels the shared Codex parser
(`internal/parser/codex.go`, `internal/parser/codex_provider.go`) onto the
`traex:` ID namespace, and `internal/sync` gates the format-shaped branches
on `isCodexFormatAgent`. The `session_index.jsonl` and S3 branches stay
Codex-only because TraeX writes no index file and has no archive layout.

## GitHub Copilot CLI (`copilot`)

- **Format:** Flat session JSONL or a session directory containing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
claude: "Claude Code",
cowork: "Claude Cowork",
codex: "Codex",
traex: "TraeX",
copilot: "Copilot",
gemini: "Gemini",
opencode: "OpenCode",
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/lib/utils/agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe("KNOWN_AGENTS", () => {
"claude",
"cowork",
"codex",
"traex",
"copilot",
"devin",
"gemini",
Expand Down Expand Up @@ -72,6 +73,9 @@ describe("agentColor", () => {
expect(agentColor("codex")).toBe(
"var(--accent-green)",
);
expect(agentColor("traex")).toBe(
"var(--accent-coral)",
);
expect(agentColor("copilot")).toBe(
"var(--accent-amber)",
);
Expand Down Expand Up @@ -214,6 +218,7 @@ describe("agentLabel", () => {
expect(agentLabel("qoder")).toBe("Qoder");
expect(agentLabel("roocode")).toBe("RooCode");
expect(agentLabel("omnigent")).toBe("Omnigent");
expect(agentLabel("traex")).toBe("TraeX");
});

it("capitalizes simple agent names", () => {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/utils/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const KNOWN_AGENTS: readonly AgentMeta[] = [
{ name: "claude", color: "var(--accent-blue)" },
{ name: "cowork", color: "var(--accent-sky)", label: "Claude Cowork" },
{ name: "codex", color: "var(--accent-green)" },
{ name: "traex", color: "var(--accent-coral)", label: "TraeX" },
{ name: "copilot", color: "var(--accent-amber)" },
{ name: "devin", color: "var(--accent-red)", label: "Devin" },
{ name: "gemini", color: "var(--accent-rose)" },
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/lib/utils/resume.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ describe("supportsResume", () => {
it("returns true for supported agents", () => {
expect(supportsResume("claude")).toBe(true);
expect(supportsResume("codex")).toBe(true);
expect(supportsResume("traex")).toBe(true);
expect(supportsResume("copilot")).toBe(true);
expect(supportsResume("cursor")).toBe(true);
expect(supportsResume("gemini")).toBe(true);
Expand Down Expand Up @@ -41,6 +42,15 @@ describe("buildResumeCommand", () => {
).toBe("codex resume sess-1");
});

it("generates traex resume command", () => {
expect(
buildResumeCommand("traex", "traex:sess-1"),
).toBe("traex resume sess-1");
expect(
buildResumeCommand("traex", "traex:run-1", { model: "gpt-5-codex" }),
).toBe("traex resume run-1 -m gpt-5-codex");
});

it("pins Claude and Codex models with shell quoting", () => {
expect(
buildResumeCommand("claude", "run-1", { model: "claude sonnet" }),
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/lib/utils/resume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ RESUME_AGENTS["claude"] = (id) =>
`claude --resume ${shellQuote(id)}`;
RESUME_AGENTS["codex"] = (id) =>
`codex resume ${shellQuote(id)}`;
// TraeX ships the traex, traecli, and trae-cli aliases; use the shortest.
RESUME_AGENTS["traex"] = (id) =>
`traex resume ${shellQuote(id)}`;
RESUME_AGENTS["copilot"] = (id) =>
`copilot --resume=${shellQuote(id)}`;
RESUME_AGENTS["cursor"] = (id) =>
Expand Down Expand Up @@ -97,7 +100,8 @@ export function buildResumeCommand(

if (flags?.model) {
if (agent === "claude") cmd += ` --model ${shellQuote(flags.model)}`;
if (agent === "codex") cmd += ` -m ${shellQuote(flags.model)}`;
if (agent === "codex" || agent === "traex")
cmd += ` -m ${shellQuote(flags.model)}`;
}

if (agent === "claude" && flags) {
Expand Down
Loading
Loading