This is an automated email from the ASF dual-hosted git repository. hsluoyz pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git
commit 6582ca032bb4d9c1e12b4e90b3fe3b7918e384a4 Author: Yang Luo <[email protected]> AuthorDate: Fri Sep 4 20:25:53 2026 +0800 fix(agents): let a link name any agent and adopt an existing Hermes plugin --- agentpatch/claude_desktop.go | 18 ++++++++++++++++++ agentpatch/hermes_agent.go | 10 +++++----- object/import_link.go | 26 +++++++++++++++++--------- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/agentpatch/claude_desktop.go b/agentpatch/claude_desktop.go index c758900..7cc0985 100644 --- a/agentpatch/claude_desktop.go +++ b/agentpatch/claude_desktop.go @@ -146,6 +146,9 @@ func (p claudeDesktopPatcher) Status(target Target) (Status, error) { if !exists || !hasClaudeDesktopServer(config) { return Status{Detail: "MCP recorder is not registered"}, nil } + if !isCurrentClaudeDesktopServer(config) { + return Status{Detail: "MCP recorder needs refresh"}, nil + } if runtime.GOOS != "windows" { return Status{Patched: true, Detail: "MCP recorder registered; restart Claude Desktop to apply it"}, nil } @@ -211,6 +214,21 @@ func hasClaudeDesktopServer(config map[string]any) bool { return stringIndex(args, mcpserver.Subcommand) >= 0 && strings.Contains(strings.Join(args, " "), "--records-url") } +// isCurrentClaudeDesktopServer narrows hasClaudeDesktopServer to an entry this +// Gateway would write now. The broad test stays what Unpatch removes by. +func isCurrentClaudeDesktopServer(config map[string]any) bool { + servers, ok := objectAt(config["mcpServers"]) + if !ok { + return false + } + entry, ok := objectAt(servers[claudeDesktopServerName]) + if !ok { + return false + } + command, _ := entry["command"].(string) + return argsAreCurrent(command, stringArray(entry["args"])) +} + func readJSONObject(changes *ChangeSet, path string) (map[string]any, error) { data, err := changes.ReadFile(path) if err != nil { diff --git a/agentpatch/hermes_agent.go b/agentpatch/hermes_agent.go index b97d6de..3f3704a 100644 --- a/agentpatch/hermes_agent.go +++ b/agentpatch/hermes_agent.go @@ -89,16 +89,16 @@ func (p hermesPatcher) Patch(target Target) error { if err != nil { return err } - applied := IsApplied(target) if info, statErr := os.Stat(layout.pluginDir); statErr == nil { if !info.IsDir() { return fmt.Errorf("%s exists and is not a directory", layout.pluginDir) } - if !applied { - return fmt.Errorf("%s already exists and is not owned by this Gateway patch state", layout.pluginDir) - } + // What the directory holds decides this, not whether this Gateway's own + // state remembers writing it. A reinstall or a move to another data + // directory leaves a previous Gateway's observer behind, and refusing + // that one left Hermes unmonitorable with nothing to do about it. if hermesPluginForeign(layout.pluginDir) { - return fmt.Errorf("%s no longer contains a Gateway-owned observer; refusing to refresh it", layout.pluginDir) + return fmt.Errorf("%s holds an observer that is not Gateway's; remove that directory to let Gateway install its own", layout.pluginDir) } } else if !os.IsNotExist(statErr) { return statErr diff --git a/object/import_link.go b/object/import_link.go index 97d6b97..bd9f3a0 100644 --- a/object/import_link.go +++ b/object/import_link.go @@ -21,6 +21,8 @@ import ( "net/url" "strings" "unicode/utf8" + + "github.com/apache/casbin-gateway/agent" ) // The resources an "add this to Gateway" link can carry. The names are CC @@ -33,22 +35,25 @@ const ( ImportResourceSkill = "skill" ) -// importLinkAgents maps an app a link names to the agents on this host that -// read that app's own configuration. Gateway manages agents CC Switch does not -// and the other way round, so an app with nothing here is reported back rather -// than silently dropped: the page says which part of the link it cannot land. +// importLinkAgents translates the app names CC Switch links use into Gateway's +// own agent ids. Only the ones that differ are here: an app a link names by an +// id Gateway already knows resolves on its own, so every agent in the catalogue +// can be the target of a link without an entry of its own. // // Two ids appear where the same files are read under both — the Codex CLI and -// ChatGPT Desktop share ~/.codex, opencode and its desktop app share -// ~/.config/opencode — because the listing keeps one entry per location, under -// whichever of the two was found first. +// ChatGPT Desktop share ~/.codex, Cursor and its CLI share ~/.cursor, opencode +// and its desktop app share ~/.config/opencode — because the listing keeps one +// entry per location, under whichever of the two was found first. var importLinkAgents = map[string][]string{ "claude": {"claude-code"}, "codex": {"codex-cli", "codex"}, + "cursor": {"cursor", "cursor-agent"}, "gemini": {"gemini-cli"}, - "opencode": {"opencode", "opencode-desktop"}, - "openclaw": {"openclaw"}, "hermes": {"hermes-agent"}, + "iflow": {"iflow-cli"}, + "kimi": {"kimi-code"}, + "opencode": {"opencode", "opencode-desktop"}, + "qwen": {"qwen-code"}, } // ImportLink is what one link carries, read and handed to the page. Nothing in @@ -242,6 +247,9 @@ func linkTargets(apps []string) (targets []string, unknown []string) { continue } agentIds, known := importLinkAgents[app] + if !known && agent.IsKnownAgentId(app) { + agentIds, known = []string{app}, true + } if !known { unknown = append(unknown, app) continue --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
