File: README.md
✨ Why HolaOS is different
Most agent products hand you a finished product and hope it fits your work. HolaOS hands you the parts. Apps, chat tools, skills, integrations, models, agents, memory — you assemble the workspace around how your work actually runs, and change it whenever the work changes. Because everything runs locally, customizing it never means handing your work to someone else's cloud.
🪟 HolaApps — apps and agent, side by side
Install apps from the in-workspace marketplace and they open as real, interactive surfaces right beside your agent. Watch it work inside the app, step in whenever you want, and the result lands in place — not a wall of chat text, but the actual app, driven by the agent, next to the agent.
- Real surfaces, not chat — every app is a live UI (Notion, a browser, your own app), not a transcript.
- Side-by-side by design — app and agent share the screen, so you always see what's happening and can take over.
- One click to install — browse the in-workspace marketplace and open any app instantly.
- Bring your own — point a HolaApp at any URL and MCP server; it lives on your machine, yours to open and drive.
- Context stays in sync both ways — drive the app by hand and the agent keeps up. Whatever you click, type, or open in the UI becomes context the agent already has, so you never stop to re-explain what you just did.
💬 IM — plug in the chat tools your team already lives in
Most work context never makes it into a document. It's in a Slack thread, a Feishu group, a DingTalk message. Connect the chat tools your team already uses and your agent works from the real conversation — not from your summary of it.
Where the context really is — decisions, requirements, and changes get made in chat and stay there. The agent reads them directly, so you stop re-explaining what was already said.
The tools your team is already in — Slack, Feishu, DingTalk, WeChat. Connect the ones you use, skip the ones you don't.
You decide what it can see — access is granted per tool and per scope, and the agent reads nothing until you approve it.
🧩 Skills, integrations, and MCP — add any capability without building one
- Integrations — connect Gmail, Notion, Slack, GitHub, Linear and 50+ more with one-click OAuth. Agents read and act across your tools, no glue code — and every agent inherits the same connections.
- MCP — plug in any Model Context Protocol server to give your agents new tools. Bring your own, or install community MCP servers in one click.
- Skills — package a workflow once; any agent runs it on demand.
- Combos — bundle skills and integrations into a single one-click install.
💸 Models your way — pick the right one per task, or bring your own
One account, every model — no keys, no setup, no switching between providers. The latest frontier models are built in: cost-efficient Kimi K3 and GLM 5.2 for everyday volume, plus top-tier GPT 5.6, Claude Opus 5, and Fable 5 for the hard problems. Prefer your own provider? Bring your own keys for OpenAI, Anthropic, or any OpenAI- or Anthropic-compatible endpoint — those run on your account, not your holaOS plan.
- Zero-setup default — one account, every SOTA model, no API keys to manage.
- BYOK when you want it — your keys, your providers, your rates.
- Right model per task — pick per job, per agent.
🔀 Agents — run the agent you prefer, not the one we picked
Claude Code, Codex, and the built-in holaOS agent — side by side, no switching. Whichever you run, it shares the same memory, tools, skills, and apps. Use the best agent for the job without rebuilding your setup every time.
- No lock-in — run Claude Code, Codex, or holaOS's own agent in the same workspace, over the same tools, files, and memory.
- Shared everything — one context, one set of tools, one workspace.
- Consistent results — the same skills and integrations, whatever's driving.
🧠 One memory, every agent
Context, preferences, and project history live in a single shared memory — stored locally, as plain files you can read and edit. Switch agents, close the app, come back next week: it already knows where you left off.
- Never start from zero — durable memory across sessions and agents.
- Local-first & yours — on your machine, visible and editable, not locked in someone else's cloud.
- Actually recallable — structured and embedded, so the right context returns when it's needed.
🛠️ Your entire workstation, agent-operable
- 🌐 A real browser, driven by agents — signed-in browsers your agents drive to browse, click, and extract — under your control.
- 🎨 Frontier generation built in — the latest image, video, and audio models inside every agent. Storyboard a video, design a poster, voice a script — one prompt.
- 📄 Real deliverables — reports, spreadsheets, and slides saved as real
.xlsx,.pptx, and.docxfiles you can send, not text stuck in a chat. - ⏰ Automation — run on a schedule or a trigger. Digests, monitors, and reports finish and file themselves.
🔒 Your work stays on your side
An agent that can reach your tools, your chats, and your whole machine only makes sense if you stay in control of it. That control is built into how HolaOS works, not bolted on as a setting.
Local-first, not cloud-first — workspace files, memory, embeddings, and session history all live on your own disk. There is no copy of your work on our servers.
We can't see your data — not your files, not your memory, not your conversations. It isn't a policy we promise; it's a place your data never goes.
Every action is approved by you — connecting a tool, reading a thread, editing a file. The agent asks first, and you decide what it can reach and what stays off limits.
Visible and reversible — open what the agent remembers, edit it, delete it. Memory is a folder you can read, not a black box.
Your own keys, your own contracts — route inference through your own model provider if that's what your policy requires.
🌐 HolaHub — the recipe platform for agents, inside HolaOS
The fastest way to set up your workspace is to start from someone else's working setup. On HolaHub, creators publish agent recipes that already run — model, prompt, skills, MCP, integrations — and anyone installs one in a click and gets the same result. Share what you built, run what other people built.
One click to share — package any run in HolaOS and post it to the Hub, process and result together. No export, no rewrite, no second account.
One click to reproduce — every skill, MCP, image, or video on the Hub runs in your own workspace instantly. Discovery and installation are the same gesture.
Recipes, not just outputs — each post opens up into how it was made: the session behind a skill, the prompt behind an image or video. You see the ingredients of a result, not only the result.
🚀 Run it your way
| 🖥️ Desktop app | Download and go. Nothing to set up — frontier models built in, free to start. |
| 🔓 Open source | Self-host it. Modified Apache 2.0, bring your own keys, run it entirely on your machine. |
| 🏢 Enterprise | SSO with per-role permissions for every agent, skill, and app. Connect internal systems without exposing them. Audit logs on every action. On-prem or your own cloud. |
Table of Contents
Quick Start
One-Line Install
For a fresh-machine bootstrap on macOS, Linux, or WSL, use the repository installer:
curl -fsSL https://raw.githubusercontent.com/holaboss-ai/holaOS/refs/heads/main/scripts/install.sh | bash -s -- --launchYou can also follow the manual path if you want to control each setup step.
Star the Repository
Manual Install
You likely will not need this section because One-Line Install runs the same setup. Use Manual Install when you want to inspect or control each step. If you use the manual path, verify the usual prerequisites first:
git --version
node --version
npm --versionThe repo pins its Node version in a root .nvmrc. If you already use nvm, running nvm use in the repo root will pick it up automatically — nvm is optional and not required to set up holaOS.
One-Line Agent Setup
If you use Codex, Claude Code, Cursor, Windsurf, or another coding agent, you can hand it the setup instructions in one sentence:
Run the holaOS install script from https://raw.githubusercontent.com/holaboss-ai/holaOS/refs/heads/main/scripts/install.sh. It should install git and Node.js 24.14.1/npm if they are missing, clone or update the repo into ~/holaboss-ai unless I specify another --dir, run desktop:install, create apps/desktop/.env from apps/desktop/.env.example if needed, run desktop:prepare-runtime:local and desktop:typecheck, and only run desktop:dev if I ask for --launch. If Electron cannot open, stop after verification and tell me the next manual step.That handoff keeps the installation flow self-contained while leaving the detailed bootstrap steps in the repo-local INSTALL.md runbook.
This is the baseline installation flow for local desktop development.
- Install the desktop dependencies from the repository root:
npm run desktop:install- Create your local environment file:
cp apps/desktop/.env.example apps/desktop/.envIf you are following the repo exactly, keep the file close to the template and only change the values that your provider or machine needs.
The canonical path is apps/desktop/.env. Existing legacy desktop/.env files are still accepted for now, but new setups should use apps/desktop/.env.
- Prepare the local runtime bundle:
npm run desktop:prepare-runtime:local- If you want a quick validation pass before launching Electron, run:
npm run desktop:typecheck- Start the desktop app in development mode:
npm run desktop:devThe predev hook will validate the environment, rebuild native modules, and make sure a staged runtime bundle exists.
If you want to stage the runtime before opening the desktop app, there are two common paths:
Build from local runtime:
npm run desktop:prepare-runtime:localFetch the latest published runtime:
npm run desktop:prepare-runtimeUse the local path when you are actively changing runtime code. Use the published bundle when you want to verify the desktop against a known release artifact.
Use One-Line Install when you want the fastest path to a working local desktop environment. Use Manual Install when you need to inspect or control each setup step yourself.
OSS Release Notes
- License: modified Apache 2.0 with additional commercial-distribution and branding conditions. See LICENSE.
- Security issues: report privately to
[email protected]. See SECURITY.md.
Star History
File: apps/desktop/README.md
Holaboss Local - Neon AI Workspace Prototype
Desktop prototype inspired by a futuristic dark neon-green AI operating workspace with three docked panes:
- File Explorer (left)
- In-app Browser panel (center)
- AI Chat assistant (right)
Built with Electron + React + TypeScript + Vite + Tailwind CSS.
Features
- Electron main/preload/renderer separation with secure preload bridge
- Full-window dark neon-green visual system
- Global top tab bar with workspace controls
- Three-pane layout with draggable split handles
- Pane widths persist in local storage
- Real local file explorer (reads your filesystem via secure IPC)
- Real embedded Chromium browser (
<webview>) with URL/home/back/forward/refresh - AI chat panel with suggestion pills, composer, user/assistant bubbles, and local rule-based responses
Tech Stack
- Electron
- React 19
- TypeScript
- Vite
- Tailwind CSS
- Lucide React icons
Run
npm install
cp .env.example .env
npm run devThis launches:
- Vite dev server for the renderer (
http://localhost:5173) - TS build watcher for Electron main/preload (
out/dist-electron/*.cjs) - Electron desktop window with live restarts on main/preload changes
The repo ships no remote endpoint URLs — backend/auth access is configured entirely through environment variables (see .env.example).
Before running npm run dev, create apps/desktop/.env from apps/desktop/.env.example and fill in the required remote values:
cp .env.example .envnpm run dev stages a runtime bundle before Electron starts. In this repo it prefers a locally built runtime when the runtime packager scripts are available.
Each staged runtime bundle is self-contained and carries the runtime API, bundled Node/npm, and bundled Python.
Build
npm run buildThis creates:
- Renderer production bundle in
out/dist/ - Electron main/preload bundles in
out/dist-electron/
Runtime Bundle
Packaged desktop builds expect a staged runtime bundle at out/runtime-/:
npm run prepare:runtimeThat stages the bundle for the current host platform from an explicit runtime source. If you need to target a specific platform explicitly:
npm run prepare:runtime:macos
npm run prepare:runtime:linux
npm run prepare:runtime:windowsFor local development against unreleased holaOS runtime changes:
# optional when your OSS repo is not ../holaOS
export HOLABOSS_OSS_ROOT=/absolute/path/to/holaOS
# builds the runtime bundle for the current host platform and stages it into out/runtime-
npm run prepare:runtime:localAfter npm run prepare:runtime:local, a normal npm run dev uses that staged local runtime bundle. Set HOLABOSS_RUNTIME_PLATFORM=macos|linux|windows if you need to override platform detection.
Or package directly with local runtime in one step:
npm run dist:mac
npm run dist:mac:dmg
npm run dist:winThe staging script accepts one of:
HOLABOSS_RUNTIME_DIR=/absolute/path/to/runtime-HOLABOSS_RUNTIME_TARBALL=/absolute/path/to/holaboss-runtime--<sha>.tar.gzHOLABOSS_RUNTIME_BUNDLE_URL=https://.../holaboss-runtime--<sha>.tar.gzHOLABOSS_RUNTIME_PLATFORM=macos|linux|windowsto override the auto-detected target platform when needed
Runtime packagers can also override the bundled Python source when needed:
HOLABOSS_RUNTIME_PYTHON_DIR=/absolute/path/to/extracted/pythonHOLABOSS_RUNTIME_PYTHON_TARBALL=/absolute/path/to/cpython-...tar.gzHOLABOSS_RUNTIME_PYTHON_URL=https://.../cpython-...tar.gzHOLABOSS_RUNTIME_PYTHON_VERSION,HOLABOSS_RUNTIME_PYTHON_RELEASE,HOLABOSS_RUNTIME_PYTHON_VARIANT, andHOLABOSS_RUNTIME_PYTHON_TARGET_TRIPLE
If none are set, it falls back to the host temp directory, for example ${TMPDIR:-/tmp}/holaboss-runtime--full, or you can run npm run prepare:runtime:local first.
To build a mac app bundle with the runtime embedded in Electron resources:
npm run dist:macUse dist:mac for the default local-runtime packaging flow. Use prepare:runtime with explicit runtime source env vars when you need to package against a prebuilt runtime bundle instead.
This produces an unsigned local mac app bundle with runtime-macos embedded in Contents/Resources/.
For Windows packaging:
npm run dist:winUse dist:win on a Windows host to build and stage a native local runtime bundle first, then produce a Windows NSIS installer.
The Windows packaging command also writes out/holaboss-config.json from your configured desktop environment before building the installer.
This produces a Windows NSIS installer .exe in out/release/.
Output:
Remote configuration for packaged runs (set via environment variables):
HOLABOSS_AUTH_BASE_URLfor Better Auth session endpointHOLABOSS_AUTH_SIGN_IN_URLfor the hosted sign-in pageHOLABOSS_BACKEND_BASE_URLfor the Holaboss backend base URL used by the desktopHOLABOSS_PACKAGED_APP_BINfor an explicit packaged binary path
Start by copying the template:
cp .env.example .envTo build a mac installer image:
npm run dist:mac:dmgThis produces a local-use .dmg installer in out/release/.
Notes:
dist:macbuilds an unpacked.appdist:mac:dmgbuilds a.dmginstaller- both local mac packaging commands force ad-hoc signing for local smoke testing via
--config.mac.identity=- - production signing and notarization are handled in GitHub Actions once the Apple secrets are configured
Signed Product Release
Desktop validation and desktop release packaging now share the single .github/workflows/ci.yml workflow, whose display name is CI.
On pull requests and pushes to main, CI runs the normal validation jobs only. On manual dispatch, that same workflow:
- calls
.github/workflows/publish-linux-runtime.ymlto build the backend-consumed Linux runtime bundle and stage it for the final release publish - checks out the requested
ref - creates the requested GitHub release in
holaboss-ai/holaOS-releases - attaches the Linux runtime bundle, manifest, and checksum to that same
holaboss-ai/holaOS-releasestag - builds the signed and notarized Apple Silicon macOS app, then uploads the DMG, ZIP, blockmaps, and
latest-mac.yml - calls
.github/workflows/publish-macos-intel-desktop.ymlto stage the notarized Intel macOS (x64) DMG, ZIP, blockmap, and mac updater manifest for the same release - merges the Apple Silicon and Intel macOS updater manifests before publishing the final shared
latest-mac.yml/beta-mac.ymlassets - builds the signed Windows NSIS installer, then uploads the installer, blockmap, and channel manifest
The .github/workflows/publish-macos-intel-desktop.yml workflow remains separately dispatchable for Intel-only reruns. It now rebuilds the notarized Intel macOS DMG, ZIP, blockmap, and updater metadata, then merges the Intel updater entries into the existing shared latest-mac.yml / beta-mac.yml assets on holaboss-ai/holaOS-releases. That standalone rerun path assumes the Apple Silicon macOS release assets for the tag already exist in the shared release repo.
Release channel policy:
- the Linux runtime bundle is published separately for remote sandbox runtime consumers
- desktop releases still do not attach standalone runtime tarballs to
holaboss-ai/holaOS-releases - desktop-shippable releases publish to
holaboss-ai/holaOS-releases - the in-app desktop updater is still intended to track desktop releases, not arbitrary runtime assets
Desktop release versioning:
- use stable semver in
YYYY.MDD.Rformat YYYY= year,MDD= month without a leading zero plus a two-digit day,R= release number for that date- examples:
2026.410.1,2026.410.2,2026.1113.1 - do not zero-pad the month in the middle segment;
2026.0410.1is not valid semver - desktop GitHub release tags must be
holaOS-YYYY.MDD.R - the desktop packager derives the app update version from the trailing
X.Y.Zsuffix inrelease_tag, so tags should end with the sameYYYY.MDD.Rvalue - to print a version for today, run
npm --prefix desktop run release:version - to print the full desktop release tag for today, run
npm --prefix desktop run release:tag - to print the second release for a specific day, run
npm --prefix desktop run release:version -- 2 --date 2026-04-10 - to print the second release tag for a specific day, run
npm --prefix desktop run release:tag -- 2 --date 2026-04-10
The manual CI release path requires these repository secrets and fails fast when any of them are missing:
MAC_CERTIFICATE: base64-encodedDeveloper ID Application.p12MAC_CERTIFICATE_PASSWORD: password for the.p12APPLE_ID: Apple Developer account emailAPPLE_APP_SPECIFIC_PASSWORD: app-specific password from Apple ID settingsAPPLE_TEAM_ID: Apple Developer Team IDAZURE_TENANT_ID: Microsoft Entra tenant ID for the Windows signing service principalAZURE_CLIENT_ID: client ID for the Windows signing service principalAZURE_CLIENT_SECRET: client secret value for the Windows signing service principal; use the secretValue, not the secret IDHOLABOSS_RELEASES_REPO_TOKEN: token with read access to the private source repo and write access toholaboss-ai/holaOS-releases
The manual CI release path also requires these repository variables for Azure Trusted Signing:
WINDOWS_SIGNING_ENDPOINT: Azure Trusted Signing endpoint for the account regionWINDOWS_SIGNING_ACCOUNT_NAME: Trusted Signing account nameWINDOWS_SIGNING_CERTIFICATE_PROFILE_NAME: certificate profile nameWINDOWS_SIGNING_PUBLISHER_NAME: publisher name exactly as it appears in the certificate profile
The Windows signing service principal must have the Artifact Signing Certificate Profile Signer role on the Trusted Signing account or certificate profile.
The Windows CI release job validates this before the build by requesting a Trusted Signing token and signing a small probe executable, so role-assignment or propagation failures fail before the expensive installer build.
When manually triggering CI, provide:
ref: the branch, tag, or commit you want to shiprelease_tag: the GitHub release tag to create or update, inholaOS-YYYY.MDD.Rformatrelease_title: optional display title for the GitHub releaseprerelease: whether the GitHub release should be marked as a prerelease when first created
The workflow stages the runtime bundle needed for each desktop build inside the runner, maps the Apple signing secrets and Azure Trusted Signing settings to electron-builder, and uploads the desktop release assets to the chosen GitHub release. The desktop build config uses hardenedRuntime plus an explicit mac entitlements plist at resources/entitlements.mac.plist.
Docs publishing remains separate in .github/workflows/deploy-docs.yml and automatically runs only for docs changes.
After a signed build, validate the produced app locally with:
codesign --verify --deep --strict --verbose=2 /path/to/holaOS.app
spctl -a -vv -t exec /path/to/holaOS.app
xcrun stapler validate /path/to/holaOS.appProject Structure
electron/
main.ts
preload.ts
src/
components/
layout/
AppShell.tsx
TopTabsBar.tsx
SplitPaneLayout.tsx
panes/
FileExplorerPane.tsx
BrowserPane.tsx
ChatPane.tsx
ui/
PaneCard.tsx
IconButton.tsx
data/
mockFiles.ts
mockBrowser.ts
utils/
mockAssistant.ts
types/
electron.d.ts
webview.d.ts
App.tsx
main.tsx
index.cssBrowser Notes
The center pane uses a real Electron webview:
- Source is set in
src/components/panes/BrowserPane.tsx - Navigation controls call real webview methods (
goBack,goForward,reload,loadURL) - Main process enables and guards webview attachment in
electron/main.ts
If you later want a multi-tab browser architecture, keep the current BrowserPane control surface and swap to a tab manager that creates one webview per tab.
File Explorer Notes
The left pane reads real directories from your machine:
- IPC handler lives in
electron/main.ts(fs:listDirectory) - Renderer call lives in preload bridge (
window.electronAPI.fs.listDirectory) - UI and navigation are in
src/components/panes/FileExplorerPane.tsx
The explorer currently supports:
- folder open (double-click or “Open Selected Folder”)
- back/forward/up/home navigation
- search/filter in current directory
- grouped rendering by modified date (Today / This Week / This Year / Earlier)
AI assistant
Current chat replies are local rule-based mocks (no backend/LLM):
- Rules and canned responses live in
src/utils/mockAssistant.ts ChatPane.tsxhandles message rendering and send flow
Replace point for real AI backend:
- Hook send flow in
src/components/panes/ChatPane.tsxwheregenerateMockReplyis called - Replace timeout mock with API call and stream/append tokens as needed
Security
Renderer runs with:
contextIsolation: truenodeIntegration: falsewebviewTag: true(enabled intentionally for embedded browser pane)
Preload bridge exposes runtime info and a constrained filesystem API via window.electronAPI.
Stretch Goals Ready
Codebase is structured to support later additions such as:
- draggable top tabs
- command palette
- collapsible icon rail
- tokenized theme switching
File: docs/plans/2026-05-18-discord-module-connect-flow-fix/README.md
Discord module connect-flow fix — 2026-05-18
When a user asked the desktop agent to build a brand-new Discord
module, three independent bugs surfaced in sequence:
- No Connect button rendered in the chat after the agent reported
the app was ready. - First
/api/composio/connectcall always failed with a 502;
retrying always worked. - After the user finally authorized, the Discord app already
running in the sandbox kept behaving as if no integration was
bound, until a manual restart.
The three bugs are independent — fixing only one of them leaves the
flow broken at the next step — so the work is split into three stages
that need to land together for the end-to-end Discord build to work.
Each stage is locally verifiable on its own (see each stage doc's
"Verification" section).
| Stage | Bug | Layer | Deploys via |
|---|---|---|---|
| 1 | Connect button | holaOS runtime | rebuild + npm run desktop:prepare-runtime:local |
| 2 | Stale grant after bind | holaOS desktop (electron main + preload + renderer) | npm run desktop:dev |
| 3 | First-time 502 + non-extensible toolkit map | Hono (frontend/apps/server) | bun run deploy:staging |
| 4 | Account labels show as hex hash for any toolkit not in PROVIDER_WHOAMI |
UI fallback (Tier 1) + per-yaml whoami passthrough across runtime/desktop/Hono (Tier 2) | desktop rebuild + bun run deploy:staging |
| 5 | not_connected / 401 on send-message after a clean OAuth (identity probe works) |
provider.ts + yaml use the actual Composio toolkit slug (discordbot, not discord); skill rewritten to enforce one-value-everywhere; composioToolkit deprecated |
edit-in-workspace + bun run desktop:prepare-runtime:local (skill ships in runtime bundle) |
| 6 | App Surface "Connect" button only rendered for ~12 hardcoded toolkits; new agent-built apps had no in-app bind UI | /api/v1/apps returns yaml-declared integrations[] per app; AppSurfacePane resolves provider from yaml (not from a hardcoded map); handleConnectAccount runs full OAuth+bind+restart inline |
desktop rebuild |
| 7 | Two parallel bind-state machines (chat card + App Surface) drifted; APP_CATALOG hardcoded display labels for 7 apps — agent-built apps got labelFromAppId fallbacks |
Shared useIntegrationBinding hook; both consumers refactored; yaml name: surfaced on /api/v1/apps; APP_CATALOG / summary / accentClassName deleted |
desktop rebuild + npm run desktop:prepare-runtime:local |
| 8 | Rejecting / closing the OAuth window left the Connect button spinning for the full 5-min poll timeout | Hook exposes cancel() + per-call AbortController; consumers swap Connect → Cancel button while busy === "connecting"; unmount aborts in-flight controller |
desktop rebuild |
The legacy module-app source is not touched — the desktop agent now
generates app modules dynamically, the legacy marketplace path is no
longer the source of truth for these bugs.
End-to-end verification (after all three stages ship)
1. Open desktop → start a new workspace → tell the agent "build a
Discord bot module for me".
2. Once the agent reports the app is ready, a "Connect Discord" card
MUST render inline in the assistant turn (Stage 1).
3. Click Connect Discord → OAuth window opens → user grants access in
their browser (Stage 3, no 502 on the first attempt).
4. Card transitions to "Bound to discordbot-module …" within seconds
of the user clicking through OAuth (Stage 2 has restarted the app
in the background so HOLABOSS_APP_GRANT is freshly injected).
5. Ask the agent "list my Discord guilds" — it must succeed on the
first call without any restart hint or manual intervention.Why the bugs presented together
All three bugs sit on the same chain — the agent's "I just built a new
toolkit-backed app" path. Existing apps (gmail, github, etc.) had at
least one of:
- a marketplace catalog entry hardcoding
provider_id, working around
Stage 1's narrow emit set; - an entry in
PROVIDER_TO_TOOLKIT, working around Stage 3's missing
mapping; - a user who had connected the toolkit before any app was running,
side-stepping Stage 2's boot-time env capture.
A truly fresh Discord install bypassed all three workarounds at once,
which is why this is the first toolkit to expose the whole chain.
File: docs/plans/2026-05-18-discord-module-connect-flow-fix/stage-1-broaden-pending-integrations-emit.md
Stage 1 — Broaden `pending_integrations` emit (Bug 1)
Symptom: desktop agent creates a new Discord module, but no Connect button
appears in the conversation. User has to open the Integrations pane manually
to authorize the toolkit.
Root cause
pending_integrations is the payload the runtime emits in a tool result so
the chat UI can render an inline Connect card next to the assistant message.
Two layers were both involved:
pendingIntegrationsFromAppManifests(runtime-agent-tools.ts) reads each
app'sapp.runtime.yaml, parsesintegrations[](and the legacyintegrationsingular viaintegration-types.ts:88fallback), and emits
one entry perrequired: trueintegration. This part already works
correctly for Discord —integration: { destination: "discordbot" }
resolves toprovider: "discordbot".claimed-input-executor.ts:3266PENDING_INTEGRATION_EMITTING_TOOLSonly
scanned the output of four tools:workspace_apps_installworkspace_apps_ensure_runningworkspace_apps_restartworkspace_apps_restart_and_wait_ready
If the desktop agent ended its build flow on any other tool —workspace_apps_scaffold, _register, _build, _wait_until_ready, or_get_status — the chat UI never saw pending_integrations and the Connect
card was never rendered. The SKILL.md instructs the agent to end onensure_running, but in practice the LLM does not always follow that path.
Fix
Two changes, both in holaOS/runtime/api-server/src/:
claimed-input-executor.ts:3266— extend the emitting set with five
completion-type tools:ts"workspace_apps_scaffold", "workspace_apps_register", "workspace_apps_build", "workspace_apps_wait_until_ready", "workspace_apps_get_status",runtime-agent-tools.ts— introduce a private helperpendingIntegrationsForApps(workspaceId, appIds)and call it at the tail
of every completion-type tool method so the JSON result carriespending_integrations: [...]whenever a registered app has at least onerequired: trueintegration:scaffoldWorkspaceAppregisterWorkspaceAppbuildWorkspaceApp(bothskippedand normal paths)getWorkspaceAppStatus(single-app + all-apps variants)restartWorkspaceAppwaitUntilWorkspaceAppReady(both ready and timed-out paths)
The card UI already de-dupes by (provider_id, app_id), so emitting the same
entry from multiple successive tools is harmless — only the most recent
assistant turn renders the card.
Files changed
runtime/api-server/src/claimed-input-executor.tsruntime/api-server/src/runtime-agent-tools.ts
Verification
- Rebuild runtime + redeploy bundled
.pycinto desktop:bashcd holaOS npm run desktop:prepare-runtime:local - Restart the desktop app.
- Ask the agent to create a Discord module from scratch.
- After the agent reports the app is ready, a Connect Discord card MUST
appear inline in the chat. This should hold regardless of which tool the
agent invoked last.
Out of scope (handled in later stages)
- The Hono
/api/composio/connectfirst-time failure (Stage 3 — Bug 2). - Running Discord app not picking up the new grant after authorization
(Stage 2 — Bug 3).
File: docs/plans/2026-05-18-discord-module-connect-flow-fix/stage-2-restart-app-after-bind.md
Stage 2 — Restart the workspace app after a binding change (Bug 3)
Symptom: user clicks Connect Discord, OAuth succeeds, the Composio
connection is finalized and the workspace-app binding is upserted — but
the running Discord app still answers as if no integration is configured.
The agent has to be told to restart the app, or the user has to do it
manually.
Root cause
The runtime bridge transport
(experiments/app-builder-sdk/src/bridge-transports/runtime-broker.ts and
the equivalent in @holaboss/bridge) reads HOLABOSS_APP_GRANT fromprocess.env once at module import. The grant carries (workspace, app)
identity, which the integration-broker uses on every provider call to look
up the bound connection.
When a new binding is created after the app has started:
- The runtime writes the new binding to
runtime.dbcorrectly. - The integration-broker can resolve the binding on subsequent calls.
- But the bridge in the running app process still carries either a
stale grant or no grant — env is captured at boot, not refreshed.
The desktop already calls composioFinalize (writes the connection) andupsertIntegrationBinding (writes the workspace-app binding) inIntegrationConnectCard.handleConnect / handleBind. What was missing
was a process-level restart of the app so integration-runtime.ts can
re-inject HOLABOSS_APP_GRANT at the next boot.
The runtime already exposes the right endpoint
(POST /api/v1/capabilities/runtime-tools/workspace-apps/:appId/restart —
backed by RuntimeAgentToolsService.restartWorkspaceApp), but no desktop
IPC surfaced it to the renderer.
Fix
End-to-end wiring of a new restartApp IPC, then call it from the bind
flow.
1. Runtime → main process
electron/main.ts — new restartWorkspaceApp(workspaceId, appId) thin
wrapper around requestWorkspaceRuntimeJson posting to theworkspace-apps/:appId/restart capability endpoint.
2. IPC registration
electron/main.ts — handleTrustedIpc("workspace:restartApp", ...)
next to the other binding handlers.
3. Preload bridge
electron/preload.ts — exposewindow.electronAPI.workspace.restartApp(workspaceId, appId).
4. Renderer type definition
src/types/electron.d.ts — add the method signature to theworkspace IPC surface.
5. Renderer consumer
src/components/panes/ChatPane/AssistantTurn/IntegrationConnectCard.tsx
— extract a rebootAppAfterBindChange helper; call it in bothhandleConnect (after a fresh OAuth-driven bind) and handleBind (after
the user switches/binds an existing connection).
The restart is fire-and-forget: a thrown error is swallowed because the
bind itself succeeded, and a manual restart later will recover the same
end state. The next agent call against the app will surface the actual
error if anything broker-side is still wrong.
Files changed
desktop/electron/main.ts(new helper + IPC handler)desktop/electron/preload.ts(bridge entry)desktop/src/types/electron.d.ts(type)desktop/src/components/panes/ChatPane/AssistantTurn/IntegrationConnectCard.tsx
(consumer)
Verification
- Rebuild desktop:bash
cd holaOS npm run desktop:install npm run desktop:dev - Create a fresh Discord module (Stage 1 must already be deployed —
otherwise the Connect card may not appear at all). - Click Connect Discord, complete the OAuth flow in the popped browser.
- After the card transitions to the "Bound to discordbot-module" state,
immediately ask the agent to call a Discord tool (e.g. list guilds). - The tool MUST work on the first call — no manual restart, no
"integration not bound" error. - Repeat with switching accounts (drop-down "Switch to account 2") to
confirmhandleBindalso triggers the restart.
Out of scope (handled separately)
- Bug 1 (Stage 1): Connect button not appearing — Stage 2 only matters if
the card renders in the first place. - Bug 2 (Stage 3): Hono
/api/composio/connectfirst-time failures — the
user has to be able to complete OAuth for Stage 2 to ever exercise. - Future optimization: drop the restart in favor of an in-process env
refresh hook on the bridge transport, so Discord doesn't lose a few
seconds of uptime per bind. Not worth the complexity until restart
proves user-visible.