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
14 changes: 14 additions & 0 deletions claude_code_log/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,25 @@ def load_transcript(
agent_messages_map: dict[str, list[TranscriptEntry]] = {}
if agent_ids:
parent_dir = jsonl_path.parent
session_basename = (
jsonl_path.stem
) # e.g., "29ccd257-68b1-427f-ae5f-6524b7cb6f20"
for agent_id in agent_ids:
# Try legacy location first (same directory as session file)
agent_file = parent_dir / f"agent-{agent_id}.jsonl"
# Skip if the agent file is the same as the current file (self-reference)
if agent_file == jsonl_path:
continue
# Try new subagents directory structure (Claude Code 2.1.2+)
if not agent_file.exists():
subagent_file = (
parent_dir
/ session_basename
/ "subagents"
/ f"agent-{agent_id}.jsonl"
)
if subagent_file.exists():
agent_file = subagent_file
if agent_file.exists():
if not silent:
print(f"Loading agent file {agent_file}...")
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{"type":"queue-operation","operation":"dequeue","timestamp":"2026-01-23T17:30:15.183Z","sessionId":"94604a7b-062f-4369-bdf0-da948381c3e5"}
{"parentUuid":null,"isSidechain":false,"userType":"external","cwd":"/src/experiments/claude_p","sessionId":"94604a7b-062f-4369-bdf0-da948381c3e5","version":"2.1.17","gitBranch":"","type":"progress","data":{"type":"hook_progress","hookEvent":"SessionStart","hookName":"SessionStart:startup","command":"\"${CLAUDE_PLUGIN_ROOT}/scripts/clmail.cmd\" actor start"},"parentToolUseID":"b161c2d3-7c3a-4b9c-9ae1-0797c4ae195f","toolUseID":"b161c2d3-7c3a-4b9c-9ae1-0797c4ae195f","timestamp":"2026-01-23T17:30:15.058Z","uuid":"eefe3c88-0bee-41ae-abf8-87481377bfc6"}
{"parentUuid":"eefe3c88-0bee-41ae-abf8-87481377bfc6","isSidechain":false,"userType":"external","cwd":"/src/experiments/claude_p","sessionId":"94604a7b-062f-4369-bdf0-da948381c3e5","version":"2.1.17","gitBranch":"","type":"user","message":{"role":"user","content":"What are the tools that are available to you (allowed or not)?"},"uuid":"c8fc3f44-cce3-44c5-8310-2615f46c301a","timestamp":"2026-01-23T17:30:15.190Z","permissionMode":"default"}
{"parentUuid":"c8fc3f44-cce3-44c5-8310-2615f46c301a","isSidechain":false,"userType":"external","cwd":"/src/experiments/claude_p","sessionId":"94604a7b-062f-4369-bdf0-da948381c3e5","version":"2.1.17","gitBranch":"","message":{"model":"claude-opus-4-5-20251101","id":"msg_019ea1G6rvdt2hTDHPjLohcR","type":"message","role":"assistant","content":[{"type":"text","text":"Here are all the tools available to me:\n\n## File Operations\n- **Read** - Read files from the filesystem (including images, PDFs, Jupyter notebooks)\n- **Write** - Create or overwrite files\n- **Edit** - Make exact string replacements in existing files\n- **Glob** - Fast file pattern matching (e.g., `**/*.js`)\n- **Grep** - Search file contents using regex (built on ripgrep)\n- **NotebookEdit** - Edit cells in Jupyter notebooks\n\n## Execution\n- **Bash** - Execute shell commands with optional timeout and background execution\n- **KillShell** - Terminate a running background shell\n\n## Web\n- **WebFetch** - Fetch and process content from URLs (not for authenticated services)\n- **WebSearch** - Search the web for up-to-date information\n\n## Agents & Tasks\n- **Task** - Launch specialized sub-agents:\n - `Bash` - Command execution specialist\n - `general-purpose` - Multi-step research and code tasks\n - `Explore` - Fast codebase exploration\n - `Plan` - Software architecture planning\n - `statusline-setup` - Configure status line settings\n- **TaskOutput** - Retrieve output from background tasks\n\n## Planning & Organization\n- **TodoWrite** - Create and manage task lists\n- **EnterPlanMode** - Transition to planning mode for complex implementations\n- **ExitPlanMode** - Signal plan completion for user approval\n\n## User Interaction\n- **AskUserQuestion** - Ask clarifying questions with predefined options\n- **Skill** - Invoke available skills (e.g., `/commit`, `/changelog`, `/gitlab-mr`, etc.)\n\n---\n\nAll these tools are **available and allowed** for me to use. The restrictions are contextual:\n- **WebFetch** won't work for authenticated/private URLs\n- **Bash** has safety protocols (no force pushes to main, no destructive git commands without explicit request)\n- Security-related tools require clear authorization context"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":6198,"cache_read_input_tokens":13794,"cache_creation":{"ephemeral_5m_input_tokens":6198,"ephemeral_1h_input_tokens":0},"output_tokens":1,"service_tier":"standard"}},"requestId":"req_011CXQhwReSDdPN7mQRP69j8","type":"assistant","uuid":"a17a6cc6-ca38-4afc-b946-6ec5412139c9","timestamp":"2026-01-23T17:30:27.778Z"}
59 changes: 59 additions & 0 deletions test/test_sidechain_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,62 @@ def flatten(msg):
# Should include the rendered content
assert "<strong>bold</strong>" in html or "<b>bold</b>" in html
assert "<code>code</code>" in html


def test_subagents_directory_structure():
"""Test that agent files are loaded from {session}/subagents/ directory (Claude Code 2.1.2+).

The new directory structure is:
session-uuid.jsonl
session-uuid/subagents/agent-*.jsonl

This test verifies the new structure works while maintaining backward compatibility.
"""
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir_path = Path(tmpdir)

# Create session file with UUID-style name (mimics real Claude Code structure)
session_uuid = "29ccd257-68b1-427f-ae5f-6524b7cb6f20"
main_file = tmpdir_path / f"{session_uuid}.jsonl"

# Create the new subagents directory structure
subagents_dir = tmpdir_path / session_uuid / "subagents"
subagents_dir.mkdir(parents=True)

# Write main transcript referencing an agent
main_file.write_text(
'{"parentUuid":null,"isSidechain":false,"userType":"external","cwd":"/workspace/test","sessionId":"test-subagents","version":"2.1.17","gitBranch":"main","type":"user","message":{"role":"user","content":[{"type":"text","text":"Do something with an agent"}]},"uuid":"s-0","timestamp":"2025-01-15T13:00:00.000Z"}\n'
'{"parentUuid":"s-0","isSidechain":false,"userType":"external","cwd":"/workspace/test","sessionId":"test-subagents","version":"2.1.17","gitBranch":"main","message":{"model":"claude-sonnet-4-5-20250929","id":"msg_01","type":"message","role":"assistant","content":[{"type":"tool_use","id":"task-sub","name":"Task","input":{"prompt":"Subagent task"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":100,"output_tokens":50}},"requestId":"req_01","type":"assistant","uuid":"s-1","timestamp":"2025-01-15T13:00:05.000Z"}\n'
'{"parentUuid":"s-1","isSidechain":false,"userType":"external","cwd":"/workspace/test","sessionId":"test-subagents","version":"2.1.17","gitBranch":"main","type":"user","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"task-sub","content":"Subagent completed the task"}]},"uuid":"s-2","timestamp":"2025-01-15T13:00:15.000Z","toolUseResult":{"status":"completed","agentId":"a2271d1","content":[{"type":"text","text":"Subagent completed the task"}]},"agentId":"a2271d1"}\n',
encoding="utf-8",
)

# Write agent file in the NEW subagents directory (not in parent dir)
agent_file = subagents_dir / "agent-a2271d1.jsonl"
agent_file.write_text(
'{"parentUuid":null,"isSidechain":true,"userType":"external","cwd":"/workspace/test","sessionId":"test-subagents","version":"2.1.17","gitBranch":"main","agentId":"a2271d1","type":"user","message":{"role":"user","content":[{"type":"text","text":"Subagent task"}]},"uuid":"agent-s-0","timestamp":"2025-01-15T13:00:06.000Z"}\n'
'{"parentUuid":"agent-s-0","isSidechain":true,"userType":"external","cwd":"/workspace/test","sessionId":"test-subagents","version":"2.1.17","gitBranch":"main","agentId":"a2271d1","message":{"model":"claude-sonnet-4-5-20250929","id":"msg_agent_01","type":"message","role":"assistant","content":[{"type":"text","text":"Subagent completed the task"}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":50,"output_tokens":25}},"requestId":"req_agent_01","type":"assistant","uuid":"agent-s-1","timestamp":"2025-01-15T13:00:14.000Z"}\n',
encoding="utf-8",
)

# Verify agent file is NOT in the legacy location
legacy_agent = tmpdir_path / "agent-a2271d1.jsonl"
assert not legacy_agent.exists(), (
"Agent should NOT be in legacy location for this test"
)

# Load transcript - should find agent in subagents directory
messages = load_transcript(main_file)

# Should have 3 main + 2 agent messages = 5 total
assert len(messages) == 5, f"Expected 5 messages, got {len(messages)}"

# Verify agent messages were loaded and marked as sidechain
sidechain_count = sum(1 for m in messages if getattr(m, "isSidechain", False))
assert sidechain_count == 2, (
f"Expected 2 sidechain messages, got {sidechain_count}"
)

# Verify HTML generation works with subagent messages
html = generate_html(messages, title="Test Subagents Directory")
assert "Subagent completed the task" in html
Loading