Protocol#
The UI and the core talk over two IPC channels: ohm:call for requests and ohm:event for
pushes. The renderer sees exactly two functions:
window.ohmHost = {
call(method: string, params?: unknown): Promise<unknown>;
on(event: string, listener: (payload: unknown) => void): () => void;
};
No Node, no ipcRenderer, no engine types. apps/ui/src/host.ts wraps it in a typed
HostBridge and returns null when the global is missing, so the UI can be opened in an
ordinary browser and says so instead of crashing.
Only the registered UI page may call: the handler table is keyed by the UI's webContents
id, and anything else is rejected with Unauthorized caller. Pages you browse cannot reach
any of this.
CoreMethods maps each name to [params, result], and the handler table is typed to be
total — adding a method is a compile error until it is implemented.
Methods#
Tabs and layout#
| Method |
Purpose |
tabs.list |
the current snapshot |
tabs.create |
{ url?, hidden? } |
tabs.close / activate / hide / show / discard |
by id |
tabs.navigate |
{ id, input } — resolved as a URL or a search |
tabs.back / forward / reload |
by id |
layout.setContentRect |
where the page view should sit |
Scripts and the console#
| Method |
Purpose |
scripts.root |
the scripts folder |
scripts.list / read / write / create / delete / rename |
the file tree |
scripts.run |
by path or inline source |
scripts.stop |
terminate a run |
scripts.runs |
run history |
scripts.export / import / restoreExamples |
folders in and out |
console.eval / console.reset |
the REPL session |
Apps#
| Method |
Purpose |
apps.list / apps.refresh |
what is installed; a rescan of apps/ |
apps.setEnabled |
start or stop one — a stopped app is granted nothing |
apps.uninstall |
move its folder to the trash |
apps.run |
run one of its commands by title |
apps.capabilities |
every capability, its risk, and the host functions it unlocks |
AI, settings and models#
| Method |
Purpose |
ai.status / ai.test / ai.generate |
test never throws; it returns { ok, error, ms } |
settings.get / saveProfile / deleteProfile / setActive |
profiles |
settings.setHfToken / pickModelsDir |
credentials and the models folder |
models.list / search / files / download / cancel / downloads / delete |
local and Hub models |
config.get |
where the data, profile, scripts and models folders are, and why |
config.setDir / pickDir |
move a folder from the next start; dir: null restores the default |
config.restart |
relaunch so a folder change takes effect |
webai.respond |
the renderer answering a web-model request |
Projects#
| Method |
Purpose |
projects.list / create / get / update / setActive / active |
|
links.add / update / remove |
|
data.preview |
parse a dataset file |
files.list / import / delete / url / read |
project documents |
annotations.get / save |
annotation sidecars |
projects.bundle / importBundle / exportFile / importFile |
portability |
Items#
| Method |
Purpose |
items.list / get / save / delete |
any item kind |
items.versions / items.version |
snapshot history |
prompts.save / render / run |
|
experiments.run / cancel / runs / getRun |
|
graphs.compile / run |
|
graphs.profiles / saveProfile / deleteProfile |
palettes |
specs.markdown |
render, and optionally write docs/<name>.spec.md |
Events#
| Event |
Payload |
tabs.changed |
the whole TabsSnapshot — there are no incremental tab events |
scripts.changed |
the file tree |
scripts.output |
{ runId, stream, text } as it happens |
scripts.status |
a run's lifecycle |
settings.changed |
the settings, without secrets |
models.progress / models.changed |
downloads and the local list |
webai.request |
a web-model generation for the renderer to perform |
projects.changed / project.updated |
|
experiments.progress |
throttled progress for a run |
apps.changed |
the installed apps, after a scan or a start/stop |
What never crosses#
No secret has a method that returns it. The UI receives only hasApiKey per profile, plus
hasHfToken and secureStorage booleans.