Conversation
Drop the @deprecated isE2ePlaintextExemptPath wrapper — zero callers in src/ or test/; its replacement isE2ePlaintextTunnelExempt is the only path used. Pure dead-code removal, no behaviour change. Bump 0.2.1 → 0.2.2. typecheck clean; 366 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
highlightCodeBlocks() called hljs.highlightElement() unconditionally, so fences without a language tag fell back to highlight.js auto-detection. That mis-classified prose as Ruby — an apostrophe (e.g. "пам'ятає") opens a string literal and the github-dark theme dims the rest of the block, making plain text hard to read. Now highlight only when the fence declares a language hljs recognizes; bare/unknown/text fences render as plain monospace (no auto-detect). Explicit-language blocks still highlight normally, so Cyrillic in code comments is unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Config-driven modes in services/chatModes.ts (ask, execute + disabled research/image/safe_run placeholders); single source of truth, extensible. - Persist mode per message (messages.mode) and per queued row (queued_messages.mode); idempotent ensureColumn migrations, plain TEXT. - Thread mode frontend -> WS 'send' -> chatRunner -> queue flush; missing/ unknown -> 'execute' so legacy chats and older clients keep working. - Ask prepends a lightweight 'answer, don't execute' directive to the gateway message; Execute is byte-for-byte unchanged. Seams left for routing Ask to a no-tools OpenClaw profile or OpenRouter later (see chatModes.ts + README). - Minimal composer pill + dropdown selector, choice persisted in localStorage.
Ask no longer relies only on a prompt instruction. When a tools-restricted agent exists on the gateway (ICLAW_ASK_AGENT, default 'ask'), each Ask turn runs on a throwaway session bound to it, so OpenClaw enforces its tool policy and the model physically cannot use shell/file/browser tools. The fresh session is seeded with a compact snapshot of the recent thread so Ask keeps context; the reply lands in the same chat thread. - config: loadAskAgentId() (ICLAW_ASK_AGENT; empty/off disables hard Ask) - openclawWs: cached agentExists() via agents.list - chatRunner: resolveTurnTarget() routes ask->ephemeral restricted session (seeded context) or falls back to the soft prompt-only preamble; Execute unchanged. activeRunSessionKeys lets abort target the ephemeral ask session. - docs: README hard-vs-soft Ask + config example + v1 limits; .env.example. v1 limitation: Execute's native session memory doesn't include Ask turns (Ask->Execute bridge via chat.inject is a planned follow-up).
After a hard-Ask turn (which runs on a throwaway tools-restricted session), the
chat's main Execute session has no native memory of it. Append a compact [Ask]
Q&A note to the main session via chat.inject — no model run, zero cost — so a
later Execute turn sees the exchange. Best-effort; stored content keeps secret
placeholders so nothing sensitive is injected.
- openclawWs.injectMessage({sessionKey,message,label})
- chatRunner: buildAskBridgeNote + inject after successful hard-Ask turn
- README: context now bridged both ways
Previously Ask silently fell back to a prompt-only 'please don't use tools' turn on the full-tools session when the ask agent wasn't configured — so the model could (and did) still create files. That fail-open is removed. Now: a lightweight (Ask) turn runs only on the tools-restricted ask agent. If that agent isn't configured/present (or ICLAW_ASK_AGENT is empty), chatRunner refuses the turn with a system note instead of running with tools. No prompt-only path remains. - chatRunner: fail-closed gate before turn start; resolveTurnTarget no longer has a soft branch. - chatModes: drop ASK_PREAMBLE + applyModeToGatewayMessage (dead); update notes. - README/.env.example: document fail-closed behavior (no prompt fallback).
- New openRouter.ts: streaming/non-streaming chat completions + audio
transcription (transcribeAudio) via /chat/completions. No extra deps —
Node 25 global fetch.
- New kv.ts: tiny iclaw_kv accessor (key/value over existing DB table)
so config can read settings without pulling store.ts (cycle-free).
- config.ts: loadOpenRouterConfig reads API key from KV (set in Settings,
not .env); adds setOpenRouterApiKey / clearOpenRouterApiKey / maskOpenRouterApiKey.
- chatTitle.ts: prefer cheap OpenRouter call for titles; fall back to
OpenClaw session when key absent or call fails.
- chatModes.ts: Ask gated on openRouterEnabled() — hidden from composer
without key, posted 'ask' coerced to 'execute'.
- chatRunner.ts: Ask branch replaced — direct OpenRouter stream (gemini-
2.0-flash default), AbortController for Stop, chat.inject bridge into
main OpenClaw session preserved. Removed ask-agent machinery (ICLAW_ASK_AGENT,
resolveTurnTarget, ASK_PREAMBLE). No prompt-only fallback.
- index.ts route: POST /api/stt endpoint (express.raw, 25 MB limit) →
transcribeAudio → {text}; sttEnabled local for views.
- chats.ts: pass sttEnabled to chat view.
- .env.example / README: key is now set in Settings UI, not env.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add mic button next to the send button (rendered only when OPENROUTER_API_KEY is set via Settings). - composer.ejs: mic button with idle/recording/busy SVG states; gated on sttEnabled EJS local. - iclaw.js: mic JS — MediaRecorder recording, pickMicMime(), transcribeBlob() POST to /api/stt, insertTranscript() into composer textarea on success. Stop mic on second click; spinner during transcription; alert on error. - style.css: .composer-mic — mirrors .composer-attach tokens (32px circle, --muted, color-mix hover). Recording state: --danger color + 12% tint bg + pulse animation. Busy state: spinner replaces mic icon. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Settings is now sectioned by URL (/settings/voice-ask, /settings/remote-access) with pill-tab navigation. /settings redirects to the first tab. Voice & Ask page: - Minimal CTA: one-liner description + monospace key input + Connect button + openrouter.ai/keys link. No perks list (removed as noise). - Connected state: green dot, masked key (sk-or-v••••••••xxxx), Disconnect button, live spend readout ($0.42 spent / $9.58 remaining + thin progress bar loaded async from /api/openrouter/usage). - Endpoints: POST /api/openrouter/key (save+validate), DELETE /api/openrouter/key (clear), GET /api/openrouter/usage (fetchUsage). - Key validated for sk-or- prefix; 400 on bad format. - Page reloads on connect/disconnect so composer picks up new key state. settings.ts: sidebarLocals() helper; per-tab routes with only the locals each tab needs. RA script/modal/dialog gated in EJS so they don't load on voice-ask tab. sidebar.ejs: settings gear links directly to /settings/voice-ask. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extend ChatMode type to include 'work' - Add Work Mode entry in CHAT_MODES catalog (requires OpenRouter key) - Wire chatRunner to route 'work' turns to iclaw-runtime service - Add workRuntime.ts HTTP client (port 7430, SSE events) - Add packages/iclaw-runtime/ — NanoClaw fork stripped to essentials: - No OneCLI dependency (removed) - No Telegram/WhatsApp/Slack/Discord channels - OpenRouter via ANTHROPIC_BASE_URL env passthrough - New iclaw-http channel: HTTP/SSE API for iClaw browser UI - Approval flow simplified for browser-first approval - Mount security module intact (path traversal prevention) - Convert repo to npm workspaces monorepo Attribution: packages/iclaw-runtime/ forked from NanoClaw (MIT) https://github.com/nanocoai/nanoclaw Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spawns packages/iclaw-runtime/src/index.ts via tsx when iClaw starts, shuts it down on SIGINT/SIGTERM. Auto-restarts on crash (max 5 times per minute). No-op when iclaw-runtime is not installed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Voice & Ask section now shows a bullet list of what the key unlocks: voice messages, Ask mode, Work mode (with description), auto-named chats. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gemini-2.0-flash not available on this account; gemini-2.5-flash is. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gemini 2.5 Flash has thinking enabled by default on OpenRouter which causes very long waits. Explicitly disable it for Ask mode and titles. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MODE_STORAGE_KEY is now iclaw:composer-mode:<chatId> so switching between chats doesn't reset each other's mode selection. Draft chats migrate their mode to the per-chat key on id adoption. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- iclaw-session: seed agent group with model + write "user" destination in inbound.db so agent can use <message to="user"> - iclaw-http: fix session wiring (sessionId as platformId), isMention:true - container-runner: pass ANTHROPIC_API_KEY for OpenRouter auth - openrouter-proxy: strip unsupported beta headers (claude-code-20250219, advisor-tool-2026-03-01) that cause 400 on OpenRouter Anthropic endpoint - session-manager: write "user" destination row on writeSessionRouting - config: default model claude-3-5-haiku-20241022 (available on OpenRouter) Work Mode full flow now works: iClaw UI → chatRunner → workRuntime HTTP → iclaw-runtime → Docker container → OpenRouter (via proxy) → SSE stream back to UI Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ing loop Remove Claude Code CLI dependency entirely. New agent: - src/agent/loop.ts — OpenRouter tool-calling cycle (any model) - src/agent/tools.ts — list_files, read_file, search_files, write_file, run_command - src/agent/security.ts — path validation, blocked patterns (.env, .ssh, *.pem) - src/sessions.ts — in-memory session manager (no SQLite) - src/index.ts — minimal HTTP server (200 lines, replaces NanoClaw pipeline) Works with any OpenRouter model that supports tool calling. No Docker for Work Mode, no NanoClaw routing, no delivery pipeline. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Reuse session across turns so conversation history is preserved - Pass HOME as default allowedFolder (TODO: use project settings) - Ignore 'aborted' SSE errors — they mean the stream closed normally Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Folder icon button appears in composer when Work Mode is selected - Click opens modal: add paths, remove with ×, stored in localStorage - workFolders passed via WS payload → chatRunner → createWorkSession - Session reuses folders from first turn (per chat) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
POST /api/pick-folder opens osascript dialog on macOS (zenity/kdialog on Linux). Browse button in folders modal — click selects folder natively, no typing required. Manual path input kept as fallback. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ect chats) Storage key: iclaw:work-folders:project:<id> or iclaw:work-folders:no-project. All chats in the same project share the same folder set. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Matches modern chat UI style (Claude.ai-like): - Textarea full width at top with 'Ask anything' placeholder - Toolbar row at bottom: [+ mode folders] ... [mic send] - Mode selector as flat text label in toolbar (no pill border) - All toolbar buttons share .composer-toolbar-btn base style Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Secure Mode: - ChatMode extended: 'ask' | 'execute' | 'work' | 'secure' - chatModes.ts: 'secure' mode enabled (requires OpenRouter) - secure-runner.ts: runs agent loop inside Docker container (--network none, --memory 512m, disposable temp workspace) - sessions.ts: routes secure:true sessions to secure-runner - chatRunner.ts: mode==='secure' goes through runWorkModeTurn(secure:true) Composer placeholders: - Ask: "Ask safely…" - Work: "Work inside selected folders…" - Secure: "Run risky tasks in isolation…" - Execute: "Use full iClaw power…" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Document the iclaw-relay tunnel feature under [Unreleased]: OPAQUE login, end-to-end encrypted HTTP/WS transport, trusted device sessions, the relay access-token gate, and the adversarial smoke + frame-capture coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Composer send-mode was tracked only in per-browser localStorage (keyed per chat,
with a fragile draft->chat migration) plus the last sent message's mode. Moving
page to page (full reloads) restored from the wrong/empty key and snapped the
mode back to the default.
Add a sticky `chats.mode` column, mirroring `chats.model_override` /
`reasoning_mode`:
- persist the moment the user picks a mode, even before sending a message
- render it as the authoritative `data-chat-mode`; client is now server-first
(falls back to localStorage only for legacy chats, then the UI default)
- broadcast `chat-updated{mode}` so other tabs/devices stay in sync
- incognito stays transient (never persisted)
Fixes the composer mode resetting when navigating between pages.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a no-Homebrew fallback so the Colima engine lands on a clean Mac with nothing preinstalled — no brew, no Xcode Command Line Tools, no sudo/password. scripts/install-docker.sh (macOS): if Homebrew is present, `brew install colima docker` as before; otherwise download PINNED colima + lima + docker binaries straight from the projects' official releases into ~/.iclaw/engine, verifying SHA-256 (hard fail on mismatch). Per-arch (arm64 + x86_64). The download is via curl, so the binaries aren't Gatekeeper-quarantined and run with their adhoc signatures. colima.ts (app + runtime): ensureColimaRouting() → ensureColimaEnv(), which now ALSO sets PATH — puts ~/.iclaw/engine/bin FIRST (so a no-brew install is found) and ensures Homebrew's bin dirs are present (a GUI-launched .app starts with a minimal PATH that omits them). Still pins docker calls to the iclaw context. New ENGINE_BIN constant (honours ICLAW_ENGINE_DIR). docker.ts: installDocker() re-runs ensureColimaEnv() after the install so the freshly downloaded binaries are on PATH before launch. build-secure.sh: same engine-dir PATH so Secure-image builds find colima too. Pinned: colima 0.10.3, lima 2.1.2 (brew's known-good pair), docker 29.5.3. Verified live on macOS arm64: with brew hidden, the installer downloaded and checksum-verified all three; `colima start iclaw` + a container ran using ONLY the downloaded toolchain; ensureColimaEnv() puts the engine dir first on PATH. typecheck passes (app + runtime). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings in: persist composer mode server-side so it survives navigation (3c6950d). No overlap with the macOS/Colima engine work on dev — clean merge.
Port the voice-recording feel from the Flutter app onto the existing speech-to-text mic, keeping the /api/stt pipeline untouched: - Hold the mic to record; slide left to cancel; slide up to lock hands-free (tap to send when locked). Pointer Events drive the gesture. - Live waveform + elapsed timer from a Web Audio meter; amplitude halo on the mic. Min 0.8s; quick taps show a "hold to record" hint. - Transcript is inserted at the caret (dictate-into-text), not appended; caret is captured when recording starts. - While recording the field collapses to just the waveform + mic; other controls are hidden via visibility (not display:none) so the mic keeps its position instead of jumping left under space-between. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Safe work runs entirely inside the secure sandbox container, but secure-runner hard-coded `iclaw-secure:latest` with no fallback — so a fresh `npx @iclawapp/iclaw` install (which ships neither the Dockerfile nor a pullable image) hard-failed with "Secure sandbox failed to start" the instant a Safe-work turn began. Work mode already degrades to a public base here; secure now matches it (AGENTS.md already documents node:22 as the emergency fallback for both). resolveSecureImage() prefers the curated image when built (or an explicit ICLAW_SECURE_IMAGE override), else node:22. The curated image bakes in `USER node` + a /workspace/.tools PATH for rootless self-installs; a bare node:22 runs as root and loses that, so the fallback path re-applies `--user 1000:1000` + HOME/NPM_CONFIG_PREFIX/PATH env to preserve the same non-root + rootless-install guarantees. The curated path is unchanged (fallbackArgs empty). Verified: runtime+host typecheck clean, 413/413 tests pass, docker smoke of both paths (non-root uid 1000, writable workspace, self-install on PATH, curl/git present, --network none egress gate intact). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…er by mode
The new-chat draft page (index.ejs) is a separate, stripped template from
chat.ejs, and adoptDraftChat() promotes a draft to a real chat client-side
without a reload. Share / Reasoning / Suggest-facts / Delete (plus the share
modal + share.js) lived only in chat.ejs, so they were missing on a just-
started chat until a manual reload.
- Extract the header tool cluster into partials/header-chat-tools.ejs, shared by
both views (draftHeader: true renders them hidden + action-less on the draft).
- promoteDraftHeaderTools() reveals them and fills in /chats/:id actions on
adopt (Suggest-facts only when the draft picked a project).
- Load the share modal + share.js on the draft too (share.js reads the chat id
live from #messages, so it works the moment adopt sets it).
- Add [hidden]{display:none!important} for the tools — they're inline-flex, so
the attribute alone wouldn't hide them.
- Bind the Reasoning toggle unconditionally and resolve activeChatId live.
Also hide the Agent picker outside Full Power (syncAgentVisibility): the agent
selects an OpenClaw session, which Work / Safe work / Incognito ignore (they
route to iclaw-runtime), so showing it there implied a no-op choice.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sandboxed tool that searches social platforms by keyword over FREE keyless sources — Reddit (RSS discovery + shreddit comment scrape) and HackerNews (Algolia). Runs inside the session container via node, honouring the same network gate as analyze_link; no API keys, no Python. - social-fetch.mjs: self-contained Node fetcher (built-ins only), shipped as an asset and base64-injected into the container. - social.ts: SOCIAL_SEARCH_TOOL definition + host-side handler/formatter. - Wired into Secure Mode (secure-runner) and the host loop (loop.ts + executeTool), gated on a sandbox backend. - build now copies the .mjs asset into dist. Reddit technique adapted from mvanhorn/last30days-skill (MIT). Reddit post upvotes are not recoverable keyless; comment counts/scores are. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Work Mode runs the sandbox as the HOST uid with folders mounted at /work/<n> and no writable /workspace (the image owns it as uid 1000), so the fetcher's `mkdir /workspace/.tools` failed with EACCES. Secure Mode runs as `node` and owns /workspace, so it worked there. Write to $HOME instead — set in both sandboxes (Work: /tmp, Secure: /home/node) and writable by any uid. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
social_search (and analyze_link) base64-inject a ~10KB tool script into a single shell command, which the dev `run_command` logger dumped verbatim on every call — flooding the log. Clamp the logged `cmd` to head + tail (the env/arg tail stays visible, the base64 middle is elided) in both the Work and Secure runners. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rd, dead-round breaker) A Work-Mode research turn could spin for dozens of rounds re-fetching the same handful of URLs with reworded `focus`/fake `#anchors` (observed: one turn ran 38 rounds / 434k tokens over ~10 distinct URLs). Since every round resends the growing context, cutting redundant rounds saves tokens ~quadratically. Three levers: - Semantic loop-guard (loop.ts `toolRepeatSignature`): web_fetch now keys on the normalized URL alone (strips `#fragment`, lowercases host), web_search on the normalized query (case/punct/operator/word-order-insensitive, unique sorted terms). The model can no longer dodge the repeat-guard by rewording. Non-web tools keep exact-args matching. Tailored nudges per tool. - Within-turn fetch cache (tools.ts `ToolContext.fetchCache`, fresh per turn): web_fetch serves a repeat pull of the same URL from memory and re-summarizes for a new `focus` with no network round-trip. Misses hit the network as before; failures are not cached so transient errors can still retry. - Dead-round circuit-breaker (loop.ts `isLowValueResult` + `DEAD_ROUND_LIMIT`, env ICLAW_DEAD_ROUND_LIMIT=5): after N consecutive rounds whose tool results were all empty/failed/timed-out/guard-blocked, force a conclusion — next round sends tool_choice:'none' and nudges the model to answer with what it has and not invent numbers, instead of grinding to MAX_ROUNDS. Verified: runtime + root typecheck, 413 host tests, 29-case smoke test of the pure helpers (normalizers, guard dedup, low-value classification). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dges When chats.mode is unset, the composer's "current mode" falls back to the most recent message's mode. System rows (e.g. the "saved X% tokens" savings badge) carry the back-compat default 'execute', so once such a row was the newest a Work chat reopened as Full Power. Skip non-user rows in the fallback so the last user-chosen mode wins. Fixes existing chats on reload (no migration). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… default GitHub repo/file pages are ~6x heavier than their raw content (1.4MB HTML vs ~220KB README) and htmlToText keeps the chrome then truncates at 20k, burying the actual content (observed: a turn hit the rendered repo page 3x before discovering the raw README). canonicalizeFetchUrl() now redirects before fetch+cache: github.com/owner/repo -> raw .../HEAD/README.md github.com/owner/repo/blob/<ref>/<p> -> raw .../<ref>/<p> Non-content routes (issues/pull/tree/wiki/profiles/reserved owners) and non-GitHub URLs pass through untouched. Cache + repeat-guard key on the rewritten URL, so the repo page and the raw README collapse to one entry. web_fetch now summarizes by DEFAULT (summarize !== false) so results stay small in history; the model sets summarize:false when it needs exact text (full lists, numbers/dates, source code, verbatim quotes). This also fixes the prior silent no-op where `focus` without `summarize:true` was ignored. Tool schema updated to teach the opt-out. Verified: typecheck, 413 host tests, 15-case canonicalizer smoke test; raw README/blob targets confirmed live (HTTP 200, clean markdown, 222KB vs 1.4MB). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wrap rendered tables in `.md-table-wrap` so wide tables scroll horizontally instead of breaking the thread layout, with a rounded frame, zebra rows and row hover. In table cells, collapse long bare-URL link text to a compact form (e.g. github.com/owner/repo) while keeping the full URL on href + title. Scoped to cells; prose URLs untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Work/Secure turns only ever showed "Thinking…" / "Generating…" — the agent's
tool activity was dropped. Two causes:
- chatRunner's runWorkModeTurn consumed the runtime SSE stream but had no
branch for tool events (only text/note/image/done/error), so they fell
through silently;
- the WorkEvent type declared a `tool` variant that never matched what the
runtime actually emits (`tool_start` / `tool_result`) — dead code.
Now Work/Secure surface tool activity through the SAME turn-tool pipeline the
gateway path already uses (status label + expandable detail in the composer):
- WorkEvent fixed to `tool_start` / `tool_result`;
- runWorkModeTurn maps tool_start → tool-start (label+detail), tool_result →
tool-end; incognito consumer updated to the real event name.
toolLabels: specific labels for iClaw's own tools, checked BEFORE the generic
regex so social_search no longer collapses to "Searching…":
social_search → "Searching social media…", web_search → "Searching the web…",
web_fetch → "Reading the web…", analyze_link → "Analyzing the link…", etc.
New toolActivityDetail() pulls a short, safe detail (query / URL host+path /
command / path, capped at 70 chars) for the expandable status line.
Verified: typecheck, 418 tests (updated existing label tests + added coverage
for the specific labels and toolActivityDetail).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "Searching the web…" / "Thinking…" status line sat ABOVE the streamed assistant text. Move it below so the agent's words come first and the live status reads as a footer beneath them. - reorder stream-body before stream-status in both client stream builders (main chat + task-ask) and the server reload-placeholder (chat.ejs); - flip .stream-status margin-bottom → margin-top so the 4px gap stays between the text and the status now under it. Frontend-only (no build step) — refresh to pick up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… as .json Follow-up to the research-loop token work, from observing a deep turn (124k/9 rounds): the model reworded social_search ~7x (the semantic repeat-guard only covered web_search/web_fetch), and fetched both the heavy HTML reddit thread page AND its .json. - toolRepeatSignature: social_search now keys on the normalized query like web_search, so reworded social queries collapse and trip the repeat-guard after the limit; shares the "rewording returns the same results" nudge. - canonicalizeFetchUrl: a reddit.com/.../comments/... URL is rewritten to the .json endpoint (clean post + comments, no HTML chrome). Idempotent + strips trailing slash / query / hash so the HTML URL and the .json URL collapse to one fetch + cache entry (kills the double-fetch). GitHub rules unchanged. Verified: runtime + root typecheck, 418 host tests, 17-case smoke (reddit variants, cache-key collapse, github regression, social_search guard). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The .json rewrite from 5a39855 backfired: Reddit's JSON API returns HTTP 403 (IP-blocked — confirmed with a browser UA too), and new-reddit (www) serves a JS-only shell (~8KB, no content). old.reddit.com is server-rendered: the real post + comments are in the HTML (verified — a thread returns 200, ~507KB, 139 comment blocks) and it's exactly the fallback the agent kept discovering on its own (www → old). canonicalizeFetchUrl now rewrites every reddit host → old.reddit.com, stripping a trailing `.json` (it 403s) and slash + the hash so the www / old / .json / slash variants collapse to one fetch + cache entry. This is the answer to "why does reddit block fetch": the JSON API and new-reddit are blocked; old.reddit is not. GitHub rules unchanged. Verified: runtime + root typecheck, 418 host tests, 13-case smoke including a live fetch of the canonical URL (HTTP 200, 507KB). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dges Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sidebar gateway banner appeared whenever OpenClaw was offline, even in Work / Safe work / Incognito modes that never touch the gateway. Gate it on the same getComposerMode() === 'execute' check the composer overlay uses, and refresh it on both gateway-status and mode changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ser/sidebar UX - Default mode after onboarding keys off whether the OpenClaw CLI is on the device: Full Power if installed, otherwise Work. New cached probe (openclawInstall.ts), primed at startup. - Mode selector always renders; runtime modes show locked with a 'needs OpenRouter' hint when no key, and clicking one opens the connect chooser. New listComposerModes() feeds per-mode availability to the composer. - Connect-a-model chooser (connectModal.ejs) replaces the post-Skip 'OpenClaw off' dead-end: composer stays usable and the chooser fires on first send (or on clicking a locked mode) when no backend is reachable. - Sidebar 'Start OpenClaw' banner gated on Full Power being the selected mode. - Sidebar declutter: Search after 15 chats, Projects after 5 (or once a project exists); the draft skips the project picker when there are no projects. - Empty-state copy reworded; welcome card (scenarios + about iClaw) extracted to a partial and shown on empty chats while no project exists. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A new chat now defaults to whatever's usable right now: Full Power when the OpenClaw gateway is reachable; Work when it's off but an OpenRouter key is set (don't drop the user into a dead Full Power); Full Power when off+no key but OpenClaw is installed (startable on demand); otherwise Work. Fixes two linked symptoms when OpenClaw is installed-but-off with a key: new chats defaulted to Full Power, and the sidebar 'Start OpenClaw' banner showed (it's gated on mode === 'execute'). Both now resolve to Work. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Quick skip past project selection: pressing Space on the "Choose a project" stage commits the draft with No project, with a hint under the title. Guarded to the active picking stage and ignored while a field is focused; Enter still activates a focused project card. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tupychka/dev
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ebar on mode switch Two linked regressions: - New-chat default no longer prefers Full Power when the gateway is up. It's key-based again: OpenRouter key set -> Work; no key + OpenClaw installed -> Full Power; otherwise Work. (Also keeps the 'OpenClaw is off' banner hidden for key users, since it's gated on mode === 'execute'.) - POST /chats/:id/mode no longer broadcasts updatedAt for draft chats, so switching mode on a freshly-created draft stops leaking the empty chat into the sidebar (the client upserts a row on any updatedAt). mode still syncs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The toggle only controlled visibility of the model's chain-of-thought (the actual effort knob, thinkingLevel, was never wired up), and behaved the same on/off in practice. Removed across the stack: - UI: header toggle + icon (header-chat-tools.ejs, header-tool-icon.ejs). - Client: change handler, cross-tab sync, turn-reasoning case, render helpers, finalize calls, draft reveal, dead selectors (iclaw.js); CSS rules. - Route: POST /chats/:id/reasoning. - Gateway: the 'reasoning' event type and the whole patchSession (its only caller was that route); analysis/chain-of-thought items are now dropped. - Store/types/DB: setReasoningMode, reasoning_mode field + column, reasoningMode + turn-reasoning protocol types. - Tests: dropped the reasoning-specific cases. Existing DBs keep a harmless unused reasoning_mode column. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tupychka/dev
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Changes
Screenshots / GIFs
How to test
Checklist
devnpm run typecheckpassesnpm run buildpassesCHANGELOG.mdunder[Unreleased]if user-visibleROADMAP.mdif a planned item is now done