UsageProcessor missed every user-typed slash invocation because the SLASH_RE regex (^\s*/<name>) expected a raw "/foo" prefix that Claude Code never writes. Real session jsonls wrap slash commands in a <command-name>/foo</command-name> XML tag inside user message content. Result on production: usage_events.command_name and usage_session_summary.slash_commands stayed NULL/0 for /clear, /exit, plugin commands like /plugin:name — verified on 17 dev-VM jsonls holding 25 <command-name> tags / 0 extracted rows. Replaces SLASH_RE with COMMAND_NAME_RE that searches for the tag anywhere in the user text (the tag sits after a <command-message> sibling). USAGE_PROCESSOR_VERSION bumps 2 → 3; operators wanting to rewrite historical rows under the new logic call POST /api/admin/usage/reprocess (agnes admin telemetry reprocess). Fixtures slash_command.jsonl, mixed.jsonl, skill_curated.jsonl rewritten from the unrealistic "/foo args" string format to the real <command-name> tag wrapper — existing assertions stay green against the new format, which is the regression baseline going forward. Adds TestCommandNameTagExtraction (4 unit tests on iter_events) covering string content, list-of-text-blocks content, mid-text tag position, and plain-prose "/x" non-match. Implicit Skill tool_use extraction (LLM-decided invocations) unchanged. Co-authored-by: Minas Arustamyan <arustamyan.minas@gmail.com>
12 lines
3.7 KiB
JSON
12 lines
3.7 KiB
JSON
{"type": "user", "uuid": "u1", "parentUuid": null, "sessionId": "sess-mixed", "timestamp": "2026-05-12T15:00:00.000Z", "cwd": "/workspace", "message": {"role": "user", "content": "<command-message>compound:debug</command-message>\n<command-name>/compound:debug</command-name>\n<command-args>start debugging</command-args>"}}
|
|
{"type": "assistant", "uuid": "a1", "parentUuid": "u1", "sessionId": "sess-mixed", "timestamp": "2026-05-12T15:00:01.000Z", "cwd": "/workspace", "message": {"role": "assistant", "model": "claude-sonnet-4-6", "content": [{"type": "tool_use", "id": "tu_bash_m", "name": "Bash", "input": {"command": "ls", "description": "List"}}]}}
|
|
{"type": "user", "uuid": "u2", "parentUuid": "a1", "sessionId": "sess-mixed", "timestamp": "2026-05-12T15:00:02.000Z", "cwd": "/workspace", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "tu_bash_m", "is_error": false, "content": [{"type": "text", "text": "file.txt"}]}]}}
|
|
{"type": "assistant", "uuid": "a2", "parentUuid": "u2", "sessionId": "sess-mixed", "timestamp": "2026-05-12T15:00:03.000Z", "cwd": "/workspace", "message": {"role": "assistant", "model": "claude-sonnet-4-6", "content": [{"type": "tool_use", "id": "tu_skill_m", "name": "Skill", "input": {"skill": "my-skill", "args": ""}}]}}
|
|
{"type": "user", "uuid": "u3", "parentUuid": "a2", "sessionId": "sess-mixed", "timestamp": "2026-05-12T15:00:04.000Z", "cwd": "/workspace", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "tu_skill_m", "is_error": false, "content": [{"type": "text", "text": "ok"}]}]}}
|
|
{"type": "assistant", "uuid": "a3", "parentUuid": "u3", "sessionId": "sess-mixed", "timestamp": "2026-05-12T15:00:05.000Z", "cwd": "/workspace", "message": {"role": "assistant", "model": "claude-sonnet-4-6", "content": [{"type": "tool_use", "id": "tu_mcp_m", "name": "mcp__github__create_issue", "input": {"title": "test"}}]}}
|
|
{"type": "user", "uuid": "u4", "parentUuid": "a3", "sessionId": "sess-mixed", "timestamp": "2026-05-12T15:00:06.000Z", "cwd": "/workspace", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "tu_mcp_m", "is_error": false, "content": [{"type": "text", "text": "created"}]}]}}
|
|
{"type": "assistant", "uuid": "a4", "parentUuid": "u4", "sessionId": "sess-mixed", "timestamp": "2026-05-12T15:00:07.000Z", "cwd": "/workspace", "message": {"role": "assistant", "model": "claude-sonnet-4-6", "content": [{"type": "tool_use", "id": "tu_task_m", "name": "Task", "input": {"subagent_type": "my-agent", "prompt": "do work"}}]}}
|
|
{"type": "user", "uuid": "u5", "parentUuid": "a4", "sessionId": "sess-mixed", "timestamp": "2026-05-12T15:00:08.000Z", "cwd": "/workspace", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "tu_task_m", "is_error": false, "content": [{"type": "text", "text": "done"}]}]}}
|
|
{"type": "assistant", "uuid": "a5", "parentUuid": "u5", "sessionId": "sess-mixed", "timestamp": "2026-05-12T15:00:09.000Z", "cwd": "/workspace", "message": {"role": "assistant", "model": "claude-sonnet-4-6", "content": [{"type": "tool_use", "id": "tu_err_m", "name": "Bash", "input": {"command": "cat /no", "description": "fail"}}]}}
|
|
{"type": "user", "uuid": "u6", "parentUuid": "a5", "sessionId": "sess-mixed", "timestamp": "2026-05-12T15:00:10.000Z", "cwd": "/workspace", "message": {"role": "user", "content": [{"type": "tool_result", "tool_use_id": "tu_err_m", "is_error": true, "content": [{"type": "text", "text": "error"}]}]}}
|
|
{"type": "assistant", "uuid": "a6", "parentUuid": "u6", "sessionId": "sess-mixed", "timestamp": "2026-05-12T15:00:11.000Z", "cwd": "/workspace", "message": {"role": "assistant", "model": "claude-sonnet-4-6", "content": [{"type": "text", "text": "All done."}]}}
|