mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
Add OpenCode to Agents spaces (#1354)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ea2cf33fd3
commit
e685762012
6 changed files with 87 additions and 34 deletions
|
|
@ -388,6 +388,13 @@ export function MemoriesGrid({
|
|||
enabled: !!user && showAgentFilters,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedAgentSource || !agentSourceCounts) return
|
||||
if ((agentSourceCounts[selectedAgentSource] ?? 0) === 0) {
|
||||
void setSelectedAgentSource(null)
|
||||
}
|
||||
}, [agentSourceCounts, selectedAgentSource, setSelectedAgentSource])
|
||||
|
||||
const {
|
||||
data,
|
||||
error,
|
||||
|
|
@ -700,22 +707,25 @@ export function MemoriesGrid({
|
|||
aria-label="Filter memories by agent"
|
||||
className="gap-1.5"
|
||||
>
|
||||
{AGENT_SOURCE_FILTERS.map((filter) => (
|
||||
<ToggleGroupItem
|
||||
key={filter.value}
|
||||
value={filter.value}
|
||||
aria-label={`Show ${filter.label} memories`}
|
||||
className={cn(
|
||||
dmSansClassName(),
|
||||
"h-auto min-w-0 flex-none shrink-0 rounded-full! border border-[#161F2C]! bg-[#0D121A] px-2.5 py-1 text-xs hover:border-[#2261CA33]! hover:bg-[#00173C] data-[state=on]:border-[#2261CA33]! data-[state=on]:bg-[#00173C]",
|
||||
)}
|
||||
>
|
||||
{filter.label}
|
||||
<span className="ml-1 text-[#737373]">
|
||||
({agentSourceCounts?.[filter.value] ?? 0})
|
||||
</span>
|
||||
</ToggleGroupItem>
|
||||
))}
|
||||
{AGENT_SOURCE_FILTERS.map((filter) => {
|
||||
const count = agentSourceCounts?.[filter.value]
|
||||
if (!count) return null
|
||||
|
||||
return (
|
||||
<ToggleGroupItem
|
||||
key={filter.value}
|
||||
value={filter.value}
|
||||
aria-label={`Show ${filter.label} memories`}
|
||||
className={cn(
|
||||
dmSansClassName(),
|
||||
"h-auto min-w-0 flex-none shrink-0 rounded-full! border border-[#161F2C]! bg-[#0D121A] px-2.5 py-1 text-xs hover:border-[#2261CA33]! hover:bg-[#00173C] data-[state=on]:border-[#2261CA33]! data-[state=on]:bg-[#00173C]",
|
||||
)}
|
||||
>
|
||||
{filter.label}
|
||||
<span className="ml-1 text-[#737373]">({count})</span>
|
||||
</ToggleGroupItem>
|
||||
)
|
||||
})}
|
||||
</ToggleGroup>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ type Category = {
|
|||
count: number
|
||||
}
|
||||
|
||||
const AGENT_CATALOG_IDS = ["claude_code", "codex"] as const
|
||||
const AGENT_CATALOG_IDS = ["claude_code", "codex", "opencode"] as const
|
||||
|
||||
export function SelectSpacesModal({
|
||||
isOpen,
|
||||
|
|
@ -1668,7 +1668,7 @@ function AgentsDiscoverPanel({
|
|||
if (catalogIds.length === 0) {
|
||||
return (
|
||||
<p className="py-8 text-center text-sm text-[#737373]">
|
||||
Claude Code and Codex are connected.
|
||||
Claude Code, Codex, and OpenCode are connected.
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
|
@ -1682,7 +1682,7 @@ function AgentsDiscoverPanel({
|
|||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-semibold text-[#FAFAFA]">Agents</p>
|
||||
<p className="text-[11px] text-[#737373]">
|
||||
Claude Code and Codex share project memory
|
||||
Claude Code, Codex, and OpenCode share project memory
|
||||
</p>
|
||||
</div>
|
||||
{catalogIds.map((catalogId) => {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
} from "./agent-space"
|
||||
|
||||
describe("Agents spaces", () => {
|
||||
it("recognizes only Claude and Codex shared and legacy tags", () => {
|
||||
it("recognizes Claude, Codex, and OpenCode shared and legacy tags", () => {
|
||||
expect(isAgentContainerTag("repo_supermemory__0123456789abcdef")).toBe(true)
|
||||
expect(isAgentContainerTag("user_project_0123456789abcdef")).toBe(true)
|
||||
expect(isAgentContainerTag("repo_supermemory")).toBe(true)
|
||||
|
|
@ -16,7 +16,8 @@ describe("Agents spaces", () => {
|
|||
)
|
||||
expect(isAgentContainerTag("codex_project_0123456789abcdef")).toBe(true)
|
||||
expect(isAgentContainerTag("codex_user_0123456789abcdef")).toBe(true)
|
||||
expect(isAgentContainerTag("opencode_project_0123456789abcdef")).toBe(false)
|
||||
expect(isAgentContainerTag("opencode_project_0123456789abcdef")).toBe(true)
|
||||
expect(isAgentContainerTag("opencode_user_0123456789abcdef")).toBe(true)
|
||||
})
|
||||
|
||||
it("shows agent filters only for an Agents selection", () => {
|
||||
|
|
@ -38,6 +39,7 @@ describe("Agents spaces", () => {
|
|||
"claude-code-plugin",
|
||||
])
|
||||
expect(agentSourceValues("codex")).toEqual(["codex"])
|
||||
expect(agentSourceValues("opencode")).toEqual(["opencode"])
|
||||
expect(agentSourceValues(null)).toBeUndefined()
|
||||
})
|
||||
|
||||
|
|
@ -46,6 +48,7 @@ describe("Agents spaces", () => {
|
|||
{ containerTag: "repo_supermemory__fedcba9876543210" },
|
||||
{ containerTag: "repo_supermemory" },
|
||||
{ containerTag: "codex_project_0123456789abcdef" },
|
||||
{ containerTag: "opencode_project_0123456789abcdef" },
|
||||
{ containerTag: "claudecode_project_0123456789abcdef" },
|
||||
{ containerTag: "user_project_0123456789abcdef" },
|
||||
]
|
||||
|
|
@ -69,6 +72,7 @@ describe("Agents spaces", () => {
|
|||
"claudecode_project_0123456789abcdef",
|
||||
"repo_supermemory",
|
||||
"codex_project_0123456789abcdef",
|
||||
"opencode_project_0123456789abcdef",
|
||||
])
|
||||
})
|
||||
|
||||
|
|
@ -110,10 +114,31 @@ describe("Agents spaces", () => {
|
|||
expect(groups[1]?.kind).toBe("legacy-personal")
|
||||
})
|
||||
|
||||
it("keeps the old global OpenCode personal container separate", () => {
|
||||
const projects = [
|
||||
{ containerTag: "user_project_0123456789abcdef" },
|
||||
{ containerTag: "opencode_user_fedcba9876543210" },
|
||||
]
|
||||
const metadata = new Map(
|
||||
projects.map((project) => [
|
||||
project.containerTag,
|
||||
{ projectName: "supermemory" },
|
||||
]),
|
||||
)
|
||||
|
||||
const groups = groupAgentSpaces(projects, metadata)
|
||||
|
||||
expect(groups).toHaveLength(2)
|
||||
expect(groups[0]?.label).toBe("supermemory")
|
||||
expect(groups[1]?.label).toBe("Legacy OpenCode personal")
|
||||
expect(groups[1]?.kind).toBe("legacy-personal")
|
||||
})
|
||||
|
||||
it("groups path-scoped legacy tags even before metadata loads", () => {
|
||||
const projects = [
|
||||
{ containerTag: "claudecode_project_0123456789abcdef" },
|
||||
{ containerTag: "codex_project_0123456789abcdef" },
|
||||
{ containerTag: "opencode_project_0123456789abcdef" },
|
||||
]
|
||||
|
||||
const groups = groupAgentSpaces(projects, new Map())
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export type AgentContainerKind =
|
|||
| "legacy-personal"
|
||||
| "legacy-project"
|
||||
|
||||
export type AgentSourceFilter = "claude-code" | "codex"
|
||||
export type AgentSourceFilter = "claude-code" | "codex" | "opencode"
|
||||
|
||||
export const AGENT_SOURCE_FILTERS: ReadonlyArray<{
|
||||
value: AgentSourceFilter
|
||||
|
|
@ -18,6 +18,7 @@ export const AGENT_SOURCE_FILTERS: ReadonlyArray<{
|
|||
sources: ["claude-code", "claude-code-plugin"],
|
||||
},
|
||||
{ value: "codex", label: "Codex", sources: ["codex"] },
|
||||
{ value: "opencode", label: "OpenCode", sources: ["opencode"] },
|
||||
]
|
||||
|
||||
export type AgentSpaceMetadata = {
|
||||
|
|
@ -54,6 +55,14 @@ const TAG_PATTERNS: Array<{
|
|||
kind: "legacy-project",
|
||||
pattern: /^codex_project_([0-9a-f]{6,64})$/i,
|
||||
},
|
||||
{
|
||||
kind: "legacy-personal",
|
||||
pattern: /^opencode_user_([0-9a-f]{6,64})$/i,
|
||||
},
|
||||
{
|
||||
kind: "legacy-project",
|
||||
pattern: /^opencode_project_([0-9a-f]{6,64})$/i,
|
||||
},
|
||||
]
|
||||
|
||||
function matchAgentTag(containerTag: string): {
|
||||
|
|
@ -137,13 +146,17 @@ function legacyGroupIdentity(
|
|||
return { key: `tag:${containerTag}`, label: containerTag, kind: "project" }
|
||||
}
|
||||
|
||||
// Old Codex personal memory was intentionally global. Even if its newest
|
||||
// document contains a project name, assigning the whole container to that
|
||||
// project would leak memories from its other historical projects.
|
||||
if (containerTag.startsWith("codex_user_")) {
|
||||
// Old Codex and OpenCode personal containers were intentionally global.
|
||||
// Even if the newest document has a project name, assigning the whole
|
||||
// container to that project would mix memories from historical projects.
|
||||
if (
|
||||
containerTag.startsWith("codex_user_") ||
|
||||
containerTag.startsWith("opencode_user_")
|
||||
) {
|
||||
const agent = containerTag.startsWith("codex_user_") ? "Codex" : "OpenCode"
|
||||
return {
|
||||
key: `legacy-personal:${containerTag}`,
|
||||
label: "Legacy Codex personal",
|
||||
label: `Legacy ${agent} personal`,
|
||||
kind: "legacy-personal",
|
||||
}
|
||||
}
|
||||
|
|
@ -159,7 +172,8 @@ function legacyGroupIdentity(
|
|||
if (
|
||||
containerTag.startsWith("user_project_") ||
|
||||
containerTag.startsWith("claudecode_project_") ||
|
||||
containerTag.startsWith("codex_project_")
|
||||
containerTag.startsWith("codex_project_") ||
|
||||
containerTag.startsWith("opencode_project_")
|
||||
) {
|
||||
return {
|
||||
key: `path:${match.id.toLocaleLowerCase()}`,
|
||||
|
|
@ -211,9 +225,9 @@ function addProjectToGroup<T extends { containerTag: string }>(
|
|||
}
|
||||
|
||||
/**
|
||||
* Collapse the physical Claude/Codex containers into one selectable Agents row
|
||||
* per project. Every returned container tag remains real; the UI never writes
|
||||
* to a synthetic "agents" tag.
|
||||
* Collapse the physical Claude/Codex/OpenCode containers into one selectable
|
||||
* Agents row per project. Every returned container tag remains real; the UI
|
||||
* never writes to a synthetic "agents" tag.
|
||||
*/
|
||||
export function groupAgentSpaces<T extends { containerTag: string }>(
|
||||
projects: T[],
|
||||
|
|
@ -256,9 +270,12 @@ export function groupAgentSpaces<T extends { containerTag: string }>(
|
|||
const canonicalMatches = projectName
|
||||
? (canonicalKeysByName.get(projectName.toLocaleLowerCase()) ?? [])
|
||||
: []
|
||||
const firstCanonical = canonicalMatches[0]
|
||||
const key =
|
||||
identity.kind === "project" && canonicalMatches.length === 1
|
||||
? canonicalMatches[0]!
|
||||
identity.kind === "project" &&
|
||||
canonicalMatches.length === 1 &&
|
||||
firstCanonical !== undefined
|
||||
? firstCanonical
|
||||
: identity.key
|
||||
addProjectToGroup(
|
||||
grouped,
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const PLUGINS: PluginDef[] = [
|
|||
id: "agents",
|
||||
label: "Agents",
|
||||
iconSrc: null,
|
||||
prefixes: ["user_project", "repo", "claudecode", "codex"],
|
||||
prefixes: ["user_project", "repo", "claudecode", "codex", "opencode"],
|
||||
},
|
||||
{
|
||||
id: "openclaw",
|
||||
|
|
|
|||
|
|
@ -62,5 +62,6 @@ export const categoriesParam = parseAsArrayOf(parseAsString, ",").withDefault(
|
|||
export const agentSourceParam = parseAsStringLiteral([
|
||||
"claude-code",
|
||||
"codex",
|
||||
"opencode",
|
||||
] as const)
|
||||
export const projectParam = parseAsArrayOf(parseAsString, ",").withDefault([])
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue