### 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)
```
```