holaOS Documentation

Open-source All in One AI agent workspace. Run any agent — Claude Code, Codex — across your tools (100+ integrations + MCP), apps, browser, and files, with...

RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy holaOS on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name holaos -p 8080:8080 --restart always holaboss-ai/holaos:latest

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 .docx files 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:

bash
curl -fsSL https://raw.githubusercontent.com/holaboss-ai/holaOS/refs/heads/main/scripts/install.sh | bash -s -- --launch

You 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:

bash
git --version
node --version
npm --version

The 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:

text
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.

  1. Install the desktop dependencies from the repository root:
bash
npm run desktop:install
  1. Create your local environment file:
bash
cp apps/desktop/.env.example apps/desktop/.env

If 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.

  1. Prepare the local runtime bundle:
bash
npm run desktop:prepare-runtime:local
  1. If you want a quick validation pass before launching Electron, run:
bash
npm run desktop:typecheck
  1. Start the desktop app in development mode:
bash
npm run desktop:dev

The 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:

bash
npm run desktop:prepare-runtime:local

Fetch the latest published runtime:

bash
npm run desktop:prepare-runtime

Use 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:

  1. File Explorer (left)
  2. In-app Browser panel (center)
  3. 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

bash
npm install
cp .env.example .env
npm run dev

This 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:

bash
cp .env.example .env

npm 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

bash
npm run build

This 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-/:

bash
npm run prepare:runtime

That stages the bundle for the current host platform from an explicit runtime source. If you need to target a specific platform explicitly:

bash
npm run prepare:runtime:macos
npm run prepare:runtime:linux
npm run prepare:runtime:windows

For local development against unreleased holaOS runtime changes:

bash
# 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:local

After 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:

bash
npm run dist:mac
npm run dist:mac:dmg
npm run dist:win

The staging script accepts one of:

  • HOLABOSS_RUNTIME_DIR=/absolute/path/to/runtime-
  • HOLABOSS_RUNTIME_TARBALL=/absolute/path/to/holaboss-runtime--<sha>.tar.gz
  • HOLABOSS_RUNTIME_BUNDLE_URL=https://.../holaboss-runtime--<sha>.tar.gz
  • HOLABOSS_RUNTIME_PLATFORM=macos|linux|windows to 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/python
  • HOLABOSS_RUNTIME_PYTHON_TARBALL=/absolute/path/to/cpython-...tar.gz
  • HOLABOSS_RUNTIME_PYTHON_URL=https://.../cpython-...tar.gz
  • HOLABOSS_RUNTIME_PYTHON_VERSION, HOLABOSS_RUNTIME_PYTHON_RELEASE, HOLABOSS_RUNTIME_PYTHON_VARIANT, and HOLABOSS_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:

bash
npm run dist:mac

Use 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:

bash
npm run dist:win

Use 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_URL for Better Auth session endpoint
  • HOLABOSS_AUTH_SIGN_IN_URL for the hosted sign-in page
  • HOLABOSS_BACKEND_BASE_URL for the Holaboss backend base URL used by the desktop
  • HOLABOSS_PACKAGED_APP_BIN for an explicit packaged binary path

Start by copying the template:

bash
cp .env.example .env

To build a mac installer image:

bash
npm run dist:mac:dmg

This produces a local-use .dmg installer in out/release/.

Notes:

  • dist:mac builds an unpacked .app
  • dist:mac:dmg builds a .dmg installer
  • 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.yml to 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-releases tag
  • 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.yml to 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.yml assets
  • 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.R format
  • 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.1 is 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.Z suffix in release_tag, so tags should end with the same YYYY.MDD.R value
  • 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-encoded Developer ID Application .p12
  • MAC_CERTIFICATE_PASSWORD: password for the .p12
  • APPLE_ID: Apple Developer account email
  • APPLE_APP_SPECIFIC_PASSWORD: app-specific password from Apple ID settings
  • APPLE_TEAM_ID: Apple Developer Team ID
  • AZURE_TENANT_ID: Microsoft Entra tenant ID for the Windows signing service principal
  • AZURE_CLIENT_ID: client ID for the Windows signing service principal
  • AZURE_CLIENT_SECRET: client secret value for the Windows signing service principal; use the secret Value, not the secret ID
  • HOLABOSS_RELEASES_REPO_TOKEN: token with read access to the private source repo and write access to holaboss-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 region
  • WINDOWS_SIGNING_ACCOUNT_NAME: Trusted Signing account name
  • WINDOWS_SIGNING_CERTIFICATE_PROFILE_NAME: certificate profile name
  • WINDOWS_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 ship
  • release_tag: the GitHub release tag to create or update, in holaOS-YYYY.MDD.R format
  • release_title: optional display title for the GitHub release
  • prerelease: 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:

