detectAgent identifies the agent type from its tool set. Each agent has a distinctive combination of tool names.
(tools []observedtools.ObservedTool)
| 206 | // detectAgent identifies the agent type from its tool set. |
| 207 | // Each agent has a distinctive combination of tool names. |
| 208 | func detectAgent(tools []observedtools.ObservedTool) AgentType { |
| 209 | nameSet := make(map[string]bool, len(tools)) |
| 210 | for _, t := range tools { |
| 211 | nameSet[t.Name] = true |
| 212 | } |
| 213 | |
| 214 | // OpenClaw: has exec + process (unique combination — no other agent has both) |
| 215 | if nameSet["exec"] && nameSet["process"] { |
| 216 | return AgentOpenClaw |
| 217 | } |
| 218 | // Claude Code: has Bash + Read + Write + Glob + Grep (capital names) |
| 219 | if nameSet["Bash"] && nameSet["Read"] && nameSet["Write"] { |
| 220 | return AgentClaudeCode |
| 221 | } |
| 222 | // Codex CLI: has shell or shell_command + apply_patch (v0.112+), or shell + read_file (older) |
| 223 | if nameSet["shell_command"] && nameSet["apply_patch"] { |
| 224 | return AgentCodexCLI |
| 225 | } |
| 226 | if nameSet["shell"] && nameSet["read_file"] { |
| 227 | return AgentCodexCLI |
| 228 | } |
| 229 | // Cursor: has run_command + create_file |
| 230 | if nameSet["run_command"] && nameSet["create_file"] { |
| 231 | return AgentCursor |
| 232 | } |
| 233 | // Windsurf: has shell_command |
| 234 | if nameSet["shell_command"] && nameSet["read_file"] { |
| 235 | return AgentWindsurf |
| 236 | } |
| 237 | // Cline: has execute_command + read_file + write_file |
| 238 | if nameSet["execute_command"] && nameSet["read_file"] { |
| 239 | return AgentCline |
| 240 | } |
| 241 | |
| 242 | return AgentUnknown |
| 243 | } |
| 244 | |
| 245 | // RecordToolsDirect sets the session's tools from a pre-built slice (for testing). |
| 246 | func (s *Session) RecordToolsDirect(tools []observedtools.ObservedTool) { |
no outgoing calls