### Start Development Server Source: https://github.com/langchain-ai/openwork/blob/main/CONTRIBUTING.md Launch the development server to start working on the project. This command is used after installing dependencies. ```bash npm run dev ``` -------------------------------- ### Install Dependencies Source: https://github.com/langchain-ai/openwork/blob/main/CONTRIBUTING.md Install all project dependencies using npm. Ensure Node.js 20+ and npm 10+ are installed. ```bash npm install ``` -------------------------------- ### Install and Run Openwork from Source Source: https://github.com/langchain-ai/openwork/blob/main/README.md Clone the repository, install dependencies, and run the development server. Requires Node.js 18+. ```bash git clone https://github.com/langchain-ai/openwork.git cd openwork npm install npm run dev ``` -------------------------------- ### Install and Launch Openwork CLI Source: https://context7.com/langchain-ai/openwork/llms.txt Install the openwork CLI globally using npm and launch the application. Alternatively, run it directly without installation using npx. ```bash npm install -g openwork openwork ``` ```bash npx openwork ``` -------------------------------- ### Show Openwork Version and Help Source: https://context7.com/langchain-ai/openwork/llms.txt Display the installed version of openwork or show the help message detailing available commands and usage. ```bash openwork --version # openwork v0.1.0 ``` ```bash openwork --help ``` -------------------------------- ### Install and Run Openwork with npx Source: https://github.com/langchain-ai/openwork/blob/main/README.md Use this command to run Openwork directly without global installation. Requires Node.js 18+. ```bash # Run directly with npx npx openwork # Or install globally npm install -g openwork openwork ``` -------------------------------- ### Thread Database Helpers: Initialize and CRUD Operations Source: https://context7.com/langchain-ai/openwork/llms.txt Initializes the main OpenWork SQLite database and provides synchronous CRUD helpers for threads. Call `initializeDatabase` once at app start. Use `flush` to force immediate disk writes. ```typescript import { initializeDatabase, getAllThreads, getThread, createThread, updateThread, deleteThread, flush } from "./src/main/db" // Call once at app start (in main process) await initializeDatabase() // Create a thread row const row = createThread("550e8400-e29b-41d4-a716-446655440000", { workspacePath: "/Users/alice/projects/my-app", model: "claude-sonnet-4-5-20250929" }) // List all threads ordered by updated_at DESC const rows = getAllThreads() // [{ thread_id, created_at, updated_at, metadata, status, thread_values, title }, ...] // Update title and status const updated = updateThread("550e8400-e29b-41d4-a716-446655440000", { title: "Migrate DB to Postgres", status: "busy" }) // Read metadata back const existing = getThread("550e8400-e29b-41d4-a716-446655440000") const meta = existing?.metadata ? JSON.parse(existing.metadata) : {} console.log(meta.workspacePath) // "/Users/alice/projects/my-app" // Delete (cascades to runs) deleteThread("550e8400-e29b-41d4-a716-446655440000") // Force immediate disk write (normally debounced 100 ms) await flush() ``` -------------------------------- ### Manage Models and API Keys with `models:*` IPC Source: https://context7.com/langchain-ai/openwork/llms.txt Interact with available models and manage provider API keys. This includes listing models and providers, setting and getting default models, storing and retrieving API keys, and removing keys. API keys are persisted to `~/.openwork/.env`. ```typescript // List all models (available=true only for providers with configured keys) const models = await window.api.models.list() // [{ id, name, provider, model, description, available }, ...] // e.g. { id: "claude-sonnet-4-5-20250929", provider: "anthropic", available: true } ``` ```typescript // List providers with hasApiKey status const providers = await window.api.models.listProviders() // [{ id: "anthropic", name: "Anthropic", hasApiKey: true }, ...] ``` ```typescript // Get / set the default model const defaultModel = await window.api.models.getDefault() // "claude-sonnet-4-5-20250929" await window.api.models.setDefault("gpt-4.1") ``` ```typescript // Store an API key (written to ~/.openwork/.env as ANTHROPIC_API_KEY=sk-ant-...) await window.api.models.setApiKey("anthropic", "sk-ant-api03-...") await window.api.models.setApiKey("openai", "sk-proj-...") await window.api.models.setApiKey("google", "AIzaSy...") ``` ```typescript // Read back a stored key const key = await window.api.models.getApiKey("anthropic") // "sk-ant-api03-..." ``` ```typescript // Remove a key await window.api.models.deleteApiKey("openai") ``` -------------------------------- ### Build Project Source: https://github.com/langchain-ai/openwork/blob/main/CONTRIBUTING.md Build the project for all platforms. This command is typically run before deployment or release. ```bash npm run build ``` -------------------------------- ### Initialize and Use LocalSandbox for Shell Commands Source: https://context7.com/langchain-ai/openwork/llms.txt Instantiate LocalSandbox with configuration for root directory, virtual mode, command timeout, and maximum output size. Execute shell commands and inspect the output, exit code, and truncation status. Handles command timeouts by returning an error. ```typescript import { LocalSandbox } from "./src/main/agent/local-sandbox" const sandbox = new LocalSandbox({ rootDir: "/Users/alice/projects/my-app", virtualMode: false, // use absolute host paths timeout: 60_000, // 1 minute per command maxOutputBytes: 50_000 // truncate at ~50 KB }) // Run a shell command — returns { output, exitCode, truncated } const result = await sandbox.execute("npm test 2>&1") console.log("Exit code:", result.exitCode) // 0 console.log("Truncated:", result.truncated) // false console.log(result.output) // > my-app@1.0.0 test // > jest // PASS src/utils.test.ts // Tests: 12 passed, 12 total // Timeout example const slow = await sandbox.execute("sleep 200") console.log(slow.output) // "Error: Command timed out after 60.0 seconds." console.log(slow.exitCode) // null // Inherited filesystem methods (from FilesystemBackend / deepagents) // sandbox.ls(), sandbox.read_file(), sandbox.write_file(), // sandbox.edit_file(), sandbox.glob(), sandbox.grep() ``` -------------------------------- ### Clone and Navigate Repository Source: https://github.com/langchain-ai/openwork/blob/main/CONTRIBUTING.md Clone the openwork repository and navigate into the project directory. Replace YOUR_USERNAME with your GitHub username. ```bash git clone https://github.com/YOUR_USERNAME/openwork.git cd openwork ``` -------------------------------- ### openwork CLI Source: https://context7.com/langchain-ai/openwork/llms.txt The `openwork` binary is used to launch the Electron application. It supports basic flags for version and help information. ```APIDOC ## openwork CLI ### Description Launches the Electron application. Supports `--version` / `-v` and `--help` / `-h` flags. ### Usage ```bash # Install globally and launch npm install -g openwork openwork # Run without installing npx openwork # Show version openwork --version # Show help openwork --help ``` ``` -------------------------------- ### Storage Utilities - API Key & Path Management Source: https://context7.com/langchain-ai/openwork/llms.txt Provides low-level utilities for managing API keys stored in `~/.openwork/.env` and resolving application data paths. ```APIDOC ## Storage Utilities ### Description Low-level helpers for persisting API keys in `~/.openwork/.env` and resolving application data paths. ### Methods #### `getOpenworkDir()` Resolves the base Openwork directory path. #### `getDbPath()` Resolves the path to the main Openwork SQLite database. #### `getThreadCheckpointPath(threadId)` Resolves the path to a specific thread's checkpoint SQLite file. - `threadId` (string): The ID of the thread. #### `setApiKey(provider, key)` Writes an API key to `~/.openwork/.env` and sets it in `process.env` for the current session. - `provider` (string): The API provider (e.g., 'anthropic', 'openai'). - `key` (string): The API key string. #### `getApiKey(provider)` Retrieves an API key, checking `.env` first, then `process.env`. - `provider` (string): The API provider. #### `hasApiKey(provider)` Checks if an API key exists for the given provider. - `provider` (string): The API provider. #### `deleteApiKey(provider)` Removes an API key from `.env` and `process.env`. - `provider` (string): The API provider. #### `deleteThreadCheckpoint(threadId)` Deletes a thread's checkpoint file from disk. - `threadId` (string): The ID of the thread whose checkpoint to delete. ``` -------------------------------- ### LocalSandbox - Execute Shell Commands Source: https://context7.com/langchain-ai/openwork/llms.txt The LocalSandbox class allows for the execution of arbitrary shell commands within a specified workspace directory. It captures stdout and stderr, with options for byte capping and timeouts. ```APIDOC ## `LocalSandbox` — Local Shell Execution Backend Extends `FilesystemBackend` from `deepagents` with the `execute()` method for running arbitrary shell commands in the workspace directory. Stdout and stderr are collected together (stderr lines prefixed with `[stderr]`), subject to a configurable byte cap and timeout. ### Method `execute(command: string): Promise<{ output: string; exitCode: number | null; truncated: boolean }> ### Parameters - **command** (string) - The shell command to execute. ### Request Example ```typescript import { LocalSandbox } from "./src/main/agent/local-sandbox" const sandbox = new LocalSandbox({ rootDir: "/Users/alice/projects/my-app", virtualMode: false, // use absolute host paths timeout: 60_000, // 1 minute per command maxOutputBytes: 50_000 // truncate at ~50 KB }) const result = await sandbox.execute("npm test 2>&1") console.log("Exit code:", result.exitCode) console.log("Truncated:", result.truncated) console.log(result.output) // Timeout example const slow = await sandbox.execute("sleep 200") console.log(slow.output) // "Error: Command timed out after 60.0 seconds." console.log(slow.exitCode) // null ``` ### Response - **output** (string) - The combined stdout and stderr of the command. - **exitCode** (number | null) - The exit code of the command. Null if timed out. - **truncated** (boolean) - Indicates if the output was truncated due to `maxOutputBytes`. ### Inherited Methods - `ls()` - `read_file()` - `write_file()` - `edit_file()` - `glob()` - `grep()` ``` -------------------------------- ### Storage Utilities: API Key and Path Management Source: https://context7.com/langchain-ai/openwork/llms.txt Provides utilities for managing API keys in `~/.openwork/.env` and resolving application data paths. API keys are also set in `process.env` for the current session. ```typescript import { getApiKey, setApiKey, deleteApiKey, hasApiKey, getOpenworkDir, getDbPath, getThreadCheckpointPath, deleteThreadCheckpoint } from "./src/main/storage" // Resolve paths console.log(getOpenworkDir()) // "/Users/alice/.openwork" console.log(getDbPath()) // "/Users/alice/.openwork/openwork.sqlite" console.log(getThreadCheckpointPath("my-thread-id")) // "/Users/alice/.openwork/threads/my-thread-id.sqlite" // Write API keys to ~/.openwork/.env (also sets process.env for current session) setApiKey("anthropic", "sk-ant-api03-...") setApiKey("openai", "sk-proj-...") setApiKey("google", "AIzaSy...") // Read (checks .env file first, then process.env) console.log(getApiKey("anthropic")) // "sk-ant-api03-..." console.log(hasApiKey("openai")) // true // Remove key from .env and process.env deleteApiKey("google") console.log(hasApiKey("google")) // false // Delete a thread's checkpoint file from disk deleteThreadCheckpoint("my-thread-id") ``` -------------------------------- ### Run Project Checks Source: https://github.com/langchain-ai/openwork/blob/main/CONTRIBUTING.md Execute linting and type checking to ensure code quality and correctness before building or submitting changes. These commands should pass before creating a pull request. ```bash npm run lint ``` ```bash npm run typecheck ``` -------------------------------- ### createAgentRuntime Source: https://context7.com/langchain-ai/openwork/llms.txt Factory function to create a LangGraph deep-agent instance. It is configured with a per-thread SQLite checkpointer and a `LocalSandbox` backend, including a workspace-aware system prompt and human-in-the-loop for shell commands. ```APIDOC ## createAgentRuntime(options) ### Description Creates a LangGraph deep-agent instance wired to a per-thread SQLite checkpointer and a `LocalSandbox` backend. The agent is configured with a workspace-aware system prompt and human-in-the-loop interruption on all shell commands. ### Parameters #### Options - **threadId** (string) - REQUIRED - Unique UUID per conversation. - **workspacePath** (string) - REQUIRED - Absolute path on disk. - **modelId** (string) - Optional - Falls back to default model if not provided. ### Usage ```typescript import { createAgentRuntime } from "./src/main/agent/runtime" const agent = await createAgentRuntime({ threadId: "550e8400-e29b-41d4-a716-446655440000", workspacePath: "/Users/alice/projects/my-app", modelId: "claude-sonnet-4-5-20250929" }) const stream = await agent.stream( { messages: [new HumanMessage("List all TypeScript files and count lines of code")] }, { configurable: { thread_id: "550e8400-e29b-41d4-a716-446655440000" }, streamMode: ["messages", "values"], recursionLimit: 1000 } ) for await (const chunk of stream) { const [mode, data] = chunk as [string, unknown] console.log(mode, JSON.stringify(data).slice(0, 120)) } ``` ### Response Returns an agent instance with a `stream` method. ``` -------------------------------- ### Create Agent Runtime with LangChain Source: https://context7.com/langchain-ai/openwork/llms.txt Create a LangGraph deep-agent instance with a per-thread SQLite checkpointer and LocalSandbox backend. Configure the agent with a workspace-aware system prompt and enable human-in-the-loop interruption for shell commands. This function is called by the IPC agent handler for every invoke or resume operation. ```typescript import { createAgentRuntime } from "./src/main/agent/runtime" // Called by the IPC agent handler for every invoke/resume const agent = await createAgentRuntime({ threadId: "550e8400-e29b-41d4-a716-446655440000", // REQUIRED: unique UUID per conversation workspacePath: "/Users/alice/projects/my-app", // REQUIRED: absolute path on disk modelId: "claude-sonnet-4-5-20250929" // optional; falls back to default model }) // Stream a message to the agent with both token and values modes const stream = await agent.stream( { messages: [new HumanMessage("List all TypeScript files and count lines of code")] }, { configurable: { thread_id: "550e8400-e29b-41d4-a716-446655440000" }, streamMode: ["messages", "values"], recursionLimit: 1000 } ) for await (const chunk of stream) { const [mode, data] = chunk as [string, unknown] // mode === "messages" → real-time AI token / tool-call chunks // mode === "values" → full state snapshot (messages, todos, files, __interrupt__) console.log(mode, JSON.stringify(data).slice(0, 120)) } // messages {"lc":1,"type":"constructor","id":["langchain_core","messages","AIMessageChunk"],"kwargs":{"content":"I'll list...","id":"msg_01..."}} // values {"messages":[...],"todos":[],"files":{"...":{}}} ``` -------------------------------- ### useAppStore for Global Application State Management Source: https://context7.com/langchain-ai/openwork/llms.txt Manages global application state including threads, selected thread, models, UI panels, and sidebar visibility using Zustand. Use this hook to interact with and modify the application's core settings and data. It handles automatic reloading of providers and models upon API key changes. ```typescript import { useAppStore } from "./src/renderer/src/lib/store" function Sidebar() { const { threads, currentThreadId, createThread, selectThread, deleteThread, loadModels, setApiKey, toggleSidebar, sidebarCollapsed } = useAppStore() useEffect(() => { loadModels() }, []) const handleNew = async () => { const thread = await createThread({ workspacePath: "/Users/alice/projects/my-app", title: "New conversation" }) console.log("Created:", thread.thread_id) // Automatically becomes the currentThreadId } const handleDelete = async (id: string) => { await deleteThread(id) // If deleted thread was current, store auto-selects the next available thread } const handleApiKey = async () => { await setApiKey("anthropic", "sk-ant-api03-...") // Reloads providers and models automatically } return ( ) } ``` -------------------------------- ### Manage Workspaces with `workspace:*` IPC Source: https://context7.com/langchain-ai/openwork/llms.txt Handle workspace folder bindings, file listings, and file reading for specific threads. Supports opening a native folder picker, programmatically setting a workspace path, retrieving the current path, loading the file tree from disk, reading text and binary files, and subscribing to filesystem change events. ```typescript // Open native folder picker dialog and bind result to thread const selectedPath = await window.api.workspace.select("550e8400-e29b-41d4-a716-446655440000") console.log(selectedPath) // "/Users/alice/projects/my-app" or null if cancelled ``` ```typescript // Set workspace programmatically (also starts fs.watch) await window.api.workspace.set("550e8400-e29b-41d4-a716-446655440000", "/Users/alice/projects/my-app") ``` ```typescript // Get current workspace path for a thread const path = await window.api.workspace.get("550e8400-e29b-41d4-a716-446655440000") ``` ```typescript // Load full file tree (excludes hidden files and node_modules) const result = await window.api.workspace.loadFromDisk("550e8400-e29b-41d4-a716-446655440000") if (result.success) { console.log("Root:", result.workspacePath) console.log("Files:", result.files.length) // [{ path: "/src/index.ts", is_dir: false, size: 4096, modified_at: "2025-07-01T12:00:00Z" }, ...] } ``` ```typescript // Read a text file (path relative to workspace root, prefixed with "/") const file = await window.api.workspace.readFile( "550e8400-e29b-41d4-a716-446655440000", "/src/utils.ts" ) if (file.success) { console.log(file.content) // full file text console.log(file.size) // bytes console.log(file.modified_at) // ISO 8601 } ``` ```typescript // Read a binary file as base64 (images, PDFs, etc.) const img = await window.api.workspace.readBinaryFile( "550e8400-e29b-41d4-a716-446655440000", "/docs/diagram.png" ) if (img.success) { const dataURL = `data:image/png;base64,${img.content}` } ``` ```typescript // Subscribe to filesystem change events (debounced 500 ms) const unsubscribe = window.api.workspace.onFilesChanged(({ threadId, workspacePath }) => { console.log(`Files changed in thread ${threadId} at ${workspacePath}`) // Trigger workspace.loadFromDisk to refresh the file tree }) // Call unsubscribe() to remove the listener ``` -------------------------------- ### Manage Per-Thread SQLite Checkpointer Source: https://context7.com/langchain-ai/openwork/llms.txt Acquire a checkpointer instance for a specific thread ID, which lazily creates a SQLite database file. This allows iterating through historical checkpoints and flushing changes to disk. Ensure to close the checkpointer when done to free the database connection. ```typescript import { getCheckpointer, closeCheckpointer } from "./src/main/agent/runtime" const threadId = "my-thread-id" // Acquire checkpointer (creates DB file if not present) const cp = await getCheckpointer(threadId) // Iterate up to 50 historical checkpoints (newest first) const config = { configurable: { thread_id: threadId } } const history: unknown[] = [] for await (const tuple of cp.list(config, { limit: 50 })) { history.push(tuple) } console.log("Checkpoint count:", history.length) // Force flush in-memory changes to disk immediately await cp.flush() // Close and free the database connection await closeCheckpointer(threadId) ``` -------------------------------- ### Database Helpers - Metadata Store Source: https://context7.com/langchain-ai/openwork/llms.txt Provides functions for initializing and managing the main Openwork SQLite database, including CRUD operations for threads and their metadata. ```APIDOC ## Database Helpers ### Description Initialises the main `openwork.sqlite` database (threads, runs, assistants tables) and provides synchronous CRUD helpers used by the IPC thread handlers. ### Methods #### `initializeDatabase()` Initialises the main database. Should be called once at application startup. #### `createThread(threadId, metadata)` Creates a new thread row in the database. - `threadId` (string): The unique identifier for the thread. - `metadata` (object): An object containing thread metadata (e.g., `workspacePath`, `model`). #### `getAllThreads()` Retrieves all threads ordered by `updated_at` in descending order. #### `getThread(threadId)` Retrieves a specific thread by its ID. - `threadId` (string): The ID of the thread to retrieve. #### `updateThread(threadId, updates)` Updates the title and status of a thread. - `threadId` (string): The ID of the thread to update. - `updates` (object): An object containing fields to update (e.g., `title`, `status`). #### `deleteThread(threadId)` Deletes a thread and its associated runs from the database. - `threadId` (string): The ID of the thread to delete. #### `flush()` Forces an immediate disk write for database changes. ``` -------------------------------- ### SqlJsSaver: Checkpoint Saver Implementation Source: https://context7.com/langchain-ai/openwork/llms.txt Implements the BaseCheckpointSaver interface using sql.js for pure-JS SQLite. Writes are debounced and large files are backed up. Use for thread checkpointing in LangGraph. ```typescript import { SqlJsSaver } from "./src/main/checkpointer/sqljs-saver" const saver = new SqlJsSaver("/path/to/thread-checkpoint.sqlite") await saver.initialize() // Put a checkpoint (called by LangGraph internally) const newConfig = await saver.put( { configurable: { thread_id: "t1", checkpoint_ns: "" } }, myCheckpoint, { source: "loop", step: 5, writes: null, parents: {} } ) // Retrieve the latest checkpoint const tuple = await saver.getTuple({ configurable: { thread_id: "t1", checkpoint_ns: "" } }) console.log(tuple?.checkpoint.id) // checkpoint UUID console.log(tuple?.pendingWrites) // any pending channel writes // List historical checkpoints for await (const t of saver.list( { configurable: { thread_id: "t1" } }, { limit: 10 } )) { console.log(t.config.configurable?.checkpoint_id) } // Force flush, then close await saver.flush() await saver.close() ``` -------------------------------- ### Model & API Key IPC Handlers Source: https://context7.com/langchain-ai/openwork/llms.txt Manages the list of available models and provider API keys. API keys are persisted to `~/.openwork/.env`. ```APIDOC ## Model & API Key IPC Handlers — `models:*` ### Description Manages the list of available models and provider API keys. Keys are persisted to `~/.openwork/.env` and also loaded into `process.env`. ### Methods #### `list` Lists all available models. `available` is true only for providers with configured keys. **Returns** - An array of model objects, each containing `id`, `name`, `provider`, `model`, `description`, and `available`. ```typescript const models = await window.api.models.list() // [{ id, name, provider, model, description, available }, ...] // e.g. { id: "claude-sonnet-4-5-20250929", provider: "anthropic", available: true } ``` #### `listProviders` Lists providers with their `hasApiKey` status. **Returns** - An array of provider objects, each containing `id`, `name`, and `hasApiKey`. ```typescript const providers = await window.api.models.listProviders() // [{ id: "anthropic", name: "Anthropic", hasApiKey: true }, ...] ``` #### `getDefault` Gets the default model. **Returns** - The ID of the default model (string). ```typescript const defaultModel = await window.api.models.getDefault() ``` #### `setDefault` Sets the default model. **Parameters** - `modelId` (string) - Required - The ID of the model to set as default. ```typescript await window.api.models.setDefault("gpt-4.1") ``` #### `setApiKey` Stores an API key for a given provider. This key is written to `~/.openwork/.env`. **Parameters** - `providerId` (string) - Required - The ID of the provider (e.g., "anthropic", "openai"). - `apiKey` (string) - Required - The API key to store. ```typescript await window.api.models.setApiKey("anthropic", "sk-ant-api03-...") await window.api.models.setApiKey("openai", "sk-proj-...") await window.api.models.setApiKey("google", "AIzaSy...") ``` #### `getApiKey` Reads back a stored API key for a provider. **Parameters** - `providerId` (string) - Required - The ID of the provider. **Returns** - The stored API key (string), or undefined if not found. ```typescript const key = await window.api.models.getApiKey("anthropic") // "sk-ant-api03-..." ``` #### `deleteApiKey` Removes an API key for a given provider. **Parameters** - `providerId` (string) - Required - The ID of the provider. ```typescript await window.api.models.deleteApiKey("openai") ``` ``` -------------------------------- ### WorkspaceWatcher - Real-Time Filesystem Change Notifications Source: https://context7.com/langchain-ai/openwork/llms.txt Manages real-time filesystem change notifications for a workspace directory, sending updates to renderer windows via IPC. ```APIDOC ## WorkspaceWatcher ### Description Starts a recursive `fs.watch` on a workspace directory and notifies all renderer windows via `workspace:files-changed` IPC events. Changes are debounced by 500 ms; hidden files and `node_modules` are ignored. ### Methods #### `startWatching(threadId, workspacePath)` Starts watching a specific workspace directory for changes. If a watcher already exists for the `threadId`, it will be replaced. - `threadId` (string): The identifier for the thread associated with the workspace. - `workspacePath` (string): The absolute path to the workspace directory to watch. #### `stopWatching(threadId)` Stops watching the workspace directory for a specific thread. - `threadId` (string): The identifier of the thread whose watcher should be stopped. #### `stopAllWatching()` Stops all active workspace watchers. Typically called when the application is quitting. #### `isWatching(threadId)` Checks if a watcher is currently active for the given thread ID. - `threadId` (string): The identifier of the thread to check. ### Events #### `workspace:files-changed` Sent to renderer windows when files within a watched workspace change. The event payload includes `threadId` and `workspacePath`. ``` -------------------------------- ### WorkspaceWatcher: Real-Time Filesystem Notifications Source: https://context7.com/langchain-ai/openwork/llms.txt Monitors a workspace directory for changes using `fs.watch`, debounced by 500 ms, ignoring hidden files and `node_modules`. Notifies renderer windows via IPC events. ```typescript import { startWatching, stopWatching, stopAllWatching, isWatching } from "./src/main/services/workspace-watcher" const threadId = "550e8400-e29b-41d4-a716-446655440000" const workspacePath = "/Users/alice/projects/my-app" // Start watching (replaces any existing watcher for this thread) startWatching(threadId, workspacePath) console.log(isWatching(threadId)) // true // Renderer receives: ipcRenderer.on("workspace:files-changed", (_, { threadId, workspacePath }) => ...) // Typical response: reload workspace.loadFromDisk to refresh the file tree // Stop watching a specific thread stopWatching(threadId) console.log(isWatching(threadId)) // false // Stop all watchers (called on app quit) stopAllWatching() ``` -------------------------------- ### Workspace IPC Handlers Source: https://context7.com/langchain-ai/openwork/llms.txt Manages per-thread workspace binding, file listing, file reading, and real-time change notifications. ```APIDOC ## Workspace IPC Handlers — `workspace:*` ### Description Manages per-thread workspace folder binding, file listing, file reading, and real-time change notifications. ### Methods #### `select` Opens the native folder picker dialog and binds the selected path to a thread. **Parameters** - `threadId` (string) - Required - The ID of the thread to bind the workspace to. **Returns** - The selected workspace path (string), or `null` if the dialog was cancelled. ```typescript const selectedPath = await window.api.workspace.select("550e8400-e29b-41d4-a716-446655440000") console.log(selectedPath) ``` #### `set` Sets the workspace path programmatically for a thread and starts file system watching. **Parameters** - `threadId` (string) - Required - The ID of the thread. - `workspacePath` (string) - Required - The path to the workspace. ```typescript await window.api.workspace.set("550e8400-e29b-41d4-a716-446655440000", "/Users/alice/projects/my-app") ``` #### `get` Gets the current workspace path for a given thread. **Parameters** - `threadId` (string) - Required - The ID of the thread. **Returns** - The workspace path (string). ```typescript const path = await window.api.workspace.get("550e8400-e29b-41d4-a716-446655440000") ``` #### `loadFromDisk` Loads the full file tree for a thread's workspace, excluding hidden files and `node_modules`. **Parameters** - `threadId` (string) - Required - The ID of the thread. **Returns** - An object with `success` (boolean), `workspacePath` (string), and `files` (array of file objects). - Each file object contains `path`, `is_dir`, `size`, and `modified_at`. ```typescript const result = await window.api.workspace.loadFromDisk("550e8400-e29b-41d4-a716-446655440000") if (result.success) { console.log("Root:", result.workspacePath) console.log("Files:", result.files.length) // [{ path: "/src/index.ts", is_dir: false, size: 4096, modified_at: "2025-07-01T12:00:00Z" }, ...] } ``` #### `readFile` Reads the content of a text file within a thread's workspace. **Parameters** - `threadId` (string) - Required - The ID of the thread. - `filePath` (string) - Required - The path to the file, relative to the workspace root (prefixed with "/"). **Returns** - An object with `success` (boolean), `content` (string), `size` (number), and `modified_at` (string in ISO 8601 format). ```typescript const file = await window.api.workspace.readFile( "550e8400-e29b-41d4-a716-446655440000", "/src/utils.ts" ) if (file.success) { console.log(file.content) console.log(file.size) console.log(file.modified_at) } ``` #### `readBinaryFile` Reads the content of a binary file (e.g., images, PDFs) as a base64 encoded string. **Parameters** - `threadId` (string) - Required - The ID of the thread. - `filePath` (string) - Required - The path to the file, relative to the workspace root (prefixed with "/"). **Returns** - An object with `success` (boolean) and `content` (string, base64 encoded). ```typescript const img = await window.api.workspace.readBinaryFile( "550e8400-e29b-41d4-a716-446655440000", "/docs/diagram.png" ) if (img.success) { const dataURL = `data:image/png;base64,${img.content}` } ``` #### `onFilesChanged` Subscribes to filesystem change events within a workspace (debounced by 500 ms). **Callback Parameters** - `event` (object) - `threadId` (string) - The ID of the thread where files changed. - `workspacePath` (string) - The path of the workspace where files changed. **Returns** - An `unsubscribe` function to remove the listener. ```typescript const unsubscribe = window.api.workspace.onFilesChanged(({ threadId, workspacePath }) => { console.log(`Files changed in thread ${threadId} at ${workspacePath}`) // Trigger workspace.loadFromDisk to refresh the file tree }) // Call unsubscribe() to remove the listener ``` ``` -------------------------------- ### SqlJsSaver - LangGraph Checkpoint Saver Source: https://context7.com/langchain-ai/openwork/llms.txt Implements the BaseCheckpointSaver interface using sql.js for checkpoint persistence. It handles saving and retrieving thread checkpoints, with features like debouncing, automatic backups, and file size management. ```APIDOC ## SqlJsSaver ### Description A `BaseCheckpointSaver` implementation using sql.js (pure-JS SQLite). Each thread gets its own `.sqlite` file. Writes are debounced by 100 ms; files larger than 100 MB are automatically backed up and replaced with a fresh database. ### Methods #### `initialize()` Initializes the saver. #### `put(config, checkpoint, metadata)` Saves a new checkpoint. - `config` (object): Configuration for the checkpoint. - `checkpoint` (any): The checkpoint data. - `metadata` (object): Metadata about the checkpoint operation. #### `getTuple(config)` Retrieves the latest checkpoint tuple for a given configuration. - `config` (object): Configuration to identify the checkpoint. #### `list(config, options)` Lists historical checkpoints. - `config` (object): Configuration to filter checkpoints. - `options` (object): Options for listing, e.g., `{ limit: number }`. #### `flush()` Forces an immediate write of any pending changes. #### `close()` Closes the saver and releases resources. ``` -------------------------------- ### getCheckpointer / closeCheckpointer Source: https://context7.com/langchain-ai/openwork/llms.txt Manages per-thread SQLite checkpointers. `getCheckpointer` returns or creates a `SqlJsSaver` instance, while `closeCheckpointer` frees the database connection. ```APIDOC ## getCheckpointer(threadId) / closeCheckpointer(threadId) ### Description Returns (or lazily creates) a `SqlJsSaver` instance backed by `~/.openwork/threads/.sqlite`. Used internally by `createAgentRuntime` but also exposed for direct checkpoint history access. ### Parameters - **threadId** (string) - REQUIRED - The unique identifier for the conversation thread. ### Usage ```typescript import { getCheckpointer, closeCheckpointer } from "./src/main/agent/runtime" const threadId = "my-thread-id" const cp = await getCheckpointer(threadId) const config = { configurable: { thread_id: threadId } } const history: unknown[] = [] for await (const tuple of cp.list(config, { limit: 50 })) { history.push(tuple) } console.log("Checkpoint count:", history.length) await cp.flush() await closeCheckpointer(threadId) ``` ### Response `getCheckpointer` returns a checkpointer instance. `closeCheckpointer` returns void. ``` -------------------------------- ### useCurrentThread Hook for Thread State Management Source: https://context7.com/langchain-ai/openwork/llms.txt This hook provides access to a thread's state (messages, todos, workspace files, etc.) and actions (append message, set pending approval). It automatically initializes the thread on first render. Use for managing UI elements tied to a specific conversation thread. ```typescript import { useCurrentThread } from "./src/renderer/src/lib/thread-context" function ChatPanel({ threadId }: { threadId: string }) { const thread = useCurrentThread(threadId) // State fields: // thread.messages — Message[] // thread.todos — Todo[] // thread.workspaceFiles — FileInfo[] // thread.workspacePath — string | null // thread.subagents — Subagent[] // thread.pendingApproval — HITLRequest | null // thread.tokenUsage — TokenUsage | null // thread.currentModel — string // thread.openFiles — OpenFile[] // thread.error — string | null const handleSend = (text: string) => { thread.appendMessage({ id: crypto.randomUUID(), role: "user", content: text, created_at: new Date() }) // Actual sending happens via useStream / ElectronIPCTransport } const handleApprove = () => { if (thread.pendingApproval) { window.api.agent.interrupt(threadId, { type: "approve", tool_call_id: thread.pendingApproval.tool_call.id }) thread.setPendingApproval(null) } } return (
{thread.messages.map((m) =>
{m.content as string}
)} {thread.pendingApproval && (

Approve: {thread.pendingApproval.tool_call.name}?

)}
) } ``` -------------------------------- ### Invoke Agent and Stream Responses via IPC Source: https://context7.com/langchain-ai/openwork/llms.txt On the renderer side, use `window.api.agent.invoke` to send a message to an agent for a specific thread. A callback function receives `IPCStreamEvent` objects for streaming, completion, or errors. The invocation can be cancelled using `window.api.agent.cancel`. ```typescript // Renderer-side (via preload api.agent.invoke) const cleanup = window.api.agent.invoke( "550e8400-e29b-41d4-a716-446655440000", // threadId "Refactor src/utils.ts to use async/await", (event) => { switch (event.type) { case "stream": // Raw LangGraph chunk: { mode: "messages" | "values", data: unknown } console.log("stream chunk", event.mode) break case "done": console.log("Agent finished") break case "error": console.error("Agent error:", event.error) break } }, "gpt-4.1" // optional modelId override ) // Cancel at any time await window.api.agent.cancel("550e8400-e29b-41d4-a716-446655440000") cleanup() // removes the IPC listener ``` -------------------------------- ### Handle HITL Interrupt Decisions via IPC Source: https://context7.com/langchain-ai/openwork/llms.txt Use `window.api.agent.interrupt` to send a HITL decision ('approve' or 'reject') for a specific thread. The 'approve' decision resumes execution from a checkpoint, while 'reject' terminates the process immediately. A callback handles completion events. ```typescript import type { HITLDecision } from "./src/main/types" // Approve (resumes execution) const approveDecision: HITLDecision = { type: "approve", tool_call_id: "toolu_01XYZ" } const cleanup = window.api.agent.interrupt( "550e8400-e29b-41d4-a716-446655440000", approveDecision, (event) => { if (event.type === "done") console.log("Approved and continued") } ) // Reject (sends done immediately without executing the command) window.api.agent.interrupt( "550e8400-e29b-41d4-a716-446655440000", { type: "reject", tool_call_id: "toolu_01XYZ" } ) ``` -------------------------------- ### Thread CRUD IPC Handlers Source: https://context7.com/langchain-ai/openwork/llms.txt Provides full CRUD operations for conversation threads, backed by an in-memory sql.js SQLite database. ```APIDOC ## Thread CRUD IPC Handlers — `threads:*` ### Description Full CRUD for conversation threads, backed by an in-memory sql.js SQLite database persisted to `~/.openwork/openwork.sqlite`. ### Methods #### `create` Creates a new thread with an auto-generated UUID. **Parameters** - `workspacePath` (string) - Required - The path to the workspace. - `model` (string) - Required - The model to use for the thread. **Returns** - `thread_id` (string) - The ID of the created thread. - `status` (string) - The status of the thread. - `title` (string) - The title of the thread. ```typescript const thread = await window.api.threads.create({ workspacePath: "/Users/alice/projects/my-app", model: "claude-sonnet-4-5-20250929" }) console.log(thread.thread_id) console.log(thread.status) console.log(thread.title) ``` #### `list` Lists all threads, sorted by `updated_at` in descending order. **Returns** - An array of thread objects, each containing `thread_id`, `created_at`, `updated_at`, `metadata`, `status`, and `title`. ```typescript const threads = await window.api.threads.list() // [{ thread_id, created_at, updated_at, metadata, status, title }, ...] ``` #### `get` Retrieves a single thread by its ID. **Parameters** - `threadId` (string) - Required - The ID of the thread to retrieve. **Returns** - The thread object. ```typescript const t = await window.api.threads.get("550e8400-e29b-41d4-a716-446655440000") ``` #### `update` Updates the title and status of a thread. **Parameters** - `threadId` (string) - Required - The ID of the thread to update. - `data` (object) - Required - An object containing the fields to update. - `title` (string) - Optional - The new title for the thread. - `status` (string) - Optional - The new status for the thread. **Returns** - The updated thread object. ```typescript const updated = await window.api.threads.update("550e8400-e29b-41d4-a716-446655440000", { title: "Refactor utils.ts", status: "idle" }) ``` #### `delete` Deletes a thread and its associated checkpoint `.sqlite` file. **Parameters** - `threadId` (string) - Required - The ID of the thread to delete. ```typescript await window.api.threads.delete("550e8400-e29b-41d4-a716-446655440000") ``` #### `getHistory` Retrieves the checkpoint history for a thread (up to 50 entries). **Parameters** - `threadId` (string) - Required - The ID of the thread. **Returns** - An array of checkpoint entries. ```typescript const history = await window.api.threads.getHistory("550e8400-e29b-41d4-a716-446655440000") console.log("Checkpoints:", history.length) ``` #### `generateTitle` Generates a title from the first user message. **Parameters** - `firstUserMessage` (string) - Required - The content of the first user message. **Returns** - The generated title string. ```typescript const title = await window.api.threads.generateTitle( "Refactor the authentication module to use JWT tokens instead of sessions" ) console.log(title) ``` ```