bash
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.app

Project Structure

text
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.css

Browser 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.tsx handles message rendering and send flow

Replace point for real AI backend:

  • Hook send flow in src/components/panes/ChatPane.tsx where generateMockReply is called
  • Replace timeout mock with API call and stream/append tokens as needed

Security

Renderer runs with:

  • contextIsolation: true
  • nodeIntegration: false
  • webviewTag: 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:

  1. No Connect button rendered in the chat after the agent reported
    the app was ready.
  2. First /api/composio/connect call always failed with a 502;
    retrying always worked.
  3. 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)

text
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:

  1. pendingIntegrationsFromAppManifests (runtime-agent-tools.ts) reads each
    app's app.runtime.yaml, parses integrations[] (and the legacy
    integration singular via integration-types.ts:88 fallback), and emits
    one entry per required: true integration. This part already works
    correctly for Discord
    — integration: { destination: "discordbot" }
    resolves to provider: "discordbot".
  2. claimed-input-executor.ts:3266 PENDING_INTEGRATION_EMITTING_TOOLS only
    scanned the output of four tools:
    • workspace_apps_install
    • workspace_apps_ensure_running
    • workspace_apps_restart
    • workspace_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 on
ensure_running, but in practice the LLM does not always follow that path.

Fix

Two changes, both in holaOS/runtime/api-server/src/:

  1. 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",
  2. runtime-agent-tools.ts — introduce a private helper
    pendingIntegrationsForApps(workspaceId, appIds) and call it at the tail
    of every completion-type tool method so the JSON result carries
    pending_integrations: [...] whenever a registered app has at least one
    required: true integration:

    • scaffoldWorkspaceApp
    • registerWorkspaceApp
    • buildWorkspaceApp (both skipped and normal paths)
    • getWorkspaceAppStatus (single-app + all-apps variants)
    • restartWorkspaceApp
    • waitUntilWorkspaceAppReady (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.ts
  • runtime/api-server/src/runtime-agent-tools.ts

Verification

  1. Rebuild runtime + redeploy bundled .pyc into desktop:
    bash
    cd holaOS
    npm run desktop:prepare-runtime:local
  2. Restart the desktop app.
  3. Ask the agent to create a Discord module from scratch.
  4. 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/connect first-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 from
process.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:

  1. The runtime writes the new binding to runtime.db correctly.
  2. The integration-broker can resolve the binding on subsequent calls.
  3. 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) and
upsertIntegrationBinding (writes the workspace-app binding) in
IntegrationConnectCard.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 the
workspace-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 — expose
window.electronAPI.workspace.restartApp(workspaceId, appId).

4. Renderer type definition

src/types/electron.d.ts — add the method signature to the
workspace IPC surface.

5. Renderer consumer

src/components/panes/ChatPane/AssistantTurn/IntegrationConnectCard.tsx
— extract a rebootAppAfterBindChange helper; call it in both
handleConnect (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

  1. Rebuild desktop:
    bash
    cd holaOS
    npm run desktop:install
    npm run desktop:dev
  2. Create a fresh Discord module (Stage 1 must already be deployed —
    otherwise the Connect card may not appear at all).
  3. Click Connect Discord, complete the OAuth flow in the popped browser.
  4. After the card transitions to the "Bound to discordbot-module" state,
    immediately ask the agent to call a Discord tool (e.g. list guilds).
  5. The tool MUST work on the first call — no manual restart, no
    "integration not bound" error.
  6. Repeat with switching accounts (drop-down "Switch to account 2") to
    confirm handleBind also 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/connect first-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.