Skip to content

Commit 42a2d99

Browse files
mtucker-virtrawesmmariusvniekerk
authored
Add support for Visual Studio Copilot sessions (#699)
* Add support for Visual Studio Copilot session tracing - Parse Visual Studio Copilot OpenTelemetry trace JSONL into sessions - Emit one session per gen_ai.conversation.id, keyed by a <traceFile>#<conversationID> virtual path - Combine spans across sibling trace files, collapsing duplicated chat outputs and execute_tool calls to their most complete copy, preferring the richest token usage when visible output ties, while preserving transcript order - Count token usage for tool-only LLM turns whose only output is executed tool calls, choosing the most complete usage copy across sibling files - Fingerprint a conversation across every sibling trace file, for both the incremental skip check and the watcher's source-mtime fallback, so a re-sync re-parses when any sibling gains spans, not only when the representative trace file changes; the skip check uses the error-returning fingerprint so a directory-enumeration or per-sibling stat failure surfaces and is retried instead of being mistaken for an unchanged skip, and Visual Studio Copilot is excluded from the generic mtime skip cache so a cached entry cannot bypass that composite check - Preserve an archived conversation instead of force-replacing it with a partial transcript when a sibling trace is rotated away: a reparse that shrinks both the message count and the composite trace size keeps the stored session, so messages and tool results already in SQLite are not dropped - Scope session export to the requested conversation by filtering each trace line down to that conversation's spans across every sibling trace file, dropping other conversations' and id-less spans, so exporting one session never discloses spans that share its trace files; report a not-found error when no trace file contains the conversation instead of writing empty output - Surface and retry trace read, scan, and directory-enumeration errors, including missing or unreadable sibling trace files, instead of caching them as skips, and clear stale read-error skip entries on upgrade from both the local and the per-host SSH remote skip caches - Resolve virtual source paths for session export, path-based sync, incremental mtime filtering, and parse-diff classification, and route sync-by-id through the single-session path so it stays scoped to the requested conversation and follows it across sibling trace files; the source-file lookup returns a conversation-scoped virtual path so a rotated-away representative does not widen a resync to the whole trace - Add documentation on Visual Studio vs VS Code traces and synthetic trace fixtures Co-Authored-By: Wes McKinney <wesmckinn+git@gmail.com> * fix: refine visual studio copilot archive preservation * fix: merge richer visual studio copilot archive spans * fix: guard visual studio copilot partial reparses * fix: derive visual studio copilot merged first message * fix: keep Visual Studio syncs complete and scoped Visual Studio trace parsing must fail on malformed non-empty OTLP lines so a transiently partial file cannot replace an indexed session with an incomplete transcript. ID-based direct syncs should use the engine's single-session path so virtual conversation keys remain scoped to one Visual Studio conversation instead of refreshing every conversation in the shared trace file. * test: keep Visual Studio sync regression growing The direct sync regression should isolate conversation scoping rather than exercising the partial-reparse preservation guard added upstream. Make the requested update visibly richer so the test verifies that the requested conversation refreshes while the sibling conversation remains archived. * fix(sync): merge visual studio copilot archive additions * fix(sync): dedupe visual studio copilot archive additions * fix(sync): persist visual studio archive fingerprints --------- Co-authored-by: Wes McKinney <wesmckinn+git@gmail.com> Co-authored-by: Marius van Niekerk <marius.v.niekerk@gmail.com>
1 parent 6234eb6 commit 42a2d99

26 files changed

Lines changed: 6644 additions & 58 deletions

README.md

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -268,42 +268,43 @@ agentsview stats --include-git-outcomes
268268

269269
agentsview auto-discovers sessions from all of these:
270270

271-
| Agent | Session Directory |
272-
| ------------------ | ------------------------------------------------------ |
273-
| Amp | `~/.local/share/amp/threads/` |
274-
| Antigravity | `~/.gemini/antigravity/` |
275-
| Antigravity CLI | `~/.gemini/antigravity-cli/` (see note below) |
276-
| Claude Code | `~/.claude/projects/` |
277-
| Claude Cowork | `~/Library/Application Support/Claude/local-agent-mode-sessions/` (macOS) |
278-
| Codex | `~/.codex/sessions/` |
279-
| Copilot CLI | `~/.copilot/` |
280-
| Cortex Code | `~/.snowflake/cortex/conversations/` |
281-
| Cursor | `~/.cursor/projects/` |
282-
| DeepSeek TUI | `~/.codewhale/sessions/`, `~/.deepseek/sessions/` |
283-
| Forge | `~/.forge/` |
284-
| Gemini CLI | `~/.gemini/` |
285-
| gptme | `~/.local/share/gptme/logs/` |
286-
| Hermes Agent | `~/.hermes/sessions/` |
287-
| iFlow | `~/.iflow/projects/` |
288-
| Kilo | `~/.local/share/kilo/` |
289-
| Kimi | `~/.kimi/sessions/` |
290-
| Kiro CLI | `~/.kiro/sessions/cli/`, `~/.local/share/kiro-cli/` |
291-
| Kiro IDE | `~/Library/Application Support/Kiro/` (macOS) |
292-
| MiMoCode | `~/.local/share/mimocode/` |
293-
| OpenClaw | `~/.openclaw/agents/` |
294-
| OpenCode | `~/.local/share/opencode/` |
295-
| OpenHands CLI | `~/.openhands/conversations/` |
296-
| Pi | `~/.pi/agent/sessions/` |
297-
| Piebald | `~/.local/share/piebald/` |
298-
| Positron Assistant | `~/Library/Application Support/Positron/User/` (macOS) |
299-
| QClaw | `~/.qclaw/agents/` |
300-
| Qwen Code | `~/.qwen/projects/` |
301-
| QwenPaw | `~/.copaw/workspaces/`, `~/.qwenpaw/workspaces/` |
302-
| VSCode Copilot | `~/Library/Application Support/Code/User/` (macOS) |
303-
| Warp | `~/.warp/` (platform-dependent) |
304-
| WorkBuddy | `~/.workbuddy/projects/` |
305-
| Zed | `~/Library/Application Support/Zed/` (macOS) |
306-
| Zencoder | `~/.zencoder/sessions/` |
271+
| Agent | Session Directory |
272+
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
273+
| Amp | `~/.local/share/amp/threads/` |
274+
| Antigravity | `~/.gemini/antigravity/` |
275+
| Antigravity CLI | `~/.gemini/antigravity-cli/` (see note below) |
276+
| Claude Code | `~/.claude/projects/` |
277+
| Claude Cowork | `~/Library/Application Support/Claude/local-agent-mode-sessions/` (macOS) |
278+
| Codex | `~/.codex/sessions/` |
279+
| Copilot CLI | `~/.copilot/` |
280+
| Cortex Code | `~/.snowflake/cortex/conversations/` |
281+
| Cursor | `~/.cursor/projects/` |
282+
| DeepSeek TUI | `~/.codewhale/sessions/`, `~/.deepseek/sessions/` |
283+
| Forge | `~/.forge/` |
284+
| Gemini CLI | `~/.gemini/` |
285+
| gptme | `~/.local/share/gptme/logs/` |
286+
| Hermes Agent | `~/.hermes/sessions/` |
287+
| iFlow | `~/.iflow/projects/` |
288+
| Kilo | `~/.local/share/kilo/` |
289+
| Kimi | `~/.kimi/sessions/` |
290+
| Kiro CLI | `~/.kiro/sessions/cli/`, `~/.local/share/kiro-cli/` |
291+
| Kiro IDE | `~/Library/Application Support/Kiro/` (macOS) |
292+
| MiMoCode | `~/.local/share/mimocode/` |
293+
| OpenClaw | `~/.openclaw/agents/` |
294+
| OpenCode | `~/.local/share/opencode/` |
295+
| OpenHands CLI | `~/.openhands/conversations/` |
296+
| Pi | `~/.pi/agent/sessions/` |
297+
| Piebald | `~/.local/share/piebald/` |
298+
| Positron Assistant | `~/Library/Application Support/Positron/User/` (macOS) |
299+
| QClaw | `~/.qclaw/agents/` |
300+
| Qwen Code | `~/.qwen/projects/` |
301+
| QwenPaw | `~/.copaw/workspaces/`, `~/.qwenpaw/workspaces/` |
302+
| VSCode Copilot | `~/Library/Application Support/Code/User/` (macOS) |
303+
| Visual Studio Copilot | `%LOCALAPPDATA%\\Temp\\VSGitHubCopilotLogs\\traces\\` (Windows), `~/Library/Caches/VSGitHubCopilotLogs/traces/` (macOS), `~/.cache/VSGitHubCopilotLogs/traces/` (Linux) |
304+
| Warp | `~/.warp/` (platform-dependent) |
305+
| WorkBuddy | `~/.workbuddy/projects/` |
306+
| Zed | `~/Library/Application Support/Zed/` (macOS) |
307+
| Zencoder | `~/.zencoder/sessions/` |
307308

308309
Each directory can be overridden with an environment variable. See the
309310
[configuration docs](https://agentsview.io/configuration/) for details.

cmd/agentsview/session_export.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
package main
55

66
import (
7+
"errors"
78
"fmt"
89
"io"
910
"os"
1011

1112
"github.com/spf13/cobra"
1213
"go.kenn.io/agentsview/internal/config"
1314
"go.kenn.io/agentsview/internal/db"
15+
"go.kenn.io/agentsview/internal/parser"
1416
)
1517

1618
func newSessionExportCommand() *cobra.Command {
@@ -55,12 +57,28 @@ func newSessionExportCommand() *cobra.Command {
5557
"session not in local archive: %s", args[0],
5658
)
5759
}
58-
path := d.GetSessionFilePath(id)
59-
if path == "" {
60+
storedPath := d.GetSessionFilePath(id)
61+
if storedPath == "" {
6062
return fmt.Errorf(
6163
"source file not found for session %s", id,
6264
)
6365
}
66+
// A Visual Studio Copilot trace file holds spans for several
67+
// conversations, so streaming the whole file would disclose
68+
// unrelated conversations. Filter to the requested conversation.
69+
if tracePath, conversationID, ok :=
70+
parser.ParseVisualStudioCopilotVirtualPath(storedPath); ok {
71+
err := parser.WriteVisualStudioCopilotConversationJSONL(
72+
cmd.OutOrStdout(), tracePath, conversationID,
73+
)
74+
if errors.Is(err, os.ErrNotExist) {
75+
return fmt.Errorf(
76+
"source file not found: %s", tracePath,
77+
)
78+
}
79+
return err
80+
}
81+
path := parser.ResolveSourceFilePath(storedPath)
6482
f, err := os.Open(path)
6583
if err != nil {
6684
if os.IsNotExist(err) {
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# Visual Studio Copilot Trace Format
2+
3+
Visual Studio Copilot trace data is not the same format as VS Code Copilot chat
4+
sessions.
5+
6+
VS Code Copilot stores app-level chat session documents. Visual Studio Copilot
7+
stores OpenTelemetry-style trace JSONL, where each JSONL line is a trace
8+
envelope containing spans.
9+
10+
## VS Code Copilot Format
11+
12+
VS Code Copilot session files are usually found under paths like:
13+
14+
```text
15+
workspaceStorage/<hash>/chatSessions/<uuid>.json
16+
workspaceStorage/<hash>/chatSessions/<uuid>.jsonl
17+
globalStorage/emptyWindowChatSessions/<uuid>.json
18+
globalStorage/transferredChatSessions/<uuid>.json
19+
```
20+
21+
The top-level shape is a chat session object:
22+
23+
- Session ID is usually `sessionId`.
24+
- Turns are stored in `requests[]`.
25+
- User prompts are stored in `requests[].message.text`.
26+
- Assistant and tool output are stored in `requests[].response[]`.
27+
- One file generally represents one chat session.
28+
29+
Sanitized shape:
30+
31+
```json
32+
{
33+
"version": 3,
34+
"sessionId": "<uuid>",
35+
"creationDate": 1781293600000,
36+
"lastMessageDate": 1781293610000,
37+
"requests": [
38+
{
39+
"requestId": "<uuid>",
40+
"message": {
41+
"text": "<user prompt>"
42+
},
43+
"response": [
44+
{
45+
"kind": "textEditGroup",
46+
"value": "<assistant-or-tool-output>"
47+
}
48+
],
49+
"modelId": "<model>",
50+
"timestamp": 1781293600000
51+
}
52+
]
53+
}
54+
```
55+
56+
## Visual Studio Copilot Format
57+
58+
Visual Studio Copilot trace files are usually found under:
59+
60+
```text
61+
%LOCALAPPDATA%\Temp\VSGitHubCopilotLogs\traces\*_VSGitHubCopilot_traces.jsonl
62+
```
63+
64+
Each line is an OpenTelemetry JSON envelope:
65+
66+
- `resourceSpans[]`
67+
- `scopeSpans[]`
68+
- `spans[]`
69+
- `attributes[]`
70+
71+
Session data is reconstructed from span attributes:
72+
73+
- Conversation ID is `gen_ai.conversation.id`.
74+
- Chat input is `gen_ai.input.messages`.
75+
- Chat output is `gen_ai.output.messages`.
76+
- Tool calls use `gen_ai.tool.name`, `gen_ai.tool.call.id`, and
77+
`gen_ai.tool.call.arguments`.
78+
- Tool results use `gen_ai.tool.call.result`.
79+
- Token usage uses `gen_ai.usage.input_tokens` and `gen_ai.usage.output_tokens`.
80+
- Multiple trace files can contain spans for the same conversation, so the
81+
parser stitches them together by `gen_ai.conversation.id`.
82+
83+
## Sanitized Visual Studio Examples
84+
85+
These examples preserve the structure of real Visual Studio Copilot trace JSONL
86+
spans while redacting prompts, paths, commands, patches, IDs, and results.
87+
88+
### Chat Span
89+
90+
```json
91+
{
92+
"sourceFile": "20260615T234102_b45c44b2_VSGitHubCopilot_traces.jsonl",
93+
"traceId": "<trace-id>",
94+
"spanId": "<span-id>",
95+
"name": "chat gpt-5.5",
96+
"startTimeUnixNano": "<unix-nano>",
97+
"endTimeUnixNano": "<unix-nano>",
98+
"attributes": {
99+
"virtual_parent.genai": "905ec1a433bff543",
100+
"gen_ai.provider.name": "github",
101+
"gen_ai.request.model": "<model>",
102+
"gen_ai.response.model": "<model>",
103+
"gen_ai.response.id": "<redacted>",
104+
"gen_ai.response.finish_reasons": "",
105+
"gen_ai.usage.input_tokens": "94179",
106+
"gen_ai.usage.output_tokens": "218",
107+
"gen_ai.conversation.id": "<uuid-or-call-id>",
108+
"copilot_chat.client_id": "Microsoft.VisualStudio.Conversations.Chat.HelpWindow",
109+
"copilot_chat.root_request_id": "<uuid-or-call-id>",
110+
"server.address": "api.githubcopilot.com",
111+
"gen_ai.tool.definitions": "<redacted>",
112+
"gen_ai.input.messages": "[{\"role\":\"user\",\"parts\":[{\"type\":\"text\",\"content\":\"<user prompt>\"}]}]",
113+
"gen_ai.output.messages": "[{\"role\":\"assistant\",\"parts\":[{\"type\":\"tool_call\",\"id\":\"<call-id>\",\"name\":\"<tool-name>\",\"arguments\":\"<json-encoded-arguments>\"},{\"type\":\"text\",\"content\":\"<assistant text>\"}]}]",
114+
"gen_ai.operation.name": "chat"
115+
}
116+
}
117+
```
118+
119+
### Tool Execution Span
120+
121+
```json
122+
{
123+
"sourceFile": "20260615T234059_b45c44b2_VSGitHubCopilot_traces.jsonl",
124+
"traceId": "<trace-id>",
125+
"spanId": "<span-id>",
126+
"name": "execute_tool get_file",
127+
"startTimeUnixNano": "<unix-nano>",
128+
"endTimeUnixNano": "<unix-nano>",
129+
"attributes": {
130+
"virtual_parent.genai": "26401e75c1171a63",
131+
"gen_ai.conversation.id": "<uuid-or-call-id>",
132+
"gen_ai.tool.call.result": "{\"Value\":\"<tool result>\"}",
133+
"gen_ai.provider.name": "other",
134+
"gen_ai.tool.name": "get_file",
135+
"gen_ai.tool.call.id": "<uuid-or-call-id>",
136+
"gen_ai.tool.type": "extension",
137+
"gen_ai.tool.description": "<redacted>",
138+
"gen_ai.tool.call.arguments": "{\"filename\":\"<path>\",\"command\":\"<command>\",\"patch\":\"<patch>\"}",
139+
"gen_ai.operation.name": "execute_tool"
140+
}
141+
}
142+
```
143+
144+
### Invoke-Agent Span
145+
146+
```json
147+
{
148+
"sourceFile": "20260615T234059_b45c44b2_VSGitHubCopilot_traces.jsonl",
149+
"traceId": "<trace-id>",
150+
"spanId": "<span-id>",
151+
"name": "invoke_agent GitHub Copilot",
152+
"startTimeUnixNano": "<unix-nano>",
153+
"endTimeUnixNano": "<unix-nano>",
154+
"attributes": {
155+
"gen_ai.provider.name": "other",
156+
"gen_ai.agent.name": "GitHub Copilot",
157+
"gen_ai.conversation.id": "<uuid-or-call-id>",
158+
"gen_ai.request.model": "<model>",
159+
"copilot_chat.turn_count": "1",
160+
"copilot_chat.client_id": "Microsoft.VisualStudio.Conversations.Chat.HelpWindow",
161+
"copilot_chat.entry_point": "Microsoft.VisualStudio.Copilot.AgentModeResponder",
162+
"copilot_chat.mode": "Agent",
163+
"copilot_chat.initiator_type": "User",
164+
"copilot_chat.root_request_id": "<uuid-or-call-id>",
165+
"gen_ai.operation.name": "invoke_agent"
166+
}
167+
}
168+
```
169+
170+
## Parser Implications
171+
172+
The Visual Studio parser treats the trace data as telemetry that must be
173+
reconstructed into a transcript:
174+
175+
- It groups spans by `gen_ai.conversation.id`.
176+
- It reads user turns from the stringified `gen_ai.input.messages` payload.
177+
- It reads assistant text and tool-call proposals from the stringified
178+
`gen_ai.output.messages` payload.
179+
- It reads executed tool calls and tool results from separate `execute_tool`
180+
spans.
181+
- It de-duplicates repeated prompt snapshots and duplicate chat/execute tool
182+
records for the same tool call ID.
183+
- It stitches sibling trace files because a single conversation can be split
184+
across multiple `*_VSGitHubCopilot_traces.jsonl` files.

frontend/src/lib/components/settings/AgentDirSettings.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
iflow: "iFlow",
1616
"vscode-copilot": "VSCode Copilot",
1717
pi: "Pi",
18+
"visualstudio-copilot": "Visual Studio Copilot",
1819
qwen: "Qwen Code",
1920
openclaw: "OpenClaw",
2021
qclaw: "QClaw",

frontend/src/lib/utils/agents.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ describe("KNOWN_AGENTS", () => {
2121
"zencoder",
2222
"zed",
2323
"vscode-copilot",
24+
"visualstudio-copilot",
2425
"pi",
2526
"qwen",
2627
"qwenpaw",
@@ -95,6 +96,9 @@ describe("agentColor", () => {
9596
expect(agentColor("vscode-copilot")).toBe(
9697
"var(--accent-teal)",
9798
);
99+
expect(agentColor("visualstudio-copilot")).toBe(
100+
"var(--accent-blue)",
101+
);
98102
expect(agentColor("qclaw")).toBe(
99103
"var(--accent-orange)",
100104
);
@@ -119,6 +123,9 @@ describe("agentLabel", () => {
119123
expect(agentLabel("vscode-copilot")).toBe(
120124
"VS Code Copilot",
121125
);
126+
expect(agentLabel("visualstudio-copilot")).toBe(
127+
"Visual Studio Copilot",
128+
);
122129
expect(agentLabel("openhands")).toBe("OpenHands");
123130
expect(agentLabel("openclaw")).toBe("OpenClaw");
124131
expect(agentLabel("qclaw")).toBe("QClaw");

frontend/src/lib/utils/agents.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ export const KNOWN_AGENTS: readonly AgentMeta[] = [
2121
color: "var(--accent-teal)",
2222
label: "VS Code Copilot",
2323
},
24+
{
25+
name: "visualstudio-copilot",
26+
color: "var(--accent-blue)",
27+
label: "Visual Studio Copilot",
28+
},
2429
{ name: "pi", color: "var(--accent-indigo)", label: "Pi" },
2530
{ name: "qwen", color: "var(--accent-cyan)", label: "Qwen Code" },
2631
{ name: "qwenpaw", color: "var(--accent-cyan)", label: "QwenPaw" },

internal/db/db.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ import (
2929
// trigger a non-destructive re-sync (mtime reset + skip cache
3030
// clear) so existing session data is preserved.
3131
//
32+
// Bumped to 47: the Visual Studio Copilot trace parser now
33+
// persists per-chat model and token usage from gen_ai.usage
34+
// attributes. Existing Visual Studio Copilot rows need re-parsing
35+
// so Usage reports include those sessions.
36+
//
3237
// Bumped to 45: the Codex parser now imports renamed session
3338
// titles from session_index.jsonl. Existing Codex rows need
3439
// re-parsing so their titles reflect later renames.
@@ -219,7 +224,7 @@ import (
219224
//
220225
// (17: Codex <skill> template filtering.)
221226
// (16: <turn_aborted> system messages.)
222-
const dataVersion = 46
227+
const dataVersion = 47
223228

224229
const tokenCoverageRepairStatsKey = "token_coverage_repair_v1"
225230

0 commit comments

Comments
 (0)