The app runs in a sandboxed iframe. window.chatoss is injected before your code runs; it is the only bridge to the OS. Every method returns a Promise — always await.
🔴 There are NO native dialogs in the sandbox —
confirm()/alert()/prompt()DO NOT WORK.
The app frame is sandbox="allow-scripts" and deliberately does not get allow-modals. So:
window.confirm(...)always returnsfalse, immediately, without showing anything.window.alert(...)andwindow.prompt(...)are no-ops (promptreturnsnull).window.print()is blocked too (use the documents API instead).
This is silent — no exception, no console warning — and it is the single most destructive mistake in a ChatOSS app: if (!confirm('Delete this card?')) return; makes the delete button do nothing, forever. Every delete/discard/overwrite path written that way is dead code.
Build your own confirmation with the platform classes instead:
// A real, in-frame confirm. .overlay + .modal are platform classes; [hidden] is
// forced display:none by the platform sheet, so toggling the attribute works.
function confirmDialog(message) {
return new Promise((resolve) => {
const el = document.getElementById('confirm');
el.querySelector('.modal-body').textContent = message;
el.hidden = false;
const done = (ok) => { el.hidden = true; resolve(ok); };
el.querySelector('.confirm-ok').onclick = () => done(true);
el.querySelector('.confirm-cancel').onclick = () => done(false);
});
}
if (await confirmDialog('Delete this card?')) await deleteCard(id);
Two filesystems — files vs drive. Never confuse them.
window.chatoss.files— the user's real disk. Needs a native folder/file picker; every path is confined to a root the user explicitly granted. CapabilityfileAccess. This is where a coding agent or an editor works on the user's own project. → Fileswindow.chatoss.drive— ChatOSS-managed storage. No picker, no prompt: your app writes into its own container the way it writes into its own SQLite database. Backup-eligible, and the user browses it in the built-in Files app. Capabilitydrive. → Drive
Ask yourself: is this the user's project, or is this my output? Project → files. Output (a generated report, an exported chart, a scraped dataset, a saved transcript) → drive. They are separate namespaces with separate capabilities on purpose — files carries the "every path was user-consented" invariant end to end, and drive is the thing cloud backup will cover.
Private storage (no capability, never prompts)
await window.chatoss.scopedData.set('myapp.state', anyJsonValue);
const value = await window.chatoss.scopedData.get('myapp.state'); // undefined if unset
await window.chatoss.scopedData.delete('myapp.state');
const keys = await window.chatoss.scopedData.list(); // every key this app has stored
// keys() is an alias for list() — no need to track your key names yourself.
Persists across launches, private to this app. Use this for all app state unless other apps must read it.
What's in the API
Every capability unlocks its own namespace on the bridge, documented one page per capability in the sidebar's API reference: AI chat, web search, files, drive, SQLite, documents, terminal, subprocesses, HTTP, webview, notifications, clipboard, global shortcuts, open external, background tasks, Kanban boards, secrets, MCP client, app-to-app APIs, the Agent Engine, install & preview, shared data & tools, platform & misc, and proposing tasks. A few things need no capability at all — private storage above, platform.info / platform.apis, the approvals window, and shared-data reads.