From db18dc8916594ed3a976ae7f719cc4764acfd8bd Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Jan 2026 16:44:26 +0000 Subject: [PATCH 1/4] docs: add comprehensive Iris Apps architecture planning This introduces the complete architecture documentation for Iris Apps, a revolutionary system where apps are developed inside Iris, expose tools to AI agents, and can run standalone or be shared with others. Documents included: - Vision: Philosophy and goals for the system - Architecture: System components and their interactions - App Model: App structure, lifecycle, and configuration - SDK Design: APIs for server and React client - Security Model: Permissions, sandboxing, and audit logging - Resilience: Error handling and development experience - Protocol: Communication protocols between components - Implementation Roadmap: Phased approach with milestones Key innovations: - Self-hosted development (build apps inside Iris) - AI-native tools as first-class citizens - Graceful error handling during development - Three modes: development, installed, standalone - Robust permission and security model --- docs/apps-architecture/00-vision.md | 162 ++++ docs/apps-architecture/01-architecture.md | 603 ++++++++++++ docs/apps-architecture/02-app-model.md | 710 ++++++++++++++ docs/apps-architecture/03-sdk-design.md | 783 ++++++++++++++++ docs/apps-architecture/04-security-model.md | 624 +++++++++++++ docs/apps-architecture/05-resilience.md | 878 ++++++++++++++++++ docs/apps-architecture/06-protocol.md | 790 ++++++++++++++++ .../07-implementation-roadmap.md | 814 ++++++++++++++++ docs/apps-architecture/README.md | 148 +++ 9 files changed, 5512 insertions(+) create mode 100644 docs/apps-architecture/00-vision.md create mode 100644 docs/apps-architecture/01-architecture.md create mode 100644 docs/apps-architecture/02-app-model.md create mode 100644 docs/apps-architecture/03-sdk-design.md create mode 100644 docs/apps-architecture/04-security-model.md create mode 100644 docs/apps-architecture/05-resilience.md create mode 100644 docs/apps-architecture/06-protocol.md create mode 100644 docs/apps-architecture/07-implementation-roadmap.md create mode 100644 docs/apps-architecture/README.md diff --git a/docs/apps-architecture/00-vision.md b/docs/apps-architecture/00-vision.md new file mode 100644 index 0000000..28ca802 --- /dev/null +++ b/docs/apps-architecture/00-vision.md @@ -0,0 +1,162 @@ +# Iris Apps: Vision Document + +## The Problem + +Today's software development is fractured: + +1. **IDEs are separate from runtime** - You write code in VS Code, but your app runs elsewhere. Context switching kills flow. + +2. **AI assistants are disconnected** - AI can help write code, but it can't truly interact with your running application. It suggests; it doesn't collaborate. + +3. **Extensions are second-class** - VS Code extensions can't really "do" things in your application. They're limited to IDE chrome, not application logic. + +4. **Mobile is an afterthought** - Most development tools barely work on tablets, let alone phones. Yet mobile is where users spend their time. + +5. **Deployment is a chasm** - The gap between "it works on my machine" and "it's deployed" remains enormous. + +## The Vision + +**Iris Apps** is a new paradigm where: + +> *The application you're building is running inside the tool you're using to build it, and an AI agent can interact with both.* + +### Core Principles + +#### 1. Self-Hosted Development +You build Iris Apps inside Iris. The app runs as a tab alongside your code. Edit a file, see it update instantly. No separate terminal, no browser tab, no context switch. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ IRIS │ +├─────────────┬───────────────────────────────────────────────┤ +│ Files │ [app.json] [server.ts] [▶ My App] │ +│ ├─ app.json │ ┌─────────────────────────────────────────┐ │ +│ ├─ server.ts│ │ │ │ +│ └─ ui/ │ │ Your app running live here │ │ +│ └─ App.tsx│ │ │ │ +│ │ │ Edit code on left │ │ +│ │ │ See changes instantly on right │ │ +│ │ │ │ │ +│ │ └─────────────────────────────────────────┘ │ +└─────────────┴───────────────────────────────────────────────┘ +``` + +#### 2. AI-Native Architecture +Apps aren't just code—they expose **tools** that AI agents can use. The agent doesn't just help you write a database query; it can *run* the query through your app's exposed tool and see the results. + +```typescript +// Your app exposes this tool +defineTool({ + name: 'query_database', + description: 'Run a SQL query', + execute: async ({ sql }) => db.query(sql) +}) + +// The AI agent can now: +// 1. Understand what your app does +// 2. Actually use it to accomplish tasks +// 3. Help users interact with YOUR app +``` + +#### 3. Graceful Degradation +During development, code breaks. That's normal. Iris Apps are resilient: + +- **Broken UI?** Show an error overlay with source link, not a white screen +- **Server crash?** Catch it, show the stack trace, let the developer fix it +- **Partial functionality?** Keep working parts running while broken parts show helpful errors + +The development experience should feel like pair programming with a safety net, not walking a tightrope. + +#### 4. Universal Runtime +An Iris App runs in three modes from the same codebase: + +| Mode | Context | Use Case | +|------|---------|----------| +| **Development** | Inside Iris IDE | Building the app | +| **Installed** | As a tab in any Iris project | Using the app as a tool | +| **Standalone** | Independent deployment | Production, sharing | + +Write once. Run everywhere. Not as a compromise, but as a feature. + +#### 5. Mobile-First +Iris Apps work beautifully on phones and tablets. Not "mobile-compatible"—truly mobile-first: + +- Touch-optimized interactions +- Responsive layouts by default +- Native performance through React Native +- Develop on your iPad, deploy to the world + +## What This Enables + +### For Individual Developers +- Build custom tools that integrate with AI assistance +- Create personal productivity apps in minutes +- Prototype ideas with instant feedback +- Take your tools with you on any device + +### For Teams +- Share internal tools as installable apps +- Standardize workflows across projects +- Give AI agents team-specific capabilities +- Collaborate on apps in real-time + +### For the Ecosystem +- A new category of AI-enhanced applications +- Apps that get smarter as AI improves +- A marketplace of composable capabilities +- The bridge between code and conversation + +## The Breakthrough + +The key insight is this: + +> **Applications and development environments have been artificially separated.** + +When your app runs inside your development environment: +- The AI can see and interact with your actual application +- Errors become learning opportunities, not catastrophes +- The feedback loop approaches zero latency +- The boundary between "building" and "using" dissolves + +This is not incremental improvement. This is a new way of creating software. + +## Success Metrics + +We'll know we've succeeded when: + +1. **Developers build apps without leaving Iris** - No context switching to browsers, terminals, or other tools + +2. **AI agents meaningfully use app tools** - Not just code suggestions, but actual tool invocation and result interpretation + +3. **Broken code doesn't break flow** - Errors are informative and recoverable, not showstoppers + +4. **Apps deploy with one command** - From development to production is a single step + +5. **Mobile development feels native** - Building on a tablet is as natural as on a desktop + +## What We're Not Building + +To stay focused, we explicitly exclude: + +- **A general-purpose IDE** - Iris is for Iris Apps, not arbitrary codebases +- **A no-code platform** - This is for developers who write code +- **A runtime without AI** - AI integration is core, not optional +- **A web-only solution** - Mobile parity is a requirement + +## The Name + +**Iris** - The messenger goddess, the rainbow bridge between worlds. + +We're building the bridge between: +- Development and runtime +- Human intent and machine execution +- Code and conversation +- Desktop and mobile + +**Iris Apps** - Applications that see, understand, and respond. + +--- + +*"The best way to predict the future is to invent it." — Alan Kay* + +*We're not predicting. We're building.* diff --git a/docs/apps-architecture/01-architecture.md b/docs/apps-architecture/01-architecture.md new file mode 100644 index 0000000..fab3b5d --- /dev/null +++ b/docs/apps-architecture/01-architecture.md @@ -0,0 +1,603 @@ +# Iris Apps: System Architecture + +## Overview + +The Iris Apps architecture consists of five major subsystems that work together to enable the development, execution, and deployment of AI-integrated applications. + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ IRIS PLATFORM │ +├─────────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ +│ │ App Manager │ │ Tool Registry │ │ Service Runner │ │ +│ │ │ │ │ │ │ │ +│ │ • Lifecycle │ │ • Discovery │ │ • Process mgmt│ │ +│ │ • Hot reload │ │ • Invocation │ │ • Health check│ │ +│ │ • Isolation │ │ • Permissions │ │ • Logging │ │ +│ └───────┬────────┘ └───────┬────────┘ └───────┬────────┘ │ +│ │ │ │ │ +│ └───────────────────┼───────────────────┘ │ +│ │ │ +│ ┌─────────▼─────────┐ │ +│ │ App Runtime │ │ +│ │ │ │ +│ │ • Server exec │ │ +│ │ • State sync │ │ +│ │ • Event bridge │ │ +│ └─────────┬─────────┘ │ +│ │ │ +│ ┌───────────────────┼───────────────────┐ │ +│ │ │ │ │ +│ ┌───────▼────────┐ ┌───────▼────────┐ ┌──────▼───────┐ │ +│ │ App Host │ │ AI Agent │ │ Protocol │ │ +│ │ (Frontend) │ │ Integration │ │ Layer │ │ +│ │ │ │ │ │ │ │ +│ │ • iframe │ │ • Tool calls │ │ • WebSocket │ │ +│ │ • Error UI │ │ • Context │ │ • HTTP API │ │ +│ │ • Bridge │ │ • Results │ │ • Events │ │ +│ └────────────────┘ └────────────────┘ └──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +## Subsystem Details + +### 1. App Manager + +The App Manager is responsible for the complete lifecycle of Iris Apps. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ APP MANAGER │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ Discovery Loading Lifecycle │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Scan │ │ Validate │ │ Start │ │ +│ │ project │─────▶│ manifest │─────▶│ runtime │ │ +│ │ for apps │ │ & code │ │ & UI │ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +│ │ │ +│ ┌──────────┐ ┌──────────┐ │ │ +│ │ Watch │ │ Hot │◀───────────┘ │ +│ │ files │─────▶│ reload │ │ +│ └──────────┘ └──────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**Responsibilities:** + +| Function | Description | +|----------|-------------| +| **Discovery** | Scan project directories for `app.json` manifests | +| **Validation** | Verify manifest schema, check dependencies | +| **Loading** | Import server module, initialize state | +| **Hot Reload** | Watch source files, reload on change | +| **Isolation** | Ensure apps don't interfere with each other | +| **Lifecycle** | Start, stop, restart, unload apps | + +**Key Data Structures:** + +```typescript +interface ManagedApp { + // Identity + id: string; // Unique app instance ID + manifest: AppManifest; // Parsed app.json + projectId: string; // Owning project + + // Paths + rootPath: string; // App directory + serverPath: string; // server.ts location + uiPath: string; // ui/ directory + + // Runtime state + status: AppStatus; // loading | active | error | stopped + serverModule: AppServerModule; // Loaded server.ts exports + devServer: DevServer | null; // Vite dev server for UI + + // Error tracking + lastError: AppError | null; + errorCount: number; + + // Hot reload + watcher: FSWatcher; + reloadCount: number; +} + +type AppStatus = + | 'discovered' // Found app.json, not yet loaded + | 'loading' // Loading server module + | 'starting' // Running onActivate + | 'active' // Fully running + | 'error' // Failed, not running + | 'stopping' // Running onDeactivate + | 'stopped'; // Cleanly stopped +``` + +### 2. App Runtime + +The App Runtime executes app server code and manages reactive state. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ APP RUNTIME │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Server Module │ │ +│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ +│ │ │ State │ │ Queries │ │ Tools │ │ │ +│ │ │ │ │ │ │ │ │ │ +│ │ │ Reactive │ │ Async │ │ AI- │ │ │ +│ │ │ values │ │ data │ │ callable │ │ │ +│ │ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │ │ +│ │ │ │ │ │ │ +│ │ └──────────────┼──────────────┘ │ │ +│ │ │ │ │ +│ └───────────────────────┼───────────────────────────────┘ │ +│ │ │ +│ ┌───────▼───────┐ │ +│ │ State Store │ │ +│ │ │ │ +│ │ Observable │ │ +│ │ values with │ │ +│ │ subscribers │ │ +│ └───────┬───────┘ │ +│ │ │ +│ ┌─────────────┼─────────────┐ │ +│ │ │ │ │ +│ ┌───────▼───────┐ ┌───▼───┐ ┌───────▼───────┐ │ +│ │ UI Sync │ │ Tools │ │ Persistence │ │ +│ │ (WebSocket) │ │ │ │ (Optional) │ │ +│ └───────────────┘ └───────┘ └───────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**State Model:** + +The runtime implements a reactive state system inspired by signals/atoms: + +```typescript +// Primitive state - simple reactive value +const count = state(0); +count.get(); // 0 +count.set(5); // Updates all subscribers +count.update(n => n + 1); + +// Computed state - derived from other state +const doubled = computed(() => count.get() * 2); + +// Async state - for data fetching +const users = query(async () => { + const response = await fetch('/api/users'); + return response.json(); +}); +users.get(); // Returns cached data or undefined +users.load(); // Triggers fetch, returns promise +users.invalidate(); // Clears cache, re-fetches on next get +``` + +**Execution Context:** + +Every tool execution and lifecycle hook receives a context: + +```typescript +interface AppContext { + // Identity + appId: string; + projectId: string; + projectPath: string; + + // State access + state: Record; + queries: Record; + + // Services + getService(name: string): Promise; + startService(name: string): Promise; + stopService(name: string): Promise; + + // Configuration + getConfig(key: string): Promise; + setConfig(key: string, value: T): Promise; + + // Events + emit(event: string, payload: unknown): void; + on(event: string, handler: (payload: unknown) => void): () => void; + + // Logging + log: Logger; +} +``` + +### 3. Tool Registry + +The Tool Registry manages tools exposed by apps to AI agents. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ TOOL REGISTRY │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Tool Index │ │ +│ │ │ │ +│ │ app:database-explorer │ │ +│ │ ├─ query "Execute SQL query" │ │ +│ │ ├─ list_tables "List database tables" │ │ +│ │ └─ describe "Describe table schema" │ │ +│ │ │ │ +│ │ app:api-tester │ │ +│ │ ├─ request "Make HTTP request" │ │ +│ │ └─ save_request "Save request to collection" │ │ +│ │ │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Discovery │ │ Invocation │ │ Permissions │ │ +│ │ │ │ │ │ │ │ +│ │ • Scan apps │ │ • Validate │ │ • Check │ │ +│ │ • Extract │ │ • Execute │ │ • Approve │ │ +│ │ • Register │ │ • Return │ │ • Log │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**Tool Namespacing:** + +Tools are namespaced to prevent collisions: + +``` +{source}:{app-name}/{tool-name} + +Examples: + app:database-explorer/query # Tool from an app + builtin:filesystem/read # Built-in Iris tool + mcp:github/create_issue # Tool from MCP server +``` + +**Tool Definition:** + +```typescript +interface ToolDefinition { + name: string; + description: string; + parameters: z.ZodSchema; + + // Execution + execute: (args: unknown, ctx: ToolContext) => Promise; + + // Optional metadata + category?: string; + requiresApproval?: boolean; + timeout?: number; + + // UI hints for the agent + examples?: ToolExample[]; + relatedTools?: string[]; +} + +interface ToolResult { + success: boolean; + data?: unknown; + error?: string; + + // Rich output for agent + summary?: string; // Human-readable summary + artifacts?: Artifact[]; // Files, images, etc. +} +``` + +### 4. Service Runner + +The Service Runner manages long-running processes defined by apps. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ SERVICE RUNNER │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Service Pool │ │ +│ │ │ │ +│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ +│ │ │ db-conn │ │ api-server │ │ watcher │ │ │ +│ │ │ ● running │ │ ● running │ │ ○ stopped │ │ │ +│ │ │ pid: 1234 │ │ pid: 1235 │ │ │ │ │ +│ │ │ 2m uptime │ │ 5m uptime │ │ │ │ │ +│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ +│ │ │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ Process Management Health Monitoring │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ • Spawn │ │ • Heartbeat │ │ +│ │ • Signal │ │ • Restart │ │ +│ │ • Stream I/O │ │ • Backoff │ │ +│ │ • Cleanup │ │ • Alerts │ │ +│ └──────────────┘ └──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**Service Types:** + +| Type | Description | Example | +|------|-------------|---------| +| **Process** | External command | `bun run server.ts` | +| **Internal** | In-process service | Database connection pool | +| **Managed** | Iris-supervised | Dev servers, watchers | + +**Service Definition:** + +```typescript +interface ServiceDefinition { + name: string; + description?: string; + + // For process services + command?: string; + args?: string[]; + cwd?: string; + env?: Record; + + // For internal services + start?: (ctx: ServiceContext) => Promise; + stop?: (instance: unknown) => Promise; + + // Lifecycle + autoStart?: boolean; // Start when app activates + restartOnCrash?: boolean; + maxRestarts?: number; + restartDelay?: number; // Exponential backoff base + + // Health + healthCheck?: () => Promise; + healthInterval?: number; +} +``` + +### 5. App Host (Frontend) + +The App Host renders app UIs with isolation and error handling. + +``` +┌─────────────────────────────────────────────────────────────┐ +│ APP HOST │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Tab Container │ │ +│ │ ┌─────────────────────────────────────────────┐ │ │ +│ │ │ [My App] [Other Tab] [+] │ │ │ +│ │ └─────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ │ ┌─────────────────────────────────────────────┐ │ │ +│ │ │ App Frame │ │ │ +│ │ │ ┌───────────────────────────────────────┐ │ │ │ +│ │ │ │ │ │ │ │ +│ │ │ │ iframe sandbox │ │ │ │ +│ │ │ │ │ │ │ │ +│ │ │ │ App UI renders here │ │ │ │ +│ │ │ │ Isolated from Iris shell │ │ │ │ +│ │ │ │ │ │ │ │ +│ │ │ └───────────────────────────────────────┘ │ │ │ +│ │ │ │ │ │ +│ │ │ ┌───────────────────────────────────────┐ │ │ │ +│ │ │ │ Bridge Layer (postMessage) │ │ │ │ +│ │ │ └───────────────────────────────────────┘ │ │ │ +│ │ └─────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ Error Overlay (when app crashes) │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ ⚠️ Error in App.tsx:42 │ │ +│ │ TypeError: Cannot read 'map' of undefined │ │ +│ │ │ │ +│ │ [View Source] [Retry] [Stop App] │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**Isolation Strategy:** + +| Concern | Solution | +|---------|----------| +| **JavaScript** | iframe with separate context | +| **CSS** | iframe boundary prevents bleed | +| **Crashes** | Caught by error boundary, doesn't affect shell | +| **Memory** | iframe can be destroyed to reclaim | +| **Security** | sandbox attribute restricts capabilities | + +**Bridge Protocol:** + +The App Host and iframe communicate via postMessage: + +```typescript +// Host → App +{ type: 'iris:init', payload: { projectId, theme, ... } } +{ type: 'iris:theme', payload: { theme: 'dark' } } +{ type: 'iris:state', payload: { key: 'users', value: [...] } } + +// App → Host +{ type: 'iris:ready' } +{ type: 'iris:action', payload: { name: 'query', args: {...} } } +{ type: 'iris:navigate', payload: { path: '/files/...' } } +{ type: 'iris:error', payload: { message, stack, file, line } } +``` + +## Data Flow + +### Development Mode + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ Developer │ │ Iris │ │ App │ +│ │ │ (Host) │ │ (iframe) │ +└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ + │ │ │ + │ Edit server.ts │ │ + │──────────────────▶│ │ + │ │ │ + │ │ Hot reload │ + │ │ server module │ + │ │ │ + │ │ Push state │ + │ │──────────────────▶│ + │ │ │ + │ │ │ Re-render + │ │ │ with new state + │ │ │ + │ Edit App.tsx │ │ + │──────────────────▶│ │ + │ │ │ + │ │ Vite HMR │ + │ │──────────────────▶│ + │ │ │ + │ │ │ Fast refresh + │ │ │ (preserves state) + │ │ │ +``` + +### Tool Invocation + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ AI Agent │ │ Iris │ │ App │ │ App UI │ +│ │ │ Server │ │ Runtime │ │ │ +└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ + │ │ │ │ + │ Tool call: │ │ │ + │ app:myapp/query │ │ │ + │──────────────────▶│ │ │ + │ │ │ │ + │ │ Route to app │ │ + │ │──────────────────▶│ │ + │ │ │ │ + │ │ │ Execute tool │ + │ │ │ Update state │ + │ │ │ │ + │ │ │ Push state │ + │ │ │──────────────────▶│ + │ │ │ │ + │ │ Return result │ │ Re-render + │ │◀──────────────────│ │ + │ │ │ │ + │ Result + state │ │ │ + │◀──────────────────│ │ │ + │ │ │ │ +``` + +## Deployment Architecture + +### Standalone Mode + +When an Iris App runs standalone (outside Iris): + +``` +┌─────────────────────────────────────────────────────────────┐ +│ STANDALONE APP │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ @iris/runtime │ │ +│ │ │ │ +│ │ Minimal runtime that provides: │ │ +│ │ • State management │ │ +│ │ • Tool execution (without AI, via API) │ │ +│ │ • Service management │ │ +│ │ • WebSocket server for UI │ │ +│ │ │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────┐ ┌──────────────────────┐ │ +│ │ Server Bundle │ │ UI Bundle │ │ +│ │ (dist/server.js) │ │ (dist/ui/) │ │ +│ └──────────────────────┘ └──────────────────────┘ │ +│ │ +│ No iframe needed - UI served directly │ +│ Tools accessible via REST API │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Installed Mode + +When an app is installed in another project: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ IRIS (Host Project) │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Installed App: database-explorer@1.2.0 │ │ +│ │ │ │ +│ │ Source: npm:@iris-apps/database-explorer │ │ +│ │ Location: .iris/apps/database-explorer/ │ │ +│ │ │ │ +│ │ • Pre-built bundles (no Vite needed) │ │ +│ │ • Tools registered in project scope │ │ +│ │ • Services run on demand │ │ +│ │ • UI loads in iframe from static files │ │ +│ │ │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Technology Choices + +| Component | Technology | Rationale | +|-----------|------------|-----------| +| **Backend Runtime** | Bun | Fast startup, native TypeScript, hot reload | +| **Frontend Build** | Vite | Fast HMR, modern ESM, great DX | +| **UI Framework** | React | Ecosystem, React Native for mobile | +| **State** | Custom (signals-inspired) | Simple, predictable, serializable | +| **IPC** | WebSocket + postMessage | Real-time, bidirectional, secure | +| **Schema** | Zod | Runtime validation, TypeScript inference | +| **Mobile** | React Native | Code sharing, native performance | + +## Security Boundaries + +``` +┌─────────────────────────────────────────────────────────────┐ +│ SECURITY ZONES │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ ZONE 1: Iris Core (Trusted) │ │ +│ │ │ │ +│ │ • Full system access │ │ +│ │ • Manages all apps │ │ +│ │ • Controls permissions │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ ZONE 2: App Server (Sandboxed by Permissions) │ │ +│ │ │ │ +│ │ • Declared permissions only │ │ +│ │ • Project-scoped file access │ │ +│ │ • Network access if permitted │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ ZONE 3: App UI (iframe Sandbox) │ │ +│ │ │ │ +│ │ • No direct system access │ │ +│ │ • Communication only via bridge │ │ +│ │ • Cannot escape iframe │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +*Next: [02-app-model.md](./02-app-model.md) - Detailed app model and lifecycle* diff --git a/docs/apps-architecture/02-app-model.md b/docs/apps-architecture/02-app-model.md new file mode 100644 index 0000000..f4761cd --- /dev/null +++ b/docs/apps-architecture/02-app-model.md @@ -0,0 +1,710 @@ +# Iris Apps: App Model & Lifecycle + +## The App Model + +An Iris App is a self-contained unit that combines: + +1. **Server Logic** - Tools, state, services (runs in Iris backend) +2. **User Interface** - React frontend (runs in sandboxed iframe) +3. **Manifest** - Declaration of capabilities and requirements + +``` +┌─────────────────────────────────────────────────────────────┐ +│ IRIS APP │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ Manifest │ │ +│ │ (app.json) │ │ +│ │ │ │ +│ │ • Identity (name, version, description) │ │ +│ │ • Capabilities (tools, services) │ │ +│ │ • Requirements (permissions, dependencies) │ │ +│ └──────────────────────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────┐ ┌────────────────────────┐ │ +│ │ Server Module │ │ UI Module │ │ +│ │ (server.ts) │ │ (ui/) │ │ +│ │ │ │ │ │ +│ │ • defineApp() │ │ • React components │ │ +│ │ • State definitions │ │ • useApp() hooks │ │ +│ │ • Tool handlers │ │ • Event handlers │ │ +│ │ • Service configs │ │ • Styling │ │ +│ │ • Lifecycle hooks │ │ │ │ +│ └────────────────────────┘ └────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Manifest Schema + +The `app.json` manifest declares everything about an app: + +```json +{ + "$schema": "https://iris.dev/schemas/app.json", + + "name": "database-explorer", + "displayName": "Database Explorer", + "version": "1.0.0", + "description": "Browse and query SQLite databases", + "icon": "database", + "author": "developer@example.com", + "license": "MIT", + + "iris": { + "minVersion": "0.1.0" + }, + + "server": { + "entry": "server.ts" + }, + + "ui": { + "entry": "ui/index.html", + "devPort": 5174 + }, + + "tools": [ + { + "name": "query", + "description": "Execute a SQL query against the database", + "confirmationRequired": true + }, + { + "name": "list_tables", + "description": "List all tables in the database" + } + ], + + "services": [ + { + "name": "connection", + "description": "Database connection pool", + "autoStart": true + } + ], + + "permissions": [ + "filesystem:read", + "filesystem:write:$PROJECT", + "network:localhost", + "ai:chat" + ], + + "configuration": { + "schema": { + "type": "object", + "properties": { + "defaultDatabase": { + "type": "string", + "description": "Path to default database file" + }, + "maxConnections": { + "type": "number", + "default": 5 + } + } + } + }, + + "dependencies": { + "better-sqlite3": "^9.0.0" + } +} +``` + +### Manifest Fields + +| Field | Required | Description | +|-------|----------|-------------| +| `name` | Yes | Unique identifier (lowercase, hyphens) | +| `displayName` | Yes | Human-readable name | +| `version` | Yes | Semver version | +| `description` | No | Brief description | +| `icon` | No | Icon identifier (from Lucide icons) | +| `author` | No | Author email or name | +| `license` | No | SPDX license identifier | +| `iris.minVersion` | No | Minimum Iris version required | +| `server.entry` | Yes | Path to server module | +| `ui.entry` | No | Path to UI entry (if app has UI) | +| `ui.devPort` | No | Port for Vite dev server | +| `tools` | No | Tools exposed to AI agent | +| `services` | No | Background services | +| `permissions` | Yes | Required permissions | +| `configuration` | No | User-configurable settings | +| `dependencies` | No | npm dependencies to install | + +## Permission Model + +Permissions control what an app can access in the Iris environment. + +### Permission Categories + +``` +┌─────────────────────────────────────────────────────────────┐ +│ PERMISSION CATEGORIES │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ FILESYSTEM │ +│ ├─ filesystem:read Read any file │ +│ ├─ filesystem:read:$PROJECT Read within project only │ +│ ├─ filesystem:read:$APP Read within app directory │ +│ ├─ filesystem:write Write any file │ +│ ├─ filesystem:write:$PROJECT Write within project only │ +│ └─ filesystem:write:$APP Write within app directory │ +│ │ +│ NETWORK │ +│ ├─ network:* Any network access │ +│ ├─ network:localhost Localhost only │ +│ ├─ network:fetch HTTP requests only │ +│ └─ network:websocket WebSocket connections │ +│ │ +│ AI │ +│ ├─ ai:chat Use chat completions │ +│ ├─ ai:embed Use embeddings │ +│ └─ ai:tools Invoke other tools via AI │ +│ │ +│ PROCESS │ +│ ├─ process:spawn Spawn child processes │ +│ ├─ process:spawn:$APP Spawn only in app directory │ +│ └─ process:env Access environment variables │ +│ │ +│ IRIS │ +│ ├─ iris:tools Call other Iris tools │ +│ ├─ iris:apps Interact with other apps │ +│ ├─ iris:navigation Navigate Iris UI │ +│ └─ iris:notifications Show notifications │ +│ │ +│ SYSTEM │ +│ ├─ system:clipboard Access clipboard │ +│ └─ system:notifications OS notifications │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Permission Scopes + +Permissions can be scoped to limit access: + +| Scope | Meaning | +|-------|---------| +| `$PROJECT` | Current project directory | +| `$APP` | App's own directory | +| `$CONFIG` | App's configuration directory | +| `$DATA` | App's data directory | +| Domain pattern | e.g., `network:*.example.com` | + +### Permission Inheritance + +``` +Development Mode: + App in project "my-project" + ├─ Inherits project permissions + ├─ Has elevated debug permissions + └─ Can be granted additional permissions interactively + +Installed Mode: + App installed from registry + ├─ Only declared permissions + ├─ User approval required at install + └─ No runtime permission escalation + +Standalone Mode: + App running independently + ├─ Full access (no sandbox) + └─ User controls via deployment config +``` + +## App Lifecycle + +### State Machine + +``` + ┌─────────────┐ + │ DISCOVERED │ + └──────┬──────┘ + │ load() + ▼ + ┌─────────────┐ + ┌───────│ LOADING │───────┐ + │ └──────┬──────┘ │ + │ │ │ + load error load success dependency + │ │ error + │ ▼ │ + │ ┌─────────────┐ │ + │ │ LOADED │ │ + │ └──────┬──────┘ │ + │ │ │ + │ activate() │ + │ │ │ + │ ▼ │ + │ ┌─────────────┐ │ + │ ┌────│ STARTING │────┐ │ + │ │ └──────┬──────┘ │ │ + │ │ │ │ │ + │ │ onActivate() │ │ + │ │ success │ │ + │ │ │ │ │ + │ │ ▼ │ │ + │ │ ┌─────────────┐ │ │ + │ │ │ ACTIVE │◀───┼──┼──── reload() + │ │ └──────┬──────┘ │ │ + │ │ │ │ │ + │ │ deactivate() │ │ + │ │ │ │ │ + │ │ ▼ │ │ + │ │ ┌─────────────┐ │ │ + │ │ │ STOPPING │ │ │ + │ │ └──────┬──────┘ │ │ + │ │ │ │ │ + │ │ onDeactivate() │ │ + │ │ │ │ │ + │ │ ▼ │ │ + │ │ ┌─────────────┐ │ │ + └──┼───▶│ STOPPED │◀───┘ │ + │ └──────┬──────┘ │ + │ │ │ + │ unload() │ + │ │ │ + │ ▼ │ + │ ┌─────────────┐ │ + └───▶│ UNLOADED │◀──────┘ + └─────────────┘ + │ + ┌────┴────┐ + ▼ ▼ + ┌─────────┐ ┌─────────┐ + │ ERROR │ │DESTROYED│ + └─────────┘ └─────────┘ +``` + +### Lifecycle Hooks + +Apps can define hooks for lifecycle events: + +```typescript +export default defineApp({ + // Called when app is first loaded (module evaluation) + // Use for static initialization + onLoad: async (ctx) => { + console.log('App module loaded'); + }, + + // Called when app becomes active (user opens it or Iris starts) + // Use for connecting to services, loading data + onActivate: async (ctx) => { + await ctx.startService('connection'); + const lastQuery = await ctx.getConfig('lastQuery'); + if (lastQuery) { + ctx.state.currentQuery.set(lastQuery); + } + }, + + // Called when app is being deactivated + // Use for cleanup, saving state + onDeactivate: async (ctx) => { + await ctx.setConfig('lastQuery', ctx.state.currentQuery.get()); + await ctx.stopService('connection'); + }, + + // Called when app is being hot-reloaded during development + // Previous state is passed so you can migrate if needed + onReload: async (ctx, previousState) => { + // Migrate state if schema changed + if (previousState.version !== STATE_VERSION) { + ctx.state.data.set(migrateState(previousState.data)); + } + }, + + // Called when an error occurs in the app + // Return true to indicate error was handled + onError: async (ctx, error) => { + ctx.log.error('App error:', error); + ctx.state.lastError.set(error.message); + return true; // Error handled, don't crash + }, +}); +``` + +### Lifecycle Events + +The app runtime emits events during lifecycle transitions: + +| Event | Payload | Description | +|-------|---------|-------------| +| `app:loading` | `{ appId }` | App load started | +| `app:loaded` | `{ appId, manifest }` | App loaded successfully | +| `app:load-error` | `{ appId, error }` | App failed to load | +| `app:activating` | `{ appId }` | App activation started | +| `app:activated` | `{ appId }` | App is now active | +| `app:activate-error` | `{ appId, error }` | Activation failed | +| `app:deactivating` | `{ appId }` | App deactivation started | +| `app:deactivated` | `{ appId }` | App is now stopped | +| `app:reloading` | `{ appId }` | Hot reload started | +| `app:reloaded` | `{ appId }` | Hot reload complete | +| `app:error` | `{ appId, error }` | Runtime error occurred | + +## State Model + +Apps manage state through a reactive system that syncs with the UI. + +### State Types + +```typescript +// 1. Primitive State - Simple reactive values +const count = state(0); +const user = state(null); +const items = state([]); + +// 2. Computed State - Derived from other state +const doubleCount = computed(() => count.get() * 2); +const activeItems = computed(() => items.get().filter(i => i.active)); + +// 3. Query State - Async data with caching +const users = query(async () => { + const response = await fetch('/api/users'); + return response.json(); +}, { + staleTime: 60000, // Consider fresh for 1 minute + cacheTime: 300000, // Keep in cache for 5 minutes + refetchOnFocus: true, // Refetch when app becomes visible +}); + +// 4. Mutation State - Async operations with optimistic updates +const updateUser = mutation(async (userId: string, data: Partial) => { + const response = await fetch(`/api/users/${userId}`, { + method: 'PATCH', + body: JSON.stringify(data), + }); + return response.json(); +}, { + onMutate: (userId, data) => { + // Optimistic update + const prev = users.get().find(u => u.id === userId); + users.update(list => list.map(u => u.id === userId ? {...u, ...data} : u)); + return { prev }; + }, + onError: (error, userId, data, context) => { + // Rollback on error + users.update(list => list.map(u => u.id === userId ? context.prev : u)); + }, +}); +``` + +### State Serialization + +State is automatically serialized for: +- **UI Sync** - Sent to iframe via postMessage +- **Hot Reload** - Preserved across code changes +- **Persistence** - Optionally saved to disk + +```typescript +// State with custom serialization +const connection = state(null, { + // Don't sync to UI (not serializable) + sync: false, + + // Don't persist (recreate on reload) + persist: false, +}); + +// State with persistence +const history = state([], { + persist: true, + persistKey: 'query-history', +}); +``` + +### State Access from UI + +The UI accesses state through the bridge: + +```typescript +// In app UI (React) +function QueryHistory() { + // Subscribes to state updates + const history = useAppState('history'); + + // Call server-side mutations + const clearHistory = useAppAction('clearHistory'); + + return ( +
    + {history.map(item => ( +
  • {item.query}
  • + ))} + +
