Problem
The plugin marketplace cannot install a plugin that lives in a monorepo subdirectory.
resolveInstallSource (packages/agent-core-v2/src/app/plugin/source.ts:63) joins every path segment after /tree/ into the git ref:
if (head === 'tree' && rest.length >= 2) {
const refValue = decodeRefSegments(rest.slice(1)); // all segments -> ref
...
}
So https://github.com/owner/repo/tree/main/path/to/plugin resolves to ref main/path/to/plugin, and codeload fetches the wrong archive instead of checking out main and extracting path/to/plugin. Confirmed by the Codex review on #2944.
Why it matters
Any plugin hosted in a monorepo subdirectory (common when one plugin is shared across several agent hosts) cannot be listed without forking it into a standalone repo, which fragments a single source of truth. This affects monorepo-hosted plugins generally.
Proposal: encode the subdirectory in the source string (following pip / go-getter)
The established convention for "repo + subdirectory + ref" encodes the subdir in the source string with an explicit delimiter, avoiding the ref-vs-subdir ambiguity (a branch name can contain slashes, which is why the current code joins the whole tail as the ref):
- pip / PEP 508:
git+https://github.com/owner/repo@ref#subdirectory=path (pip docs)
- Terraform / go-getter:
github.com/owner/repo//modules/vpc?ref=v1.2.0 (Terraform module sources)
For Kimi's GitHub web-URL source form, the pip-style #subdirectory= fragment is the cleanest fit because it rides on the existing new URL() parse with no change to ref handling:
https://github.com/mem0ai/mem0/tree/main#subdirectory=integrations/mem0-plugin
Implementation:
source.ts: parseGithubUrl already builds a URL, so read url.hash (#subdirectory=<path>) and carry an optional subdir on the github ResolvedSource. The existing /tree/<ref> parsing is unchanged (url.pathname still yields ref main).
manager.ts (after extractZip): sourceRoot = path.join(sourceRoot, subdir) before parseManifest, and persist subdir on the plugin record so updates reuse it.
The marketplace entry stays a single source field:
{ "id": "mem0", "tier": "curated", "source": "https://github.com/mem0ai/mem0/tree/main#subdirectory=integrations/mem0-plugin" }
This keeps source a single string (consistent with the existing local-path / zip-url / github-url handling), matches a widely used convention, and is unambiguous. (A separate subdir entry field would also work, but the URL-encoded form is more consistent with the single-string source model.)
Offer
Happy to contribute the PR (source.ts + manager.ts + tests + docs + changeset). Relates to #2944 (adding a monorepo-hosted plugin to the marketplace). Which syntax do you prefer before I open it?
Problem
The plugin marketplace cannot install a plugin that lives in a monorepo subdirectory.
resolveInstallSource(packages/agent-core-v2/src/app/plugin/source.ts:63) joins every path segment after/tree/into the git ref:So
https://github.com/owner/repo/tree/main/path/to/pluginresolves to refmain/path/to/plugin, andcodeloadfetches the wrong archive instead of checking outmainand extractingpath/to/plugin. Confirmed by the Codex review on #2944.Why it matters
Any plugin hosted in a monorepo subdirectory (common when one plugin is shared across several agent hosts) cannot be listed without forking it into a standalone repo, which fragments a single source of truth. This affects monorepo-hosted plugins generally.
Proposal: encode the subdirectory in the
sourcestring (following pip / go-getter)The established convention for "repo + subdirectory + ref" encodes the subdir in the source string with an explicit delimiter, avoiding the ref-vs-subdir ambiguity (a branch name can contain slashes, which is why the current code joins the whole tail as the ref):
git+https://github.com/owner/repo@ref#subdirectory=path(pip docs)github.com/owner/repo//modules/vpc?ref=v1.2.0(Terraform module sources)For Kimi's GitHub web-URL
sourceform, the pip-style#subdirectory=fragment is the cleanest fit because it rides on the existingnew URL()parse with no change to ref handling:Implementation:
source.ts:parseGithubUrlalready builds aURL, so readurl.hash(#subdirectory=<path>) and carry an optionalsubdiron thegithubResolvedSource. The existing/tree/<ref>parsing is unchanged (url.pathnamestill yields refmain).manager.ts(afterextractZip):sourceRoot = path.join(sourceRoot, subdir)beforeparseManifest, and persistsubdiron the plugin record so updates reuse it.The marketplace entry stays a single
sourcefield:{ "id": "mem0", "tier": "curated", "source": "https://github.com/mem0ai/mem0/tree/main#subdirectory=integrations/mem0-plugin" }This keeps
sourcea single string (consistent with the existing local-path / zip-url / github-url handling), matches a widely used convention, and is unambiguous. (A separatesubdirentry field would also work, but the URL-encoded form is more consistent with the single-stringsourcemodel.)Offer
Happy to contribute the PR (
source.ts+manager.ts+ tests + docs + changeset). Relates to #2944 (adding a monorepo-hosted plugin to the marketplace). Which syntax do you prefer before I open it?