The Next.js web app was only built inside the gateway process on first
boot. When the daemon was freshly installed (e.g. `onboard
--install-daemon`), the LaunchAgent would start and block on `next
build`, causing a noticeably slow first startup.
Add `ensureWebAppBuilt()` to `src/gateway/server-web-app.ts` — a
standalone pre-build function that checks for `.next/BUILD_ID` and runs
dep install + `next build` if missing. Skips silently when the web app
is disabled, already built, in dev mode, or inapplicable (global npm
install without `apps/web`).
Call both `ensureWebAppBuilt()` and `ensureControlUiAssetsBuilt()` before
the daemon is installed in every relevant path:
- Interactive onboarding (`onboarding.finalize.ts`) — moved the existing
Control UI build from after the daemon install to before it, and added
the web app build alongside it.
- Non-interactive onboarding (`daemon-install.ts`) — added both pre-build
calls before `service.install()`.
- Standalone `openclaw gateway install` CLI (`daemon-cli/install.ts`) —
added both pre-build calls before `service.install()`.
- Configure wizard (`configure.wizard.ts`) — added the web app build
alongside the existing Control UI build.
Updated test mocks for `ensureWebAppBuilt` in onboarding, configure
wizard, and daemon CLI coverage tests.
Bumped version to 2026.2.6-3.7 and published to npm.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add gateway.webApp config (enabled, port, dev) as the unified toggle
for both the Next.js web UI and the built-in control UI
- Spawn Next.js app alongside the gateway; stop it on shutdown
- Auto-enable webApp in config for new and existing installs
- Pre-build Next.js in deploy.sh and ship .next/ in the npm package
so installed users get instant startup (no build step)
- Gateway skips build when pre-built .next/ exists; builds on first
run for dev/git-checkout users
- Onboarding "Open the Web UI" now opens the Ironclaw web app
- Fix pre-existing Next.js build errors (ES2023 lib, Tiptap v3 types,
Suspense boundary, ReportConfig type alignment)
- Rename deploy target from openclaw-ai-sdk to ironclaw
Co-authored-by: Cursor <cursoragent@cursor.com>
Rebrand from OpenClaw to Ironclaw across 16 files:
Web app (apps/web):
- layout.tsx: update metadata title to "Ironclaw" and description to
"AI CRM with an agent that connects to your apps and does the work for you"
- page.tsx: change landing page heading from "OpenClaw Dench" to "Ironclaw"
- agent-runner.ts: rename stderr log prefix to [ironclaw stderr]
- package.json: rename package from "openclaw-web" to "ironclaw-web"
Package identity (root):
- package.json: rename package from "openclaw-ai-sdk" to "ironclaw",
update description to reflect CRM/workspace focus, change bin entry
from "openclaw-ai-sdk" to "ironclaw"
- openclaw.mjs: update error message to reference "ironclaw"
- src/version.ts: change CORE_PACKAGE_NAME to "ironclaw"
CLI and TUI:
- command-format.ts: extend CLI prefix regex to accept both "ironclaw"
and "openclaw" for backward compatibility
- register.agent.ts: update example identity name to "Ironclaw" with
🔩 emoji (replacing 🦞)
- tui.ts: rename TUI header from "openclaw tui" to "ironclaw tui"
Onboarding and configuration wizards:
- configure.wizard.ts: rename engine selection prompts and wizard intro
headers to "Ironclaw"
- onboarding.ts: rename onboarding intro and security warning text
- onboarding.finalize.ts: rename all dashboard/control-UI messages and
Brave Search setup instructions to reference "Ironclaw"
Security audit:
- audit.ts: rename state-dir permission warning details to "Ironclaw"
- audit-extra.ts: rename plugin remediation text to "Ironclaw"
Telegram:
- bot-message-context.ts: rename access-denied message to "Ironclaw"
── Tiptap Markdown Editor ──
- Add full Tiptap-based WYSIWYG markdown editor (markdown-editor.tsx, 709 LOC)
with bubble menu, auto-save (debounced), image drag-and-drop/paste upload,
table editing, task list checkboxes, and frontmatter preservation on save.
- Add slash command system (slash-command.tsx, 607 LOC) with "/" trigger for
block insertion (headings, lists, tables, code blocks, images, reports) and
"@" trigger for file/document mention with fuzzy search across the workspace
tree.
- Add ReportBlockNode (report-block-node.tsx) — custom Tiptap node that renders
embedded report-json blocks as interactive ReportCard widgets inline in the
editor, with expand/collapse and edit-JSON support.
- Add workspace asset serving API (api/workspace/assets/[...path]/route.ts) to
serve images from the workspace with proper MIME types.
- Add workspace file upload orkspace/upload/route.ts) for multipart
image uploads (10 MB limit, image types only), saving to assets/ directory.
- Add ~500 lines of Tiptap editor CSS to globals.css (editor layout, task lists,
images, tables, slash command dropdown, bubble menu toolbar, code blocks, etc.).
- Add 14 @tiptap/* dependencies to apps/web/package.json (react, starter-kit,
markdown, image, link, table, task-list, suggestion, placeholder, etc.).
── Document View: Edit/Read Mode Toggle ──
- document-view.tsx: Add edit/read mode toggle; defaults to edit mode when a
filePath is available. Lazy-loads MarkdownEditor to keep initial bundle light.
- workspace/page.tsx: Pass activePath, tree, onSave, onNavigate, and
onRefreshTree through to DocumentView for full editor integration with
workspace navigation and tree refresh after saves.
── Subagent Session Isolation ──
- agent-runner.ts: Add RunAgentOptions with optional sessionId; when set, spawns
the agent with --session-key agent:main:subagent:<id> ant so
file-scoped sidebar chats run in isolated sessions independent of the main
agent.
- route.ts (chat API): Accept sessionId from request body and forward it to
runAgent. Resolve workspace file path prefixes (resolveAgentWorkspacePrefix)
so tree-relative paths become agent-cwd-relative.
- chat-panel.tsx: Create per-instance DefaultChatTransport that injects sessionId
via body function and a ref (avoids stale closures). On file change, auto-load
the most recent session and its messages. Refresh session tab list after
streaming ends. Stop ongoing stream when switching sessions.
- register.agent.ts: Add --session-key <key> and --lane <lane> CLI flags.
- agent-via-gateway.ts: Wire sessionKey into session resolution and validation
for both interactive and --stream-json code paths.
- workspace.ts: Add resolveAgentWorkspacePrefix() to map workspace-root-relative
paths to repo-root-relative paths for the agent process.
── Error Surfacing ──
- agent-runner.ts: Add onAgentError callback extraction helpers
(parseAgentErrorMessage, parseErrorBody, parseErrorFromStderr) to surface
API-level errors (402 payment, rate limits, etc.) to the UI. Captures stderr
for fallback error detection on non-zero exit.
- route.ts: Wire onAgentError into the SSE stream as [error]-prefixed text
parts. Improve onError and onClose handlers with clearer error messages and
exit code reporting.
- chat-message.tsx: Detect [error]-prefixed text segments and render them as
styled error banners with alert icon instead of plain text.
- chat-panel.tsx: Restyle the transport-level error bar with themed colors and
an alert icon consistent with in-message error styling.
* fix(cli): exit with non-zero code when configure/agents-add wizards are cancelled
Follow-up to the onboard cancel fix. The configure wizard and
agents add wizard also caught WizardCancelledError and exited with
code 0, which signals success to callers. Change to exit(1) for
consistency — user cancellation is not a successful completion.
This ensures scripts that chain these commands with set -e will
correctly stop when the user cancels.
* fix(cli): make wizard cancellations exit non-zero (#14156) (thanks @0xRaini)
---------
Co-authored-by: Rain <rain@Rains-MBA-M4.local>
Co-authored-by: Sebastian <19554889+sebslight@users.noreply.github.com>
* feat: add LiteLLM provider types, env var, credentials, and auth choice
Add litellm-api-key auth choice, LITELLM_API_KEY env var mapping,
setLitellmApiKey() credential storage, and LITELLM_DEFAULT_MODEL_REF.
* feat: add LiteLLM onboarding handler and provider config
Add applyLitellmProviderConfig which properly registers
models.providers.litellm with baseUrl, api type, and model definitions.
This fixes the critical bug from PR #6488 where the provider entry was
never created, causing model resolution to fail at runtime.
* docs: add LiteLLM provider documentation
Add setup guide covering onboarding, manual config, virtual keys,
model routing, and usage tracking. Link from provider index.
* docs: add LiteLLM to sidebar navigation in docs.json
Add providers/litellm to both English and Chinese provider page lists
so the docs page appears in the sidebar navigation.
* test: add LiteLLM non-interactive onboarding test
Wire up litellmApiKey flag inference and auth-choice handler for the
non-interactive onboarding path, and add an integration test covering
profile, model default, and credential storage.
* fix: register --litellm-api-key CLI flag and add preferred provider mapping
Wire up the missing Commander CLI option, action handler mapping, and
help text for --litellm-api-key. Add litellm-api-key to the preferred
provider map for consistency with other providers.
* fix: remove zh-CN sidebar entry for litellm (no localized page yet)
* style: format buildLitellmModelDefinition return type
* fix(onboarding): harden LiteLLM provider setup (#12823)
* refactor(onboarding): keep auth-choice provider dispatcher under size limit
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Problem:
When users execute `/think off`, they still receive `reasoning_content`
from models configured with `reasoning: true` (e.g., GLM-4.7, GLM-4.6,
Kimi K2.5, MiniMax-M2.1).
Expected: `/think off` should completely disable reasoning content.
Actual: Reasoning content is still returned.
Root Cause:
The directive handlers delete `sessionEntry.thinkingLevel` when user
executes `/think off`. This causes the thinking level to become undefined,
and the system falls back to `resolveThinkingDefault()`, which checks the
model catalog and returns "low" for reasoning-capable models, ignoring the
user's explicit intent.
Why We Must Persist "off" (Design Rationale):
1. **Model-dependent defaults**: Unlike other directives where "off" means
use a global default, `thinkingLevel` has model-dependent defaults:
- Reasoning-capable models (GLM-4.7, etc.) → default "low"
- Other models → default "off"
2. **Existing pattern**: The codebase already follows this pattern for
`elevatedLevel`, which persists "off" explicitly to override defaults
that may be "on". The comment explains:
"Persist 'off' explicitly so `/elevated off` actually overrides defaults."
3. **User intent**: When a user explicitly executes `/think off`, they want
to disable thinking regardless of the model's capabilities. Deleting the
field breaks this intent by falling back to the model's default.
Solution:
Persist "off" value instead of deleting the field in all internal directive handlers:
- `src/auto-reply/reply/directive-handling.impl.ts`: Directive-only messages
- `src/auto-reply/reply/directive-handling.persist.ts`: Inline directives
- `src/commands/agent.ts`: CLI command-line flags
Gateway API Backward Compatibility:
The original implementation incorrectly mapped `null` to "off" in
`sessions-patch.ts` for consistency with internal handlers. This was a
breaking change because:
- Previously, `null` cleared the override (deleted the field)
- API clients lost the ability to "clear to default" via `null`
- This contradicts standard JSON semantics where `null` means "no value"
Restored original null semantics in `src/gateway/sessions-patch.ts`:
- `null` → delete field, fall back to model default (clear override)
- `"off"` → persist explicit override
- Other values → normalize and persist
This ensures backward compatibility for API clients while fixing the `/think off`
issue in internal handlers.
Signed-off-by: Liu Yuan <namei.unix@gmail.com>
* refactor: consolidate duplicate utility functions
- Add escapeRegExp to src/utils.ts and remove 10 local duplicates
- Rename bash-tools clampNumber to clampWithDefault (different signature)
- Centralize formatError calls to use formatErrorMessage from infra/errors.ts
- Re-export formatErrorMessage from cli/cli-utils.ts to preserve API
* refactor: consolidate remaining escapeRegExp duplicates
* refactor: consolidate sleep, stripAnsi, and clamp duplicates
* initial commit
* feat: implement deriveSessionTotalTokens function and update usage tests
* Added deriveSessionTotalTokens function to calculate total tokens based on usage and context tokens.
* Updated usage tests to include cases for derived session total tokens.
* Refactored session usage calculations in multiple files to utilize the new function for improved accuracy.
* fix: restore overflow truncation fallback + changelog/test hardening (#11551) (thanks @tyler6204)
* fix: use STATE_DIR instead of hardcoded ~/.openclaw for identity and canvas
device-identity.ts and canvas-host/server.ts used hardcoded
path.join(os.homedir(), '.openclaw', ...) ignoring OPENCLAW_STATE_DIR
env var and the resolveStateDir() logic from config/paths.ts.
This caused ~/.openclaw/identity and ~/.openclaw/canvas directories
to be created even when state dir was overridden or resided elsewhere.
* fix: format and remove duplicate imports
* fix: scope state-dir patch + add regression tests (#4824) (thanks @kossoy)
* fix: align state-dir fallbacks in hooks and agent paths (#4824) (thanks @kossoy)
---------
Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
* Tests: harden flake hotspots and consolidate provider-auth suites
* Tests: restore env vars by deleting missing snapshot values
* Tests: use real newline in memory summary filter case
* Tests(memory): use fake timers for qmd timeout coverage
* Changelog: add tests hardening entry for #11598
* fix(gateway): use LAN IP for WebSocket/probe URLs when bind=lan (#11329)
When gateway.bind=lan, the HTTP server correctly binds to 0.0.0.0
(all interfaces), but WebSocket connection URLs, probe targets, and
Control UI links were hardcoded to 127.0.0.1. This caused CLI commands
and status probes to show localhost-only URLs even in LAN mode, and
made onboarding display misleading connection info.
- Add pickPrimaryLanIPv4() to gateway/net.ts to detect the machine's
primary LAN IPv4 address (prefers en0/eth0, falls back to any
external interface)
- Update pickProbeHostForBind() to use LAN IP when bind=lan
- Update buildGatewayConnectionDetails() to use LAN IP and report
"local lan <ip>" as the URL source
- Update resolveControlUiLinks() to return LAN-accessible URLs
- Update probe note in status.gather.ts to reflect new behavior
- Add tests for pickPrimaryLanIPv4 and bind=lan URL resolution
Closes#11329
Co-authored-by: Cursor <cursoragent@cursor.com>
* test: move vi.restoreAllMocks to afterEach in pickPrimaryLanIPv4
Per review feedback: avoid calling vi.restoreAllMocks() inside
individual tests as it restores all spies globally and can cause
ordering issues. Use afterEach in the describe block instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Changelog: note LAN bind URLs fix (#11448) (thanks @AnonO6)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* fix(docs): correct OpenCode Zen description in code comment
OpenCode Zen is a pay-as-you-go token-based API, not a $200/month
subscription. The subscription tiers ($20/$100/$200) are OpenCode Black,
a separate product.
This fixes the misleading comment that conflated Zen with Black.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: align OpenCode Zen billing copy (#9998) (thanks @therealZpoint-bot)
---------
Co-authored-by: Claude <claude@archibald.local>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
* feat: add Claude Opus 4.6 to built-in model catalog
- Update default model from claude-opus-4-5 to claude-opus-4-6
- Add opus-4.6 model ID normalization
- Add claude-opus-4-6 to live model filter prefixes
- Update image tool to prefer claude-opus-4-6 for vision
- Add CLI backend alias for opus-4.6
- Update onboard auth default selections to include opus-4.6
- Update model picker placeholder
Closes#9811
* test: update tests for claude-opus-4-6 default
- Fix model-alias-defaults test to use claude-opus-4-6
- Fix image-tool test to expect claude-opus-4-6 in fallbacks
* feat: support claude-opus-4-6
* docs: update changelog for opus 4.6 (#9853) (thanks @TinyTb)
* chore: bump pi to 0.52.0
---------
Co-authored-by: Slurpy <slurpy@openclaw.ai>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
When spawning a subagent, the requesterOrigin's threadId, to, and
accountId were not forwarded to the callGateway({method:'agent'}) params.
This meant the subagent's runContext had no currentThreadTs or
currentChannelId, so resolveTelegramAutoThreadId could not auto-inject
the forum topic thread ID when the subagent used the message tool.
Changes:
- sessions-spawn-tool: pass to, accountId, threadId from requesterOrigin
- run-context: populate currentChannelId from opts.to as fallback
Fixes subagent messages landing in General Topic instead of the correct
Telegram DM topic thread.