Interface: UiService
Defined in: packages/sdk/src/ui-service.ts:291
The user-interaction domain, exposed as ExtensionContext.ui. The host renders the chrome; an extension only asks. Interactions today:
- Native OS dialogs — pickFolder, pickFile, savePath. Thin wrappers over the platform dialogs the host owns; each resolves to an absolute path or
nullwhen the user cancels. - Notifications — notify shows a transient toast, optionally with a title and action buttons (see NotifyOptions). The only way an extension can proactively message the user.
- Menus — showMenu pops a context menu or button dropdown, themed to match the rest of the app.
- Modal dialogs — confirm and prompt pop a host-owned modal and resolve on the user's choice; showModal pops one around your own custom content (a form or bespoke layout).
- External links — openExternal hands a URL to the OS (browser / mail client), the host's gateway to the world outside the app.
Mirrors VS Code's window.show*. More host-rendered chrome (quick-pick, progress) is planned — see the roadmap.
Methods
pickFolder()
pickFolder(opts?): Promise<string | null>;Defined in: packages/sdk/src/ui-service.ts:298
Show the native folder picker. Resolves to the chosen absolute path, or null if the user cancelled.
Parameters
opts?
defaultPath?
string
Absolute path to open the dialog at.
Returns
Promise<string | null>
pickFile()
pickFile(opts?): Promise<string | null>;Defined in: packages/sdk/src/ui-service.ts:306
Show the native open-file picker (single selection). Resolves to the chosen absolute path, or null if the user cancelled.
Parameters
opts?
defaultPath?
string
Absolute path to open the dialog at.
filters?
Restrict the selectable file types (see FileFilter).
Returns
Promise<string | null>
savePath()
savePath(opts?): Promise<string | null>;Defined in: packages/sdk/src/ui-service.ts:317
Show the native save dialog. Resolves to the chosen destination's absolute path, or null if the user cancelled.
Parameters
opts?
defaultPath?
string
Seeds the dialog's location and suggested filename.
filters?
Restrict the file-type dropdown (see FileFilter).
Returns
Promise<string | null>
notify()
notify(
level,
message,
options?): void;Defined in: packages/sdk/src/ui-service.ts:352
Show a transient toast notification to the user. Fire-and-forget — the host renders it (and, for info / warn without actions, auto-dismisses it). level drives the icon and accent.
Pass NotifyOptions for a bold title, footer actions, or an explicit durationMs. Errors and toasts with actions stay until dismissed (so a "View details" action isn't lost to the timer); everything else auto-dismisses after ~4s.
Parameters
level
"info" | "warn" | "error"
message
string
options?
Returns
void
Example
// a plain info toast (auto-dismisses)
ctx.ui.notify("info", "Theme exported.");
// an error with a title and an action that opens the full detail in a modal
ctx.ui.notify("error", String(err), {
title: "Commit failed",
actions: [
{
label: "View details",
run: () =>
ctx.ui.showModal((close) => <pre>{String(err)}</pre>, {
title: "Commit failed",
dismissible: true,
}),
},
],
});showMenu()
showMenu(opts): Promise<void>;Defined in: packages/sdk/src/ui-service.ts:387
Pop a menu — the same themed primitive behind every context menu and dropdown in Silo. Supply the rows and where to place it (see ShowMenuOptions); the host renders it, runs the chosen item's run, and dismisses on outside-click or Escape.
Only one menu is open at a time — calling showMenu again replaces it, except that re-opening with the same anchor toggles it closed (a second click on a dropdown button dismisses it); opt out with toggle: false. Resolves once an item runs or the menu is dismissed.
Parameters
opts
Returns
Promise<void>
Example
// A right-click context menu at the cursor.
element.addEventListener("contextmenu", (e) => {
e.preventDefault();
ctx.ui.showMenu({
items: [
{ label: "Rename", run: rename },
{ type: "separator" },
{ label: "Delete", danger: true, run: del },
],
});
});
// A dropdown anchored under a button.
ctx.ui.showMenu({ items, anchor: buttonEl });confirm()
confirm(opts): Promise<boolean>;Defined in: packages/sdk/src/ui-service.ts:406
Pop a host-rendered confirm dialog and resolve to the user's choice — true for confirm, false for cancel. Always dismissible: Escape and backdrop-click both resolve false. The dialog stacks above all host chrome via the modal manager, so it works from anywhere.
Parameters
opts
Returns
Promise<boolean>
Example
if (await ctx.ui.confirm({
title: "Delete workspace?",
body: `"${name}" and its saved terminals will be permanently removed.`,
confirmLabel: "Delete",
danger: true,
})) {
service.delete(id);
}prompt()
prompt(opts): Promise<string | null>;Defined in: packages/sdk/src/ui-service.ts:417
Pop a host-rendered single-line input dialog and resolve to the entered string, or null if the user cancelled (Escape / backdrop / Cancel).
Parameters
opts
Returns
Promise<string | null>
Example
const name = await ctx.ui.prompt({ title: "Rename", initialValue: current });
if (name !== null) rename(name);showModal()
showModal<T>(render, options?): Promise<T | undefined>;Defined in: packages/sdk/src/ui-service.ts:454
Pop a host-rendered modal around your own custom content — the escape hatch beyond confirm / UiService.prompt | prompt when you need a form or bespoke layout. The host owns the hard parts (backdrop, central z-stacking above all chrome, focus trap, restore-focus-on-close); you own the content.
Supply a render callback that receives a close function and returns the modal's content; wire your own buttons to close(result) (or close() to cancel). The returned promise resolves with the value passed to close, or undefined if the modal was dismissed (only possible when ModalOptions.dismissible is set) or close() was called with no argument — paralleling confirm→false / prompt→null. If you must tell "dismissed" from "closed with no result" apart, pass a distinct sentinel.
Not dismissible by default: unless you set ModalOptions.dismissible, Escape and backdrop-click do nothing and the modal stays open until your content calls close. A non-dismissible modal whose content never calls close leaves the promise pending forever — by design, so staged edits can't be lost to an accidental click-away.
Type Parameters
T
T = void
The result type your content resolves with via close.
Parameters
render
(close) => ReactNode
Returns the modal content; receives close to settle it.
options?
Presentation options (see ModalOptions).
Returns
Promise<T | undefined>
Example
const changes = await ctx.ui.showModal<Changes>(
(close) => (
<MyForm onCancel={() => close()} onSave={(c) => close(c)} />
),
{ title: "Properties", size: "md" },
);
if (changes) apply(changes);openExternal()
openExternal(url): Promise<void>;Defined in: packages/sdk/src/ui-service.ts:486
Hand a URL to the operating system — open an http/https link in the user's default browser, or a mailto: link in their mail client. The host owns the privileged platform access; this is an extension's only sanctioned way to send the user out of the app.
Scheme-guarded. Only http:, https:, and mailto: URLs are opened; any other scheme (notably file: and javascript:) is rejected — the returned promise rejects, nothing is opened. This makes it safe to pass untrusted URLs (e.g. links inside a rendered Markdown document) straight through without first vetting the scheme yourself.
Parameters
url
string
The URL to open. Must be http:, https:, or mailto:.
Returns
Promise<void>
Throws
If url has any other scheme (or is unparseable).
Example
// open a docs link in the browser
await ctx.ui.openExternal("https://silo.dev/docs");
// route a clicked Markdown link safely — bad schemes just reject
try {
await ctx.ui.openExternal(href);
} catch {
ctx.ui.notify("warn", "That link can't be opened.");
}getActiveSelectionText()
getActiveSelectionText(): string | null;Defined in: packages/sdk/src/ui-service.ts:501
The text currently selected in the focused surface — the active editor or a focused terminal — or null when nothing is selected. Reads the most-recently-focused of the two, so a command (e.g. "Find in Files") can seed itself with whatever the user has highlighted regardless of which surface has focus. Returns null (never throws) when no surface is focused or the selection is empty.
Returns
string | null
Example
const seed = ctx.ui.getActiveSelectionText();
if (seed) runSearch(seed);