The subprocess restart approach did not solve the underlying Bun native
memory retention issue (oven-sh/bun#28318). Remove all restart plumbing
from rpc.ts, thread.ts, worker.ts, app.tsx, and sdk.tsx to keep the
diff clean. The diff-size and store-eviction fixes remain.
Bun Workers are threads within the same OS process — terminate() frees
the JSC context but mimalloc retains every page process-wide, so the
previous Worker-restart approach had zero effect on RSS.
Switch to Bun.spawn() with IPC which creates a separate child process.
Killing that process returns all its native memory to the OS.
The IPC relay pattern means the RPC client persists across subprocess
restarts — event listeners, fetch proxy, and SDK all continue working
without getter-indirection or rebinding.
Workaround for https://github.com/oven-sh/bun/issues/28318
Bun's JSC does not return freed native heap pages to the OS within a
single Worker lifetime. After large sessions, the only way to reclaim
that 2-3 GB of native allocator retention is to terminate the worker
and spawn a fresh one.
Workaround for https://github.com/oven-sh/bun/issues/28318
- Add getter-indirection layer so fetch/events transparently follow
worker replacement without rebuilding the TUI
- Add rejectAll() to RPC client to fail in-flight calls on termination
- Add rebindable event source that re-registers handlers on new client
- Wire /new command to fire-and-forget restart; sync layer re-bootstraps
via server.instance.disposed event from the new worker
- Guard against re-entry and skip in external server mode
User messages carry summary.diffs with full before/after file content
(the same giant strings as session_diff). The TUI never reads this
field. Strip it at both entry points (SSE handler + full sync) to
prevent multi-MB strings from accumulating in the Solid store.
The Solid store accumulated messages, parts, diffs, todos, status, and
permissions for every session visited during a TUI lifetime. Navigating
away via /new or the session list never freed the old session's data.
Add an evict() function that deletes all per-session entries from the
store maps and clears the fullSyncedSessions cache. Wire it into:
- A createEffect in app.tsx that fires when the route changes away
from a session (on() tracks prev vs current sessionID)
- The session.deleted SSE handler, which previously only removed the
session list entry but left orphaned per-session data
Existing sessions may have multi-GB before/after strings persisted in
session_diff JSON files. The read path in Summary.diff() now checks each
entry against the 256 KB cap and replaces oversized content with empty
strings, then rewrites the file so subsequent loads are fast. This follows
the existing unquoteGitPath migration pattern.
Avoids allocating multi-MB strings in the JS heap for oversized files.
Previously the full content was read then discarded; now the object size
is checked first via cat-file -s and the git show is skipped entirely
when either side exceeds 256 KB.
Defense-in-depth: destructure away before/after content from FileDiff
objects at both TUI store entry points (SSE handler + full sync). The
sidebar only reads file, additions, deletions — carrying full file content
in the Solid store is unnecessary and risks memory bloat.
When diffFull() reads file contents via git show, files exceeding 256 KB
(e.g. .heapsnapshot JSON) are now treated like binary files — before/after
are replaced with empty strings. This prevents multi-GB strings from
accumulating in downstream consumers (storage, SSE, TUI, VS Code, sharing).
Remove find (-exec), env (runs commands), git (hooks/aliases),
npm/yarn/pnpm/bun (postinstall scripts, run subcommand), and make
(executes Makefile recipes). Keep only commands that cannot spawn
subprocesses or execute arbitrary code.
- Remove commands that can execute arbitrary code (node, python, curl,
docker, etc.) from the default bash allowlist
- Keep only read-only/informational commands, text processing, file
operations, git, package managers, compilers, and archive tools
- Handle legacy TOML config file in bash migration to detect existing
users who only have the old config format
- Move bash permission migration from storage.ts to Config.global() so
it runs before config is consumed (fixes timing issue)
- Check all 5 global config files (config.json, kilo.json, kilo.jsonc,
opencode.json, opencode.jsonc) for existing bash permission before
migrating, and write to the highest-precedence existing file
- Add default allow-list of safe bash commands (ls, git, npm, etc.) so
users aren't prompted for common development commands
Add bash:ask to hardcoded permission defaults so new users are prompted
before shell commands execute. A storage migration preserves the existing
bash:allow behavior for existing users by writing it to their global config
if they haven't explicitly set a bash permission.
Instead of placing user before the orchestrator allowlist (which prevents
users from restricting allowed tools), keep user in the normal position
but add an explicit bash deny after user. This way:
- User can still deny orchestrator-allowed tools (e.g. webfetch)
- User cannot re-enable bash since the post-user deny wins via findLast
On Windows, path.relative() returns an absolute path when source and
target are on different drives. The ignore npm package throws a
RangeError when fed such paths. This caused 'Failed to send prompt'
when VS Code had open tabs from another drive (e.g. extension settings
in AppData while workspace is on D:).
Guard all path.relative() → ignore.ignores() call sites against
absolute results by checking path.isAbsolute() and a Windows
drive-letter regex.
Move `user` before the orchestrator-specific PermissionNext.fromConfig()
so that findLast() returns the orchestrator's rules (which appear later
in the merged array), preventing user config from re-enabling bash.
This mirrors the pattern already used by the `ask` agent.
Also removes the dead commented-out bash line since `*: deny` already
covers it.
The orchestrator mode had `bash: "allow"` in its permission ruleset, creating a
loophole where the LLM could use shell commands (sed, echo >, tee, etc.) to write
files directly instead of delegating to sub-agents via the task tool.
This is inconsistent with the orchestrator system prompt which says "Do not edit
files directly" and with how kilocode-legacy handled this (no command access at
all for orchestrator).
Removes bash from the allowed tools. The orchestrator still has read, grep, glob,
and list for lightweight codebase investigation, plus task for delegation.
Closes#7575