Skip to content

feat: install plugins from GitLab repositories#1801

Open
Ecthlion wants to merge 4 commits into
MoonshotAI:mainfrom
Ecthlion:agent/gitlab-plugin-install
Open

feat: install plugins from GitLab repositories#1801
Ecthlion wants to merge 4 commits into
MoonshotAI:mainfrom
Ecthlion:agent/gitlab-plugin-install

Conversation

@Ecthlion

Copy link
Copy Markdown

Related Issue

No linked issue.

Problem

For necessary organizational, security, or distribution constraints, some plugins can only be hosted on internal GitLab instances. Kimi Code CLI currently resolves repository URLs only for GitHub, so those plugins require a separate manual download and extraction workflow.

Supporting GitLab directly through /plugins install <url> keeps plugin documentation and installation instructions consistent across hosting providers and makes internal plugins substantially more convenient to install.

What changed

  • Recognize GitLab.com and self-managed GitLab repository URLs, including nested namespaces and HTTPS clone URLs ending in .git.
  • Support bare repository URLs, /-/tree/<ref>, /-/releases/<tag>, and /-/commit/<sha> forms. Bare URLs select the latest release and fall back to the default branch when no release exists.
  • Resolve GitLab releases and zip archives through the selected instance's /api/v4 endpoints in both plugin engines.
  • Keep the installed-plugin persistence schema unchanged by recording GitLab installs through the existing URL-backed source path.
  • Update the /plugins Custom-tab hint and the mirrored English/Chinese plugin documentation.
  • Add parser, resolver, and end-to-end manager coverage for both plugin engines, plus a patch changeset.

Authentication behavior is unchanged: the GitLab instance and repository must already be reachable from the CLI. This PR does not add token or credential storage.

Validation

  • vitest targeted plugin/TUI suite: 7 files, 126 tests passed.
  • TypeScript typechecks passed for @moonshot-ai/agent-core, @moonshot-ai/agent-core-v2, and @moonshot-ai/kimi-code.
  • Production builds passed for both agent-core packages and the CLI.
  • pnpm lint completed with 0 errors (the repository currently reports existing warnings outside this change).
  • VitePress documentation build passed; it reports the existing ES2024 target warnings.
  • Live GitLab.com checks confirmed latest-release resolution and a 200 application/zip repository archive response with ref_type=tags.
  • pnpm test is blocked only in 25 existing MCP stdio tests across two files: this checkout path contains a space, and those tests incorrectly resolve fixture paths with %20. The suite completed after excluding those two path-sensitive files.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

The gen-docs workflow's referenced docs/scripts/sync-changelog.mjs prerequisite is not present in this checkout. No changelog files were changed; the bilingual page pair was reviewed directly and the docs site was built successfully.

@changeset-bot

changeset-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 432b295

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Ecthlion
Ecthlion marked this pull request as ready for review July 17, 2026 03:06

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cc286658b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +55 to +56
const isGitlabHost = url.hostname.toLowerCase().split('.').includes('gitlab');
if (!isGitlabHost && separatorIndex === -1 && !hasGitSuffix) return undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recognize hostnames that contain GitLab

For a bare repository URL on hosts such as mygitlab.example.com or gitlab01.example.com, this exact-label check returns false even though the documented detection rule says hostnames containing gitlab are recognized. Without a /-/… route or .git suffix, the source falls through to zip-url, so installation downloads the GitLab HTML page and then fails to extract it as a ZIP. The legacy resolver mirrors the same predicate.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—fixed in cdfbfab. Hostname detection now recognizes any hostname containing gitlab, and regression coverage in both plugin implementations includes mygitlab.example.com and gitlab01.example.com.

Comment on lines +62 to +63
const projectPath = decodeRefSegments([...projectSegments.slice(0, -1), repo]);
const base = { kind: 'gitlab' as const, baseUrl: url.origin, projectPath };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve self-managed GitLab path prefixes

When a self-managed instance is served below a relative URL, such as https://code.example.com/gitlab, its documented HTTPS clone URL https://code.example.com/gitlab/team/sample.git is parsed with baseUrl reduced to the origin and gitlab folded into projectPath. The resolver consequently requests /api/v4/projects/gitlab%2Fteam%2Fsample instead of the instance endpoint /gitlab/api/v4/projects/team%2Fsample, so these supported self-managed installs fail. Preserve or otherwise resolve the instance path prefix; the legacy parser has the same issue.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this. Supporting arbitrary relative URL roots requires distinguishing the instance path prefix from the project namespace, which cannot be determined unambiguously from the clone URL alone. To keep this PR small and its support scope clear, cdfbfab narrows the English and Chinese docs to self-managed GitLab instances served from the origin root and explicitly notes that relative URL roots are not currently supported. Full relative-root discovery/support can be handled separately.

@Ecthlion

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cdfbfab3a0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

): Promise<string | undefined> {
const projectId = encodeURIComponent(input.projectPath);
const url = `${input.baseUrl}/api/v4/projects/${projectId}/releases/permalink/latest`;
const resp = await fetch(url);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound the legacy latest-release request

When installing a bare GitLab repository URL through the legacy engine, this preliminary release lookup has no abort signal, so a self-managed server that accepts the request but stalls can leave /plugins install pending indefinitely before the bounded archive download begins. The v2 implementation already applies a 10-second timeout; apply an equivalent timeout here.

Useful? React with 👍 / 👎.

@@ -0,0 +1,51 @@
import { afterEach, describe, expect, it, vi } from 'vitest';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Add the required test scenario header

The applicable agent-core-review/test guidance requires every new v2 test file to start with a header describing the scenario, responsibilities, real versus stubbed wiring, and the command to run it. This new file starts directly with imports, so add that header for consistency with the v2 test contract.

Useful? React with 👍 / 👎.

});

it('uses the latest release or falls back to the default branch', async () => {
const fetch = vi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Split release and fallback resolver scenarios

This test verifies two distinct behaviors—selecting a latest release and falling back after a 404—using ordered mock responses in one it, contrary to the applicable one-behavior-per-test rule. Split them so each failure identifies the broken contract directly and neither scenario depends on the other consuming the first mocked response.

Useful? React with 👍 / 👎.

});
});

it('resolves GitLab.com and self-managed GitLab URLs', () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Separate GitLab source recognition conditions

This it combines GitLab.com, hostname-based self-managed detection, and .git-suffix detection, which are separate parser conditions and violates the applicable one-behavior-per-test guidance. Convert these cases to individually named tests or an it.each table whose case names identify the condition and expected result.

Useful? React with 👍 / 👎.

@Ecthlion

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5a082100e8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return { ...base, ref: { kind, value } };
}

if (head === 'releases' && rest.length >= 2) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve GitLab release asset URLs as zip sources

When an existing zip source is a GitLab release asset such as https://gitlab.example.com/team/plugin/-/releases/v1/downloads/plugin.zip, this broad condition treats every segment after releases as the tag and requests a repository archive for v1/downloads/plugin.zip instead of downloading the supplied zip. Such URLs previously worked through the generic zip-url path, so require the release form to end after the tag (and apply the same fix to the mirrored legacy parser).

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant