Skip to content

feat(vscode): Marketplace#341

Open
bernaferrari wants to merge 17 commits intoKilo-Org:devfrom
bernaferrari:marketplace
Open

feat(vscode): Marketplace#341
bernaferrari wants to merge 17 commits intoKilo-Org:devfrom
bernaferrari:marketplace

Conversation

@bernaferrari
Copy link
Contributor

@bernaferrari bernaferrari commented Feb 16, 2026

There are a few minor differences with Kilo current extension:

  • You can apply mcps to project or globally
  • You can specify the API on the marketplace screen

If you want full fidelity I can do that too. I think project/globally is probably a feature that opencode provides, so not sure we want or not to use it.

The only thing missing: I need to translate some strings into every other language. I just want to be sure you like the PR before I do that.

@github-actions
Copy link
Contributor

Hey! Your PR title feat(vscode) Marketplace doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, kilo).

See CONTRIBUTING.md for details.

@bernaferrari bernaferrari changed the title feat(vscode) Marketplace feat(vscode): Marketplace Feb 16, 2026
@github-actions
Copy link
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

* Fetch extension settings for the authenticated user/organization.
* These settings include org-managed Marketplace policy fields.
*/
export async function fetchExtensionSettings(
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure where this came from, if it's in the plan or based no the current extension, but we dont actually have org settings in our current marketplace. Roo does, and the code is there in the kilocode extension, but that is actually dead. Can you remove all this organization checking logic?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I'll get rid of that

Copy link
Contributor

@markijbema markijbema left a comment

Choose a reason for hiding this comment

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

The org stuff shouldnt be there, and those are also the changes that span extension and cli-backend. Can you please revert those? Then there is some small stuff, but I can do that postmerge as well if you want (like adding other translations, which isnt hard but just costs a lot of tokens)

@bernaferrari
Copy link
Contributor Author

Yes, I even did that in a separate commit, but without them the marketplace won't work, is that OK?

@markijbema
Copy link
Contributor

Why wouldnt it work, the marketplace is publicly available endpoints like http://app.kilo.ai/api/marketplace/mcps

There was functionality in the old extension to disable certain parts of the marketplace for orgs (which never did anything in kilo, but was from roo), but the marketplace should just work fine as is?

@bernaferrari
Copy link
Contributor Author

I'm still a noob on this. Got it working. Thanks for your patience. I also fixed translations. I made another PR (much simpler) improving the profile UI while trying to not mess with server.

@bernaferrari bernaferrari marked this pull request as ready for review February 17, 2026 19:26
"command.permissions.autoaccept.enable": "قبول التعديلات تلقائيًا",
"command.permissions.autoaccept.disable": "إيقاف قبول التعديلات تلقائيًا",
"command.workspace.toggle": "تبديل مساحات العمل",
"command.workspace.toggle.description": "Enable or disable multiple workspaces in the sidebar",
Copy link
Contributor

Choose a reason for hiding this comment

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

the 118n works like a cascade (like css); so we have translations in the extension, fall back to kilo i18n fall back to i18n, but i18n should never differ from upstream, so we dont want to make changes here, only in kilo i18n/vscode i18n.

We probably need to add some checks to make this clearer though as you couldnt've known

backendBase,
DEFAULT_API_BASE_URL,
DEFAULT_BACKEND_BASE_URL,
]
Copy link
Contributor

@markijbema markijbema Feb 18, 2026

Choose a reason for hiding this comment

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

this seems too complex, but that might be due to our original extension being to complex here. We should avoid this now though

}

async removeItem(item: MarketplaceItem, target: "project" | "global"): Promise<void> {
if (item.managedByOrganization) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be gone

throw new Error(`Marketplace request failed for ${route}.${detail}`)
}

private parseStructured(text: string): unknown {
Copy link
Contributor

Choose a reason for hiding this comment

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

this seems to complex, just try to parse as json, or give up

private getProjectKiloDirectoryPath(): string {
const workspaceDir = this.getWorkspaceDir()
const kiloDir = path.join(workspaceDir, ".kilocode")
const legacyDir = path.join(workspaceDir, ".roo")
Copy link
Contributor

Choose a reason for hiding this comment

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

we definitely dont want to keep supporting .roo dirs

}
}
} catch {
// Fallback to item.id if content is invalid.
Copy link
Contributor

Choose a reason for hiding this comment

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

lets not do that, fail if it is invalid


const config = { ...(input as Record<string, unknown>) }

if (config.command !== undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need checks like this; kilo maintains the marketplace and this should be checked in the marketplace repo

@@ -0,0 +1,17 @@
declare module "tar-fs" {
Copy link
Contributor

Choose a reason for hiding this comment

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

i would expect we can import some types

vscode.postMessage({ type: "requestMarketplaceData" })
}

let lastOrganizationRefreshKey: string | undefined
Copy link
Contributor

Choose a reason for hiding this comment

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

I would expect this to be redundant now as well

</Show>

<Show
when={!managedByOrganization}
Copy link
Contributor

Choose a reason for hiding this comment

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

this condition should be gone

@markijbema
Copy link
Contributor

I think it looks good overall, there is quite some notes i made but I'll take a stab at fixing them so we can get this merged

})

requestData()
return () => unsubscribe()
Copy link
Contributor

Choose a reason for hiding this comment

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

WARNING: Memory leak — SolidJS onMount does not support cleanup return values

Unlike React's useEffect, SolidJS's onMount ignores the return value. This means () => unsubscribe() is never called and the message listener accumulates on every component mount/unmount cycle.

Use onCleanup instead:

Suggested change
return () => unsubscribe()
onCleanup(() => unsubscribe())

(You'll also need to remove the return and call onCleanup before the closing }) of onMount, or move the subscription setup into a createEffect with onCleanup.)

type: z.literal("skill"),
category: z.string(),
githubUrl: z.string().url(),
content: z.string().min(1),
Copy link
Contributor

Choose a reason for hiding this comment

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

WARNING: Skill content field used as tarball download URL but not validated as a URL

In installSkill(), item.content is passed directly to fetch(tarballUrl) (marketplace-service.ts:650). However, this schema only validates it as z.string().min(1), not as a valid URL. If the catalog data were ever compromised, this could allow SSRF by pointing to internal network resources.

Consider changing to:

Suggested change
content: z.string().min(1),
content: z.string().url(),

return `${currentOrgId}|${orgNames}`
})