+ ); +} +``` + +## Tool Model + +Tools are functions that AI agents (and the UI) can invoke. + +### Tool Definition + +```typescript +defineTool({ + // Identity + name: 'query', + description: 'Execute a SQL query against the connected database', + + // Parameter schema (Zod) + parameters: z.object({ + sql: z.string().describe('The SQL query to execute'), + params: z.array(z.unknown()).optional().describe('Query parameters'), + }), + + // Return schema (optional, for documentation) + returns: z.object({ + rows: z.array(z.record(z.unknown())), + rowCount: z.number(), + executionTime: z.number(), + }), + + // Execution handler + execute: async (args, ctx) => { + const db = await ctx.getService('connection'); + const start = Date.now(); + + try { + const rows = await db.query(args.sql, args.params); + return { + success: true, + data: { + rows, + rowCount: rows.length, + executionTime: Date.now() - start, + }, + }; + } catch (error) { + return { + success: false, + error: error.message, + }; + } + }, + + // Metadata + category: 'database', + confirmationRequired: true, // Ask user before executing + timeout: 30000, // 30 second timeout + + // Examples for AI context + examples: [ + { + description: 'Get all users', + args: { sql: 'SELECT * FROM users' }, + }, + { + description: 'Find user by email', + args: { + sql: 'SELECT * FROM users WHERE email = ?', + params: ['user@example.com'], + }, + }, + ], +}); +``` + +### Tool Execution Context + +```typescript +interface ToolContext extends AppContext { + // Caller information + caller: { + type: 'ai' | 'ui' | 'api'; + agentId?: string; // If called by AI + sessionId?: string; // AI session + userId?: string; // User who initiated + }; + + // Progress reporting + progress: (message: string, percent?: number) => void; + + // Cancellation + signal: AbortSignal; + + // Tool-specific permissions (may be elevated) + permissions: PermissionSet; +} +``` + +### Tool Results + +Tools return structured results: + +```typescript +interface ToolResult { + success: boolean; + + // On success + data?: unknown; + + // On failure + error?: string; + errorCode?: string; + + // Rich output + summary?: string; // Human-readable summary for AI + artifacts?: Artifact[]; // Files, images created + + // Side effects + stateUpdates?: Record; // State changes to apply + notifications?: Notification[]; // User notifications +} + +interface Artifact { + type: 'file' | 'image' | 'chart' | 'table'; + name: string; + path?: string; // For files + data?: unknown; // For inline data + mimeType?: string; +} +``` + +## Service Model + +Services are long-running processes or connections managed by the app. + +### Service Definition + +```typescript +// Process service (external command) +const devServer: ServiceDefinition = { + name: 'dev-server', + type: 'process', + command: 'bun', + args: ['run', 'dev'], + cwd: './server', + env: { + PORT: '3001', + }, + autoStart: false, + restartOnCrash: true, + healthCheck: async () => { + const res = await fetch('http://localhost:3001/health'); + return res.ok; + }, +}; + +// Internal service (in-process) +const dbConnection: ServiceDefinition = { + name: 'connection', + type: 'internal', + + start: async (ctx) => { + const config = await ctx.getConfig('database'); + const db = new Database(config.path); + + // Run migrations + await db.exec(MIGRATIONS); + + return db; + }, + + stop: async (db) => { + await db.close(); + }, + + autoStart: true, +}; +``` + +### Service Access + +```typescript +// In tool or lifecycle hook +async execute(args, ctx) { + // Get service instance (starts if needed) + const db = await ctx.getService('connection'); + + // Use service + const result = await db.query(args.sql); + + return { success: true, data: result }; +} +``` + +## Configuration Model + +Apps can define configurable settings: + +### Configuration Schema + +```json +{ + "configuration": { + "schema": { + "type": "object", + "properties": { + "database": { + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "Path to database file" + }, + "maxConnections": { + "type": "number", + "default": 5, + "minimum": 1, + "maximum": 20 + } + }, + "required": ["path"] + }, + "ui": { + "type": "object", + "properties": { + "theme": { + "type": "string", + "enum": ["light", "dark", "auto"], + "default": "auto" + }, + "pageSize": { + "type": "number", + "default": 50 + } + } + } + } + } + } +} +``` + +### Configuration Access + +```typescript +// Server-side +const dbPath = await ctx.getConfig('database.path'); +await ctx.setConfig('ui.theme', 'dark'); + +// Watch for changes +ctx.onConfigChange('database', async (newConfig) => { + await ctx.restartService('connection'); +}); +``` + +### Configuration UI + +Iris auto-generates a settings UI from the schema, or apps can provide custom UI: + +```typescript +// In app.json +{ + "configuration": { + "schema": { ... }, + "ui": "ui/settings.html" // Custom settings page + } +} +``` + +--- + +*Next: [03-sdk-design.md](./03-sdk-design.md) - SDK API design and developer experience* diff --git a/docs/apps-architecture/03-sdk-design.md b/docs/apps-architecture/03-sdk-design.md new file mode 100644 index 0000000..13dc74c --- /dev/null +++ b/docs/apps-architecture/03-sdk-design.md @@ -0,0 +1,783 @@ +# Iris Apps: SDK Design + +## Overview + +The Iris App SDK provides a unified developer experience for building apps that run inside Iris and standalone. The SDK is split into two main packages: + +``` +@iris/app-sdk +├── /server Server-side APIs (tools, state, services) +├── /react React hooks and components for UI +├── /runtime Standalone runtime (for deployment) +└── /types Shared TypeScript types +``` + +## Design Principles + +### 1. Progressive Disclosure +Simple things should be simple. Complex things should be possible. + +```typescript +// Simple: Define a tool in 5 lines +defineTool({ + name: 'hello', + description: 'Say hello', + parameters: z.object({ name: z.string() }), + execute: async ({ name }) => ({ greeting: `Hello, ${name}!` }), +}); + +// Complex: Full control when needed +defineTool({ + name: 'query', + description: '...', + parameters: z.object({ sql: z.string() }), + execute: async (args, ctx) => { + ctx.progress('Connecting...'); + const db = await ctx.getService('db'); + ctx.progress('Executing query...'); + // ... + }, + confirmationRequired: true, + timeout: 60000, + permissions: ['filesystem:read'], +}); +``` + +### 2. Type Safety Throughout +Full TypeScript support with inference. No runtime surprises. + +```typescript +// Types flow from schema to handler to result +const tool = defineTool({ + parameters: z.object({ + userId: z.string(), + includeProfile: z.boolean().optional(), + }), + // args is typed as { userId: string; includeProfile?: boolean } + execute: async (args) => { + const user = await getUser(args.userId); + // Return type is inferred + return { user, timestamp: Date.now() }; + }, +}); +``` + +### 3. Familiar Patterns +Use patterns developers already know (React hooks, Zod, etc.). + +```typescript +// Feels like React Query +const users = useQuery('users'); + +// Feels like useState +const [count, setCount] = useAppState('count'); + +// Feels like event handlers + +``` + +### 4. Resilient by Default +Errors are expected. Handle them gracefully. + +```typescript +// Tools can't crash the app +const result = await runTool('query', { sql }); +if (result.success) { + setResults(result.data.rows); +} else { + setError(result.error); +} + +// UI errors show friendly overlays, not white screens +// Server errors are caught and surfaced +``` + +## Server SDK (`@iris/app-sdk/server`) + +### App Definition + +```typescript +import { + defineApp, + defineTool, + state, + computed, + query, + mutation, +} from '@iris/app-sdk/server'; +import { z } from 'zod'; + +export default defineApp({ + // State definitions + state: { + count: state(0), + items: state([]), + selectedId: state(null), + }, + + // Computed values + computed: { + selectedItem: computed((get) => { + const items = get('items'); + const id = get('selectedId'); + return items.find(i => i.id === id); + }), + itemCount: computed((get) => get('items').length), + }, + + // Async data + queries: { + remoteItems: query(async (ctx) => { + const response = await ctx.fetch('https://api.example.com/items'); + return response.json(); + }, { + staleTime: 60_000, + }), + }, + + // Tools exposed to AI and UI + tools: [ + defineTool({ + name: 'add_item', + description: 'Add a new item to the list', + parameters: z.object({ + title: z.string().describe('Item title'), + priority: z.enum(['low', 'medium', 'high']).optional(), + }), + execute: async (args, ctx) => { + const item: Item = { + id: crypto.randomUUID(), + title: args.title, + priority: args.priority ?? 'medium', + createdAt: Date.now(), + }; + ctx.state.items.update(list => [...list, item]); + return { success: true, item }; + }, + }), + ], + + // Background services + services: { + sync: { + start: async (ctx) => { + // Start sync service + return startSyncService(ctx); + }, + stop: async (service) => { + await service.close(); + }, + autoStart: true, + }, + }, + + // Lifecycle hooks + onActivate: async (ctx) => { + ctx.log.info('App activated'); + }, + + onDeactivate: async (ctx) => { + ctx.log.info('App deactivated'); + }, +}); +``` + +### State API + +```typescript +import { state, computed, query, mutation } from '@iris/app-sdk/server'; + +// Primitive state +const count = state(0); +count.get(); // 0 +count.set(5); // Set to 5 +count.update(n => n + 1); // Increment + +// With options +const history = state([], { + persist: true, // Save to disk + persistKey: 'history', // Custom storage key + sync: true, // Sync to UI (default: true) + maxHistory: 100, // Keep last N values for undo +}); + +// Computed state +const doubled = computed(() => count.get() * 2); +doubled.get(); // Always up-to-date + +// Query state (async with caching) +const users = query(async (ctx) => { + const res = await ctx.fetch('/api/users'); + return res.json(); +}, { + staleTime: 60_000, // Fresh for 1 minute + cacheTime: 300_000, // Cache for 5 minutes + refetchOnFocus: true, // Refetch when app visible + retry: 3, // Retry failed requests +}); + +users.get(); // Current data (may be undefined) +users.load(); // Force load, returns promise +users.invalidate(); // Clear cache + +// Mutation state (async operations) +const createUser = mutation( + async (data: CreateUserInput, ctx) => { + const res = await ctx.fetch('/api/users', { + method: 'POST', + body: JSON.stringify(data), + }); + return res.json(); + }, + { + onMutate: (data, ctx) => { + // Optimistic update + }, + onSuccess: (result, data, ctx) => { + users.invalidate(); + }, + onError: (error, data, ctx, rollback) => { + rollback(); + }, + } +); +``` + +### Tool Definition API + +```typescript +import { defineTool } from '@iris/app-sdk/server'; +import { z } from 'zod'; + +const queryTool = defineTool({ + // Required + name: 'query', + description: 'Execute a SQL query', + parameters: z.object({ + sql: z.string().describe('SQL query to execute'), + params: z.array(z.unknown()).optional(), + }), + execute: async (args, ctx) => { + // Implementation + }, + + // Optional metadata + category: 'database', + tags: ['sql', 'data'], + + // Behavior + confirmationRequired: true, // Prompt user before running + timeout: 30_000, // Execution timeout + retryable: false, // Can AI retry on failure + + // Permissions (elevated beyond app permissions) + permissions: [], + + // Documentation + examples: [ + { + description: 'Select all users', + input: { sql: 'SELECT * FROM users' }, + output: { rows: [{ id: 1, name: 'Alice' }] }, + }, + ], + + // Return type (for documentation/validation) + returns: z.object({ + rows: z.array(z.unknown()), + count: z.number(), + }), +}); +``` + +### Context API + +```typescript +interface AppContext { + // Identity + appId: string; + appName: string; + appVersion: string; + projectId: string; + projectPath: string; + + // State access + state: StateMap; // All state handles + queries: QueryMap; // All query handles + + // Services + getService(name: string): Promise; + startService(name: string): Promise; + stopService(name: string): Promise; + restartService(name: string): Promise; + + // Configuration + getConfig(path: string): Promise; + setConfig(path: string, value: T): Promise; + onConfigChange(path: string, handler: (value: unknown) => void): () => void; + + // Iris Platform Access (requires permissions) + iris: { + // AI capabilities + ai: { + chat(messages: Message[], options?: ChatOptions): Promise; + embed(text: string | string[]): Promise; + complete(prompt: string, options?: CompleteOptions): Promise; + }; + + // Filesystem (scoped by permissions) + fs: { + read(path: string): Promise; + write(path: string, content: string): Promise; + list(path: string): Promise; + exists(path: string): Promise; + watch(path: string, handler: (event: FSEvent) => void): () => void; + }; + + // Other Iris tools + tools: { + list(): Promise; + call(name: string, args: unknown): Promise; + }; + + // Other apps (requires iris:apps permission) + apps: { + list(): Promise; + call(appName: string, toolName: string, args: unknown): Promise; + }; + + // UI navigation + navigate(path: string): void; + openTab(tabData: TabData): void; + + // Notifications + notify(message: string, options?: NotifyOptions): void; + }; + + // Network (scoped by permissions) + fetch: typeof fetch; + + // Logging + log: { + debug(...args: unknown[]): void; + info(...args: unknown[]): void; + warn(...args: unknown[]): void; + error(...args: unknown[]): void; + }; + + // Events + emit(event: string, payload?: unknown): void; + on(event: string, handler: (payload: unknown) => void): () => void; +} +``` + +### Iris Platform Integration + +The SDK provides safe access to Iris capabilities: + +```typescript +// Using AI (requires ai:chat permission) +const response = await ctx.iris.ai.chat([ + { role: 'user', content: 'Summarize this data' }, +], { + model: 'claude-sonnet', + maxTokens: 1000, +}); + +// Reading files (requires filesystem:read permission) +const content = await ctx.iris.fs.read('/path/to/file.txt'); + +// Calling other tools (requires iris:tools permission) +const result = await ctx.iris.tools.call('builtin:bash', { + command: 'ls -la', +}); + +// Navigating Iris UI (requires iris:navigation permission) +ctx.iris.navigate(`/projects/${ctx.projectId}/files/src/index.ts`); + +// Cross-app communication (requires iris:apps permission) +const dbResult = await ctx.iris.apps.call('database-explorer', 'query', { + sql: 'SELECT * FROM users', +}); +``` + +## React SDK (`@iris/app-sdk/react`) + +### Provider Setup + +```tsx +// ui/src/main.tsx +import { IrisAppProvider } from '@iris/app-sdk/react'; +import App from './App'; + +// Provider connects to app server via bridge +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); +``` + +### State Hooks + +```tsx +import { + useAppState, + useComputed, + useQuery, + useMutation, + useAppContext, +} from '@iris/app-sdk/react'; + +function MyComponent() { + // Subscribe to primitive state + const [count, setCount] = useAppState('count'); + // count: number + // setCount: (value: number | (prev: number) => number) => void + + // Subscribe to computed state + const doubled = useComputed('doubled'); + // doubled: number (read-only) + + // Subscribe to query state + const users = useQuery('users'); + // users: { data?: User[], isLoading: boolean, error?: Error, refetch: () => void } + + // Get mutation handle + const createUser = useMutation('createUser'); + // createUser: { mutate: (args) => Promise, isLoading: boolean, error?: Error } + + // Access full app context + const ctx = useAppContext(); + // ctx: { appId, projectId, theme, ... } + + return ( +
+

Count: {count}

+ + +

Doubled: {doubled}

+ + {users.isLoading ? ( + + ) : ( +
    + {users.data?.map(user => ( +
  • {user.name}
  • + ))} +
+ )} + + +
+ ); +} +``` + +### Tool Hooks + +```tsx +import { useTool, useToolCall } from '@iris/app-sdk/react'; + +function QueryRunner() { + // Full tool control + const queryTool = useTool('query'); + // queryTool: { + // call: (args) => Promise, + // isRunning: boolean, + // lastResult?: ToolResult, + // lastError?: Error, + // reset: () => void, + // } + + // Simple one-shot call + const [runQuery, queryState] = useToolCall('query'); + // runQuery: (args) => Promise + // queryState: { isRunning, result?, error? } + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + const result = await queryTool.call({ sql: e.target.sql.value }); + if (result.success) { + setResults(result.data.rows); + } + }; + + return ( +
+