Only show the Save button when the channel list or enabled toggle differs from the last-persisted config, so it disappears after a successful save and reappears when a new channel is entered.
* fix(code-mode): make packaged code mode work via on-demand engine provisioning
Packaged builds could never run code mode: the Claude/Codex ACP adapters are
spawned as separate `node <entry>` processes resolved at runtime, but esbuild
can't inline a dynamic spawn target and Forge strips the workspace node_modules,
so every release threw `Cannot find module '@agentclientprotocol/...'`. Dev
worked only because of the pnpm symlink.
Rather than bundle the ~400 MB of native engines (one claude + one codex binary
per OS), provision them on demand:
- forge.config.cjs: stage the two ACP adapters + their JS dependency closure into
.package/acp/node_modules (npm-style nested layout, native engines skipped),
exempt .package from the node_modules ignore rule, and only sign/notarize when
APPLE_ID is set so unsigned local/CI builds can package.
- agents.ts: resolve the adapter from the staged location first (node_modules
fallback in dev); provision the pinned engine and point the adapter at it via
CLAUDE_CODE_EXECUTABLE / CODEX_PATH. No dependence on a user's global install.
- engine-provisioner.ts: ensureEngine() downloads the per-platform engine package
from npm AT THE EXACT VERSION THE ADAPTER WAS BUILT AGAINST, verifies its sha512
integrity, extracts atomically into ~/.rowboat/engines/<agent>/<version>/, and
caches it. Version-pinning keeps the ACP handshake compatible.
- engine-manifest.ts + scripts/gen-engine-manifest.mjs: committed manifest of
tarball URLs + integrity for all platforms, regenerated from the adapters'
pinned versions on a bump.
Verified on macOS arm64: both engines provision and run, and both adapters
complete the ACP initialize handshake from the packaged .app against the
provisioned engines. Installer drops from ~790 MB to 390 MB.
* feat(code-mode): explicit per-agent Enable in Settings; no silent chat download
Code mode now requires the user to explicitly enable an agent before use, instead
of silently downloading a ~200 MB engine on the first chat message.
- Settings → Code Mode: each agent shows "Not enabled" + an Enable button that
downloads its engine with a live progress indicator (download % → verify →
install), then flips to "Engine ready". Driven by a new codeMode:provisionEngine
IPC call + a codeMode:engineProgress push channel. The section now states the
prerequisite explicitly: the agent must be installed (Enable) and logged in
(claude login / codex login — code mode reuses that saved credential).
- Chat path no longer auto-downloads: getProvisionedEnginePath() returns the
enabled engine or throws a clear "enable it in Settings → Code Mode" error, so
there's never a surprise mid-conversation download. getAgentLaunchSpec is sync
again.
- Agent status: `installed` now means "engine provisioned" (downloaded), driving
the Enable/Ready state; the new-session dialog shows "Enable in Settings" and
disables un-enabled agents. Dropped the dead PATH-probing for a global CLI.
Verified: empty cache -> status installed=false and the chat path throws the
enable-in-Settings error (no download); core, renderer, and main typecheck/build;
no new lint errors.
* fix(code-mode): show only percentage during engine download in Settings
* feat(code-mode): prune superseded engine versions after install
After a successful provision, remove any other version dirs (and their .meta) for
that agent so old ~200 MB engines don't accumulate across version bumps. Best-effort;
never fails a good install. Verified: a planted stale version dir + meta are both
removed after provisioning the current version.
* fix(code-mode): keep showing engine download % after reopening Settings
Provisioning state lived in the row component, which unmounts when the Settings
dialog closes — so reopening mid-download showed the Enable button again even though
the download was still running in the main process. Move provisioning state to a
module-level store with one persistent listener on codeMode:engineProgress, so a row
remounting (dialog reopened) reflects the live % and resolves to Ready on completion.
* fix(code-mode): flip Enable row straight to Ready after install (no Enable flash)
On successful provision the in-flight flag was cleared before the async status
refresh completed, so the row briefly (or until reopen) showed the Enable button
again. Await the status refresh before clearing the flag so it transitions directly
to Ready.
* fix(code-mode): optimistically show Ready right after Enable completes
Awaiting the status refresh wasn't enough — setStatus re-renders the parent
separately from the row, leaving a window where the in-flight flag was cleared but
the status prop was stale, so the row flashed/stuck on the Enable button until
reopen. Track just-enabled agents in a module-level set and treat them as installed
immediately; loadStatus still syncs the real status in the background.
* fix(code-mode): graft login-shell PATH + add startup deadline
#1 (the gh/git "command not found" in packaged builds): GUI/Finder launches inherit
launchd's stripped PATH (/usr/bin:/bin:...), so tools the engine spawns — gh, git,
rg, bash — fail even though they work from a terminal (e.g. Homebrew's
/opt/homebrew/bin/gh). Probe the user's login-shell PATH and graft it onto the
engine's env before spawn (shell-env.ts; no-op on Windows / probe failure).
#2: add a 60s startup deadline (initialize / session create+load) so a wedged engine
fails with a clear, stderr-enriched error instead of an infinite "(pending...)".
Overridable via ROWBOAT_ACP_STARTUP_TIMEOUT_MS. Manager now disposes the client on
startup failure so the spawned adapter doesn't leak.
Verified: getAgentLaunchSpec's env.PATH now includes /opt/homebrew/bin (where gh
lives); core builds; no new lint errors.
* chore(code-mode): comment out signing/notarization for local builds
Revert to the explicit comment-out approach for osxSign/osxNotarize: uncomment them
(with APPLE_ID/APPLE_PASSWORD/APPLE_TEAM_ID) for a signed release build.
* chore(code-mode): keep signing/notarization active in committed config
The repo's forge.config ships with osxSign/osxNotarize enabled (release-ready).
Developers comment them out locally for unsigned test builds and don't commit that.
* chore: approve workspace build scripts so packaging runs non-interactively
The allowBuilds entries were left as "set this to true or false" placeholders, so
`pnpm install` / the pre-build deps check aborted with ERR_PNPM_IGNORED_BUILDS and
`npm run package` failed. Set them to true (and add node-pty, used by the code-mode
embedded terminal) so build scripts are approved and packaging works without a manual
`pnpm approve-builds`.
* fix(code-mode): ship ACP adapters in packaged builds
Code mode spawns the Claude/Codex ACP adapters as separate `node <entry>`
processes resolved at runtime, so each must exist as a real file on disk.
esbuild can't inline them (dynamic require.resolve + spawn target), and Forge's
`ignore: /node_modules/` rule strips the workspace node_modules — so packaged
builds threw `Cannot find module '@agentclientprotocol/claude-agent-acp'` and
code mode was broken in every release. Dev worked only because the pnpm symlink
was present.
Stage the two adapters and their full production dependency closure into
.package/acp/node_modules during generateAssets, reconstructing an npm-style
nested layout: nest on version conflict (claude and codex keep their own
@agentclientprotocol/sdk; the @openai/codex launcher keeps its platform binary)
and skip platform-optional deps not installed for the build OS, so each OS ships
its own native binary. Exempt .package from the node_modules ignore rule, and
make the adapter resolver check the staged location first, falling back to
node_modules in dev.
Resolve dependency directories by walking node_modules directly rather than
require.resolve(`${pkg}/package.json`): the latter throws for packages whose
`exports` map doesn't expose package.json (e.g. @anthropic-ai/claude-agent-sdk),
which would silently drop them and their subtrees from the staged closure.
* fix(code-mode): drive agents from local install, drop bundled engines
Skip the platform-native engine packages (~230 MB each) when staging the ACP adapters and point each adapter at the user's local claude/codex via CLAUDE_CODE_EXECUTABLE / CODEX_PATH, erroring clearly when neither is installed. The codex resolver mirrors claude's login-shell probe so nvm/fnm installs resolve on macOS/Linux. Shrinks each installer ~460 MB.
* fix(code-mode): surface agent startup failures instead of hanging forever
- 60s deadline on adapter initialize / session create+load: an engine that
launches but never completes the SDK handshake (e.g. an outdated local
CLI) now fails with the adapter's stderr attached instead of leaving the
turn (pending...) indefinitely; prompts themselves stay un-timed
- dispose the adapter client when startup fails so the spawned process
does not leak
- set DEBUG_CLAUDE_AGENT_SDK=1 so the SDK logs the exact spawn command and
claude's stderr to ~/.claude/debug/sdk-*.txt and startup errors point at
that file (engine stderr is otherwise discarded entirely)
- graft the user's login-shell PATH onto the adapter env on macOS/Linux:
GUI launches inherit launchd's stripped PATH, which breaks node-shebang
claude launchers (nvm/npm installs) and the engines' own subprocess spawns
* ci(code-mode): cross-platform smoke matrix + one-shot diagnose script
- x-code-mode-smoke.yml: on apps/x PRs, package the app on mac/linux/windows
and run acp-smoke.mjs, which asserts (1) adapters staged + native engines
stripped, (2) each staged adapter boots from the packaged app via the
packaged Electron binary and answers ACP initialize, (3) a fake engine that
launches but never responds is converted into a clear startup-timeout error
instead of hanging forever (the silent-hang class)
- diagnose-code-mode.sh: colleagues run one command and send one blob
(engine versions/paths/types, login-shell vs GUI PATH, auth presence,
stream-json probe, newest SDK debug log) — one round trip instead of five
- forge.config.cjs: only sign/notarize when APPLE_ID is set, so unsigned
local mac builds and the CI smoke matrix can package deterministically
- client.ts: startup timeout overridable via ROWBOAT_ACP_STARTUP_TIMEOUT_MS
(CI uses 10s; also an escape hatch for slow MCP-heavy setups)
Verified on Windows: all smoke checks pass, including the end-to-end
fake-hanging-engine timeout (fails in 10.0s with the stderr-enriched error).
* fix(ci): make smoke fake engines executable — codex-acp spawns CODEX_PATH directly on unix
A bare .js with no shebang/exec bit fails with EACCES and crashes the adapter
on mac/linux. Split into an exec-bit exit-0 fake for the handshake check and
the hanging fake for the timeout check.
* fix(code-mode): resolve claude/codex installed via version managers
commonInstallPaths only checked ~/.nvm/versions/node/<binary>, never
descending into nvm's versioned vX.Y.Z/bin subdirs, so an nvm-installed
codex/claude showed "not installed" in Settings and failed code-mode
chat with "CLI not found" on GUI launches (where the login-shell PATH
isn't inherited).
Enumerate each installed Node version's bin dir for nvm/fnm/asdf, and
add pnpm global (PNPM_HOME / platform default) and Claude Code's legacy
~/.claude/local install location. Shared by both the Settings status
check and the chat/tab binary resolvers, so it covers all code-mode
entry points.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(code-mode): remove code-mode smoke test workflow and script
Only needed during cross-platform verification of the packaged code-mode
fix; drop the CI workflow and its acp-smoke.mjs helper now that it's done.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Styled HTML emails (newsletters, branded mail) declared color-scheme
'light dark' whenever the app theme was dark, so Chromium resolved the
iframe to the dark scheme and painted an opaque dark canvas under
emails that assume a white background, making their text unreadable.
Only opt into the dark scheme when the email actually adapts to the
app theme.
Drop the per-load LLM ranker (cost/latency/model dependency) in favor of a stronger deterministic filter + recency ordering. The filter now removes system messages, emoji/reaction-only posts, bare greetings/acks, and empty bodies, with a durable-signal escape hatch. Expand tests to one describe per noise class plus ordering/cap/volume coverage.
Classify agent-slack errors (not_authed/rate_limited/network/bad_channel), persist per-source sync status with rate-limit backoff, and expose it via slack:knowledgeStatus. Fix the Settings Enable bounce-back with actionable copy, a browser-paste (parse-curl) fallback, and a Windows quit-Slack-and-import button; add home-feed empty/error states.
Pins agent-slack@0.9.3, bundles it next to main.cjs (replaces the startup npm install -g), adds a structured-result executor with bundled/global/PATH resolution, a slack:cliStatus IPC probe, and a PATH shim so the Copilot skill keeps working.
On macOS, the first getUserMedia({audio:true}) call hits TCC permission
status 'not-determined' — the OS prompt appears but the in-flight call
rejects, is silently swallowed, and the UI snaps back to idle. Second
click works because permission is already granted.
Fix: add voice:ensureMicAccess IPC channel (mirroring the existing
meeting:checkScreenPermission pattern) that calls
systemPreferences.askForMediaAccess('microphone') before getUserMedia,
so the same first click proceeds once the user grants access.
Also fixes a secondary bug: on the failure path, the code only called
setState('idle'), leaking the WebSocket that connectWs() had already
opened. Now calls stopAudioCapture() for proper cleanup.
* fix: add track-state polling for macOS meeting auto-stop
On macOS (ScreenCaptureKit/Electron 39) the system-audio track does not
fire 'ended' or 'mute' events when the meeting ends. Add a 3-second
polling interval that checks track.readyState and track.muted directly.
Auto-stops after the track is muted for 3 consecutive polls (~9 seconds),
or immediately if readyState becomes 'ended'. Windows behavior unchanged
(existing 'ended' event listener kept intact).
* fix: gate macOS meeting muted-poll auto-stop on scheduled calendar end
On macOS the system-audio track reports muted=true both when the meeting
ends and during any silent stretch of a still-live meeting, so the
unconditional ~9s muted hard-stop could cut a quiet-but-live meeting short
with no warning.
Only hard-stop on sustained mute once we're past the linked calendar
event's scheduled end (a strong "it's really over" signal); otherwise let
the existing silence nudge + backstop handle it. Gate the whole poll to
macOS — Windows already auto-stops via the track "ended" event — and drop
the noisy per-poll debug log.
---------
Co-authored-by: Gagancreates <gaganp000999@gmail.com>
* feat: render background-task index.html output in a sandboxed iframe
The task output pane now prefers bg-tasks/<slug>/index.html when present and
non-empty, rendering it full-bleed via HtmlFileViewer (app://workspace
protocol) so CSS, layout, and scripts render faithfully. Falls back to the
markdown index.md note when there is no HTML artifact. The viewer remounts on
refreshKey so a re-run's updated HTML reloads. The Source/Rendered toggle works
for both formats.
The runner agent is instructed to choose index.md (default, notes) vs a
self-contained index.html (visual/styled output) per run, written via the
existing file-writeText tool. The Copilot background-task skill notes the HTML
option so visual asks are steered toward it.
* fix: open links from HTML report iframes in the system browser
Links inside the sandboxed iframe that renders a background-task/workspace
index.html did nothing on click, unlike the markdown viewer which opens links
in the browser.
Two causes: target="_blank" links were blocked by the sandbox before reaching
the window-open handler, and plain links fire will-frame-navigate (subframe),
which the app did not handle (will-navigate only covers the main frame).
- Add allow-popups to the HtmlFileViewer iframe sandbox so target="_blank"
reaches setWindowOpenHandler, which routes to shell.openExternal.
- Handle will-frame-navigate in main, routing external subframe navigations to
the system browser. Scoped to app://workspace frames so third-party note
embeds (YouTube/Figma/Twitter) keep their internal navigation.
Adds a gmail:searchContacts IPC channel backed by two indices: a
SENT-label API-backed index (gmail_sent_contacts) for full historical
coverage of people you've actually emailed, and a local-snapshot
fallback (gmail_contacts) used until the SENT sync finishes on first
launch. Both indices warm at startup so the first keystroke in the
recipient box is instant. Renderer wires the suggestions into the
to/cc/bcc fields in email-view with styled chips.
Co-authored-by: arkml <6592213+arkml@users.noreply.github.com>
Custom Electron Forge maker that wraps makepkg to produce a
.pkg.tar.zst with /opt/<app>, /usr/bin wrapper, .desktop entry,
and hicolor icon. Only activates on Linux when makepkg is present,
so other platforms are unaffected.
Detect silence from raw mic+system audio armed at recording start, add a quiet-meeting stop nudge, shorten the window once past the calendar end time, and stop instantly when the shared call window closes.
* fix: prevent chat bar model selector from overflowing in narrow panel
* fix: contain chat bar left items so code pill clips instead of overflowing
* fix: compact icon-only mode for chat bar when panel is narrow
* fix: dynamic compact threshold based on visible toolbar items
* fix: use actual DOM overflow detection to eliminate toolbar overlap
* fix: progressive right-to-left icon collapse for chat toolbar
* fix: instant icon switch, remove search label transition
* fix: correct right-to-left collapse order (code→perm→search→workDir)
* fix: measure actual DOM overflow instead of estimating — eliminates half-text and disappearing icons
* refactor: replace JS overflow logic with CSS container queries
Drop the ResizeObserver/useLayoutEffect collapse machinery and the
estimated pixel thresholds in favor of declarative @container variants.
Each toolbar item swaps to icon-only at a fixed container-width
breakpoint (code 560, perm 460, search 410, workDir 370px), collapsing
right-to-left. Atomic swaps mean no half-clipped text and no
disappearing buttons.
* fix: move @container to card root so breakpoints track panel width
Putting container-type on the toolbar's own flex row made it stop
stretching to fill the card and hug its collapsed content instead, so
the query read a permanently-narrow width that never grew on widen.
The card root reliably spans the full panel width.
* fix: collapse toolbar by measuring real overflow, not fixed breakpoints
Fixed container-query breakpoints can't know the workdir name length or
model name width, so labels stayed full and overflowed into the model
selector. Replace with overflow measurement: a ResizeObserver resets to
full on any width/content change, then a pre-paint layout effect collapses
items right-to-left (code -> perm -> search -> workdir) until the row fits.
overflow-hidden on the group is a hard guarantee against any overlap.
* feat: overflow menu for toolbar items that don't fit even as icons
When the bar is too narrow to show every control as an icon, the
right-most items move into a '...' overflow dropdown (code -> perm ->
search -> workdir) instead of being clipped, so no icon is ever hidden.
Toggle items keep the menu open on click via onSelect preventDefault.
* fix: keep overflow menu open when toggling items inside it
Toggling an in-menu item (code mode, agent, search, perm) updated state
that was in the collapse-reset deps, resetting collapseLevel to 0 and
unmounting the '...' trigger mid-interaction. Drop the in-place toggles
from the reset deps so the menu stays open on click.
* fix: drop 'Options' label from toolbar overflow menu
---------
Co-authored-by: arkml <6592213+arkml@users.noreply.github.com>
* feat(code-mode): add ACP client engine (Layer 2 core)
Own the Agent Client Protocol client instead of shelling out to `acpx`, so code
mode can stream structured events (tool calls, diffs, plan) and surface live
permission requests. Headless acpx can't do live approvals (it only supports
--approve-all), which is why we drive the agent adapters ourselves.
- code-mode/acp/{agents,client,permission-broker,session-store,manager,types}.ts:
headless engine driving the Claude/Codex ACP adapters; one warm session per chat
with create-or-resume via session/load; approval policy (ask | auto-approve-reads
| yolo) in the broker.
- claude-exec.ts: cross-platform claude resolver (Windows .cmd EINVAL fix + macOS/Linux
GUI-PATH safety net) shared with the legacy acpx path in builtin-tools.ts.
- add @agentclientprotocol/sdk + claude/codex adapters to core.
* feat(code-mode): route code mode through code_agent_run tool + live approvals
Replace the acpx shell-out with a structured code_agent_run tool that drives the
ACP engine directly, streaming the agent's tool calls / diffs / plan into the chat
and surfacing permission requests inline.
- shared: code-mode.ts zod schemas; add code-run-event + code-run-permission-request
RunEvent variants (stream to the renderer over the existing runs:events channel);
codeRun:resolvePermission IPC channel.
- core: CodePermissionRegistry (promise-based mid-run approvals — the LLM tool-loop's
pre-call gate can't model a mid-execution wait); register codeModeManager +
codePermissionRegistry in awilix.
- core: code_agent_run builtin tool (streams via ctx.publish, asks via the registry,
cancels on ctx.signal, returns the agent summary). CodeModeConfig.approvalPolicy
(ask | auto-approve-reads | yolo; default ask). Exclude the tool from the headless
background-task / live-note / inline-task agents so they can't block on an approval.
- main: codeRun:resolvePermission handler -> registry.resolve.
- rewrite the code-with-agents skill and the runtime "Code Mode (Active)" block to call
code_agent_run instead of emitting npx acpx commands.
* feat(code-mode): render coding runs inline (live timeline + permission card)
Render a code_agent_run tool call as a live CodingRun block instead of generic
tool output: the agent's text, tool-call rows (kind icon + status + changed-file
names from diffs), a plan checklist, and resolved-permission lines — plus an
inline Allow / Always-allow / Deny card wired to codeRun:resolvePermission.
- chat-conversation.ts: ToolCall carries codeRunEvents + pendingCodePermission;
code_agent_run is excluded from tool-grouping so it renders standalone.
- App.tsx: handle code-run-event / code-run-permission-request, clear the pending
card on tool-result, handleCodePermissionResponse, render via CodingRunBlock.
* fix(code-mode): run the ACP adapter as Node under Electron + resolve it from main
Two runtime failures that only surfaced inside the packaged/bundled Electron app
(the headless harness used real node, so neither showed there):
- "ACP connection closed": the main process spawns the adapter via
process.execPath, which inside Electron is the Electron binary, not node — so
the child never ran as Node and its ACP stdio stream closed immediately. Set
ELECTRON_RUN_AS_NODE=1 on the adapter env (a no-op under real node).
- "Cannot find module '@agentclientprotocol/claude-agent-acp'": the adapters were
transitive (core) deps, unreachable from the esbuild-bundled main.cjs. Add them
as direct deps of the main app so require.resolve finds them at runtime (and so
they ship when packaged).
Also capture the adapter's stderr + exit code and enrich connection errors, so a
future failure reports the real cause instead of the opaque "ACP connection closed".
* chore(code-mode): remove dead acpx code paths and stale copy
Code mode now runs through the code_agent_run tool (owning the ACP client), so the
legacy acpx shell-out paths are dead. Remove them:
- core: envForCommand (acpx-only CLAUDE_CODE_EXECUTABLE injection) from
executeCommand; getCodeModeCommandLabel (acpx run-status label).
- renderer: the acpx-detection "switch agent / auto-flip the code-mode chip" flow —
App.tsx executeCommand detection, the permission-request onSwitchAgent button +
badge, and the composer's code-mode-detected listener.
- copy: Settings -> Code Mode and the code-with-agents skill summary no longer
mention acpx; tidy stale comments (claude-exec, command-executor).
No behavior change for code mode; the general executeCommand tool is unaffected.
* feat(code-mode): approval-policy selector in Settings
Surface the approval policy (Ask every time / Auto-approve reads / YOLO) in
Settings -> Code Mode, instead of being config-file only. The broker already
reads CodeModeConfig.approvalPolicy; this plumbs it through the
codeMode:getConfig / setConfig IPC + main handlers and adds the picker UI
(with a one-line explanation of each level). Defaults to "ask".
* fix(code-mode): harden ACP engine — turn-scoped connections, chip-authoritative agent, reliable stop
Three robustness fixes that co-modify manager.runPrompt and the code_agent_run
tool, so they land together:
- Lifecycle: scope each ACP adapter connection to the agent turn. Dispose it a
short grace (60s) after the turn ends instead of holding it for the app's life;
the next turn resumes via session/load (both agents support it). Wire
disposeAll() on app quit (was dead code). Fixes the unbounded per-chat leak of
booted agent processes.
- Agent selection: make the composer chip the source of truth. Thread codeMode
into ToolContext; code_agent_run uses it instead of the model's guessed `agent`
arg, which anchored on the thread's earlier agent and ignored a chip change.
Prompts updated to match; the run is labelled by the agent that actually ran.
- Stop/abort: guarantee a stopped turn unwinds. On abort the manager sends ACP
session/cancel, then force-kills the adapter after a 2s grace and resolves the
turn as cancelled — a wedged adapter can no longer hang the run and lock the
chat. code_agent_run returns a clean cancelled result.
* fix(code-mode): hide Codex's native console window on Windows
Codex's engine ships as a native console-subsystem binary (codex.exe). Launched
from our console-less Electron process tree, Windows allocated a fresh *visible*
console window for it; closing that window wedged the run in a pending state.
(Claude Code is a Node CLI, so it never triggers this.)
The window is created by @openai/codex's launcher (bin/codex.js), which spawns
codex.exe with no windowsHide. Patch it via pnpm to pass windowsHide: true
(CREATE_NO_WINDOW) so the console stays hidden — no window, nothing to close.
* refactor(code-mode): move ACP session files out of WorkDir/config
Per-run ACP session state is runtime state that accumulates one file per
chat run, not user/app config. Relocate it from WorkDir/config to a
dedicated WorkDir/code-mode/sessions/ so it can be listed, cleaned up, and
managed on its own without crowding config. Drop the now-redundant
codesession- filename prefix (the directory conveys it).
- add LLM-based auto permission classifier for permission-gated tool calls
- store run-level permission mode and auto permission decision events
- auto-approve low-risk calls, and bubble auto-denied calls to manual approval
- show auto-denied reasons in chat and auto-approved labels below tool cards
- add BYOK setting for the auto-permission decision model
Add a DocxFileViewer (via @eigenpal/docx-editor-react) wired into the file-type viewer switch, reading/saving bytes through the existing base64 workspace IPC with debounced autosave.
Pin Electron release builds to Node 24.15.0, the last known-good runner version for Windows/Linux packaging, and fail artifact upload when out/make is empty so successful jobs cannot hide missing release assets.
Move volatile current time and middle-pane data out of the system prompt and into a hidden userMessageContext stored on each user message. Reconstruct the LLM-facing message from this persisted context so older conversation turns remain stable across later requests while UI-facing content stays unchanged.
Keep finite branch instructions such as voice, search, code mode, agent notes, and workdir behavior in the system prompt so each conversation can still benefit from reusable prompt-cache prefixes.