createEffect(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

SUGGESTION: Telemetry event fires on initial render

This createEffect tracks activeTab() and fires a "Marketplace Tab Viewed" telemetry event. Since SolidJS effects run immediately on creation, this will fire for the default "mcp" tab on every component mount — even before the user has interacted with any tab. Consider guarding against the initial run if that's not intentional:

let initialized = false
createEffect(() => {
  const tab = activeTab()
  if (!initialized) {
    initialized = true
    return
  }
  vscode.postMessage({ ... })
})

@kiloconnect
Copy link
Contributor

kiloconnect bot commented Feb 18, 2026

Code Review Summary

Status: No New Issues Found | Recommendation: Address existing comments before merge

Overview

This PR adds a full Marketplace feature to the VS Code extension, including:

  • A MarketplaceService for fetching, installing, and removing marketplace items (modes, MCP servers, skills)
  • A MarketplaceView SolidJS component with filtering, search, and install/remove actions
  • Type definitions and Zod schemas for marketplace data
  • Integration into KiloProvider for webview ↔ extension communication
  • i18n translations for 15+ languages

The implementation is generally well-structured with good input validation (Zod schemas, safe tar path checking, marketplace ID sanitization) and proper error handling throughout.

Existing Comments Summary

The 20 existing inline comments cover the key issues. Several have already been addressed by subsequent commits (merge from dev). The remaining actionable items are:

Severity Count Status
WARNING 5 From prior review — some addressed by merge
SUGGESTION 2 From prior review
Conversational 13 Team discussion threads
Key Issues from Prior Review (click to expand)

WARNING

File Line Issue Status
marketplace-service.ts 468 _selectedIndex parameter key collision with user parameters Open
marketplace-service.ts 552 No URL scheme validation before fetching skill tarball Open
marketplace-service.ts 564 No response size limit on tarball download Open
marketplace-service.ts 598 replaceTemplateToken performs naive string substitution Open
schema.ts N/A Skill content field used as tarball download URL — naming is misleading Open

SUGGESTION

File Line Issue Status
MarketplaceView.tsx 67 Telemetry event fires on initial render (not just tab switch) Open
MarketplaceView.tsx 122 Redundant data re-fetch after successful install/remove Open
Other Observations (not in diff)

Issues found in code added by the merge commit (outside the PR diff range):

File Line Issue
marketplace-service.ts 335-353 Legacy .roo directory fallback — per team discussion, this should be removed
marketplace-service.ts 232-265 parseStructured has complex double-parsing logic (JSON → string → JSON/YAML) — consider simplifying to just JSON parse
KiloProvider.ts 587 handleTelemetryEvent only logs to console.debug — telemetry events are not actually sent anywhere
KiloProvider.ts 591 handleRequestMarketplaceData declares an errors array that is never populated (dead code)
Files Reviewed (27 files)
  • bun.lock — dependency additions (tar-fs, yaml)
  • packages/kilo-vscode/package.json — new dependencies
  • packages/kilo-vscode/src/KiloProvider.ts — marketplace message handling integration
  • packages/kilo-vscode/src/services/marketplace/index.ts — barrel export (new file)
  • packages/kilo-vscode/src/services/marketplace/marketplace-service.ts — core service (new file)
  • packages/kilo-vscode/src/services/marketplace/schema.ts — Zod schemas (new file)
  • packages/kilo-vscode/src/services/marketplace/types.ts — TypeScript types (new file)
  • packages/kilo-vscode/src/types/tar-fs.d.ts — type declarations (new file)
  • packages/kilo-vscode/webview-ui/src/App.tsx — marketplace view integration
  • packages/kilo-vscode/webview-ui/src/components/MarketplaceView.tsx — UI component (new file)
  • packages/kilo-vscode/webview-ui/src/types/messages.ts — message type definitions
  • packages/kilo-vscode/webview-ui/src/i18n/en.ts — English translations
  • packages/kilo-vscode/webview-ui/src/i18n/ar.ts — Arabic translations
  • packages/kilo-vscode/webview-ui/src/i18n/br.ts — Brazilian Portuguese translations
  • packages/kilo-vscode/webview-ui/src/i18n/bs.ts — Bosnian translations
  • packages/kilo-vscode/webview-ui/src/i18n/da.ts — Danish translations
  • packages/kilo-vscode/webview-ui/src/i18n/de.ts — German translations
  • packages/kilo-vscode/webview-ui/src/i18n/es.ts — Spanish translations
  • packages/kilo-vscode/webview-ui/src/i18n/fr.ts — French translations
  • packages/kilo-vscode/webview-ui/src/i18n/ja.ts — Japanese translations
  • packages/kilo-vscode/webview-ui/src/i18n/ko.ts — Korean translations
  • packages/kilo-vscode/webview-ui/src/i18n/no.ts — Norwegian translations
  • packages/kilo-vscode/webview-ui/src/i18n/pl.ts — Polish translations
  • packages/kilo-vscode/webview-ui/src/i18n/ru.ts — Russian translations
  • packages/kilo-vscode/webview-ui/src/i18n/th.ts — Thai translations
  • packages/kilo-vscode/webview-ui/src/i18n/zh.ts — Chinese Simplified translations
  • packages/kilo-vscode/webview-ui/src/i18n/zht.ts — Chinese Traditional translations

Fix these issues in Kilo Cloud

Moved translation keys from packages/app/src/i18n/ to
packages/kilo-vscode/webview-ui/src/i18n/ where they belong.
Reverted app/src/i18n/ to match origin/dev baseline.
SolidJS onMount does not support cleanup return values like React's
useEffect. The unsubscribe callback was never called, causing a memory
leak. Use onCleanup() inside onMount instead.
The skill content field is used as a tarball download URL passed to
fetch(). Validate it as z.string().url() instead of z.string().min(1)
to prevent potential SSRF if catalog data were compromised.
SolidJS createEffect runs immediately on creation, which fires a
Marketplace Tab Viewed telemetry event for the default tab before
any user interaction. Guard the initial run so telemetry only fires
on actual tab switches.
private async installMcp(item: Extract<MarketplaceItem, { type: "mcp" }>, options: MarketplaceInstallOptions): Promise<void> {
const filePath = this.mcpFilePath(options.target)
const mcpId = this.sanitizeMarketplaceItemId(item.id, "MCP server")
const selectedFromParameters = options.parameters?._selectedIndex
Copy link
Contributor

Choose a reason for hiding this comment

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

WARNING: _selectedIndex parameter key collision with user-provided parameters

The fallback options.parameters?._selectedIndex reads from the same parameters object that contains user-provided MCP parameter values. If a marketplace MCP item defines a parameter with key _selectedIndex, the user's value for that parameter would be misinterpreted as an installation method index, silently selecting the wrong installation method.

Consider using a dedicated field on MarketplaceInstallOptions instead of overloading the parameters map, or at minimum filtering out _selectedIndex before iterating allParameters at line 580.

}),
)
} else {
setStatusMessage(
Copy link
Contributor

Choose a reason for hiding this comment

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

SUGGESTION: Redundant data re-fetch after successful install/remove

On success, this calls requestData() which sends requestMarketplaceData to the extension. However, the extension handler (handleInstallMarketplaceItem / handleRemoveMarketplaceItem) already calls this.handleRequestMarketplaceData() before posting the marketplaceActionResult message — so the webview has already received fresh marketplaceData by the time this code runs. This triggers a second unnecessary network fetch of the entire catalog.

throw new Error("Skill item missing tarball URL")
}

const response = await fetch(tarballUrl)
Copy link
Contributor

Choose a reason for hiding this comment

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

WARNING: No URL scheme validation before fetching skill tarball

The tarballUrl (from item.content) is fetched without validating that it uses an https:// (or at minimum http://) scheme. While the URL originates from the marketplace API, a compromised or misconfigured catalog could supply file://, data:, or internal network URLs, potentially enabling SSRF or local file exfiltration.

Consider adding a scheme check before the fetch:

const parsed = new URL(tarballUrl)
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
  throw new Error(`Unsupported URL scheme for skill tarball: ${parsed.protocol}`)
}

}

private replaceTemplateToken(content: string, key: string, value: string): string {
return content.split(`{{${key}}}`).join(value)
Copy link
Contributor

Choose a reason for hiding this comment

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

WARNING: replaceTemplateToken performs naive string substitution without JSON-escaping the user-supplied value.

If the MCP content template is JSON (e.g. {"args": ["{{api_key}}"]}), a user-supplied value containing JSON structural characters (quotes, brackets, etc.) can alter the parsed structure. For example, a value like foo", "injected would break out of the string context and inject additional array elements into args.

While sanitizeMcpServerConfig validates the types of known fields (command, args, env), it doesn't restrict unknown keys, so injected keys would pass through to the written config file.

Consider JSON-escaping the replacement value when the template content is JSON, or performing substitution on the parsed object rather than the raw string.

await fs.mkdir(destination, { recursive: true })

try {
const bytes = Buffer.from(await response.arrayBuffer())
Copy link
Contributor

Choose a reason for hiding this comment

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

WARNING: No response size limit on tarball download — the entire response body is loaded into memory via response.arrayBuffer() before being written to disk.

A malformed or malicious catalog entry could point to an arbitrarily large file, causing memory exhaustion. Consider checking Content-Length header and enforcing a maximum size (e.g. 50 MB), or streaming the response directly to disk instead of buffering it entirely in memory.

@markijbema
Copy link
Contributor

@bernaferrari I tried it out, but i don't think it fully works yet. Can you please iterate on it first and make sure all the paths work?

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants