* fix(cli): drain session ingest queue on shutdown and flush terminal batches promptly
* fix(cli): drain the session ingest queue on process shutdown
* fix(cli): pin drain bound expiry, add changeset, conform to naming rule
* fix(cli): keep kilo-sessions out of the CLI startup import graph
* fix(cli): never let the ingest drain task reject the shutdown sequence
* test(cli): pin drain-before-dispose ordering on the KiloCli shutdown path
* fix(cli): make the guarded ingest drain non-rejecting and correct the lazy-import rationale
* test(cli): cover the retryable-status drain path under shutdown
* test(cli): decouple cli-shutdown drain assertions from declaration order
* fix(cli): advertise the instance from enableRemote so /remote registers as a spawn target
Enabling the remote relay from the TUI `/remote` slash command connected the
socket and mirrored sessions, but never advertised the instance, so the CLI
never appeared as a spawn target in the mobile "Run on" picker. Only the
explicit `kilo remote` command called setInstanceAdvertisement.
The advertisement now runs on every successful enableRemote() entry, before the
already-connected and coalescing early returns. That ordering matters: bootstrap
auto-enable frequently connects first, so `/remote` usually hits
`if (remote) return` and an advertisement placed in the connection-setup body
would leave the defect unfixed in the common case. `ingestDisabled` returns
before the advertisement and stays unadvertised.
The ensure helper is a no-op when an advertisement is already set, so it fires no
extra heartbeat, while explicit setInstanceAdvertisement keeps its existing
replace semantics. buildInstanceAdvertisement moves to a shared module so the
command path and the enable path derive it identically.
* fix(cli): report pending question and permission on the session heartbeat
The heartbeat built each session's status from SessionStatus.Service, whose
union is idle/retry/busy/offline and which never consults Question.Service or
Permission.Service. deriveStatus() already did consult both, but only fed the
ingest session_status sync. So a session genuinely blocked on a question was
advertised as busy on the heartbeat, and the mobile app — which takes live row
status from the heartbeat — showed no needs-input badge.
Extract the precedence (permission, then question, then SessionStatus) into a
shared helper used by both deriveStatus and the heartbeat, so the two channels
cannot drift.
The heartbeat runs on a ~10s timer across every session, and deriveStatus makes
service calls per session, so the permission and question lists are fetched once
per tick and indexed by session id rather than queried per session. A test pins
the call count.
Behaviour note beyond the strict fix: sharing the derivation also means a
SessionStatus of offline now reports as retry on the wire, matching what
deriveStatus has always sent to ingest. Nothing consumes offline from the
heartbeat — the transport forwards only idle and busy, and the mobile row treats
both as non-attention — so the effect is that the two channels now agree. The
detach fence test is parameterised accordingly; its assertion that the status
clears on detach is unchanged.
* chore(cli): widen the promise-facade allowlist for the heartbeat attention tests
The DEF-3 heartbeat tests raise and reply to real Question and Permission
requests through the global AppRuntime, which took kilo-sessions.test.ts from 4
classified references to 29 and failed the allowlist check.
Bumping the count rather than restructuring the tests is deliberate: the
heartbeat resolves attention status from the global Question.Service and
Permission.Service, so asserting it requires driving those same services.
Scoped layers cannot express that — the global-runtime coupling is the thing
under test — and it is the same integration pattern this entry already
sanctioned for the detach fence. The reason string records that.
* refactor(cli): run remote sessions in one process with safe per-session exit
Consolidate remote session handling into a single CLI process instead of
spawning one process per remote-created session (addresses the PR review):
- restore in-process create_session (accepts an absent sessionId and targets
the connection directory); remove the session spawner, the
KILO_REMOTE_ATTACH_SESSION attach-on-boot path, the child-advertisement gate,
and their tests
- retain instance advertisement and fire one immediate out-of-band heartbeat on
(re)connect when advertising, so a headless `kilo remote` host is discoverable
without delay
Make /exit (wire command exit_cli, unchanged for compatibility) detach only the
target session instead of terminating the CLI:
- AttachedState.detach with a presence-suppression tombstone; detach also clears
the target's SessionStatus so the negative-containment heartbeat fence resolves
deterministically for busy/retry/offline sessions
- exit_cli handler verifies ownership, cancels the active prompt, detaches and
awaits the detach heartbeat, then ACKs; the interactive RemoteExit callback is
invoked only after the ACK when the last owned session exits; a headless
`kilo remote` host stays alive and advertising at zero sessions
- add an optional canExitSession boolean to the list_commands v1 catalog
(always true, independent of exitAvailable) so clients can detect safe
session-exit semantics
History and stored sessions are preserved on exit.
* fix(cli): break module-load cycle in remote session prompt-cancel
The K1 in-process exit_cli seam added a static `import { SessionPrompt }`
to kilo-sessions.ts. @/session/prompt evaluates KiloSessionPrompt at module
load, so the new static edge raced that init and left the namespace in TDZ,
crashing unrelated test files with 'undefined is not an object (evaluating
KiloSessionPrompt.shouldAskPlanFollowup)'. Defer to a dynamic import at the
single call site, mirroring remote-command.ts.
* fix(cli): correct AttachedState announce/detach concurrency and rollback
Address review findings on the shared-process session lifecycle:
- announce/detach no longer join the OPPOSITE in-flight operation. Joining
detach's negative-containment fence made announce resolve success for a
detached id (and vice versa: detach joined announce and resolved success
while still attached, which exit_cli treats as license to ACK/close). Each
path now joins only a same-kind in-flight op and, when the opposite op is
in flight, awaits it to settle and then performs the real work.
- Failed-detach rollback now releases the suppression tombstone, so a
still-attached session is not dropped by the next setPresence (the tombstone
loop would otherwise remove the still-present id and never clear).
- Both catch/rollback branches now honor the lifecycle generation guard
(mirroring the success path); a stale in-flight op that rejects after
reset() no longer mutates the new lifecycle's presence/pending/suppressed
sets (reset clears the same Set instances).
Adds regression tests for each fix, plus AC6f covering the remote-ws
detachSessionId negative-containment waiter.
Confirm process-tree exit after SIGKILL on the non-Windows kill path, and
escalate SIGTERM to SIGKILL on the shutdown-hook (no-wait) path so a
SIGTERM-ignoring tree is not orphaned on JVM exit. Deflake the process-tree
kill test by capturing both children deterministically and add coverage for
the no-wait escalation.
Make JetBrains IDE app-close teardown non-blocking: send SIGTERM and return,
letting the JVM shutdown hook confirm exit, so quitting the IDE no longer
stalls the EDT for up to several seconds. Stop plugin-unload teardown from
waiting on the lifecycle mutex behind an in-flight download.
Add a parent-death watchdog to `kilo serve`: the VS Code extension and
JetBrains plugin pass their PID via KILO_PARENT_PID and the server exits when
that process disappears, covering hard client kills where no signal or
shutdown hook runs.
Reboots the per-directory instance, reloading config, skills, agents,
commands, and MCP prompts changed on disk without restarting the
server. Sessions and history are preserved; only the per-directory
instance caches are torn down and rebuilt.
Server: POST /instance/reload wraps the existing atomic
InstanceStore.reload path (the same one project.git.init uses). The
rebuild completes before the 200 response, so clients can refetch with
no race. Returns 409 ConflictError while a session is actively
running. Emits the existing server.instance.disposed SSE event, which
the TUI and extension already use to auto-refetch.
CLI: /reload palette command calls the endpoint; the TUI already
bootstraps on server.instance.disposed.
Extension: /reload slash command, a reload button in the task header
and settings panel, and a Kilo Code: Reload Config and Skills command
palette entry. The handler clears the command cache and reuses
reloadAfterAuthChange to re-fetch config, providers, agents, skills,
and commands. Reload targets the current session's directory so Agent
Manager worktree sessions reload their own worktree instance rather
than the workspace root.
SDK: regenerate so client.instance.reload is available to external
integrations.
* feat(memory): opt-in project memory — capture, recall, CLI + TUI integration
Add project memory: the standalone @kilocode/kilo-memory effect layer plus the
opencode CLI/server/TUI integration. Memory is disabled by default, so it is a
no-op until enabled (no behavior change when off).
Capture (turn-close consolidation): per-op parse salvage, secret redaction that
skips the offending op instead of aborting the batch, supersede-only auto-updates
(never model-driven deletes), correction-aware echo handling, non-LLM fallback
digests on interrupted/error turns, a shared interval throttle with idle-flush.
Recall + injection: keyword tokenizer with camelCase/compound splitting, light
stemming, and an English-first stopword filter (Unicode-aware; non-English falls
back to plain token-overlap), a live relevance floor, a budget-reserved startup
index, a session-digest catalog, and per-session prompt-cache pinning of the
injected memory block.
Surfaces: kilo_memory_save / kilo_memory_recall tools, the memory HTTP API
(contract schemas live in the package), and a status-focused TUI sidebar showing
auto-save, loaded context, and active recall, plus the /memory dialog.
* fix(memory): address PR review feedback
- C1: bump @kilocode/kilo-memory in the changeset
- C2: redact secrets before they hit the audit log (skip + salvage paths);
redact before truncating in salvageTyped so a secret straddling the
500-char cap can't leak an unmatched fragment; opText -> salvageText
- C3: de-abbreviate savedOperations, "changes" wording, ops.ts -> operations.ts
- C4: log.warn on the remaining silent-catch fallbacks (turn diff, memory
context injection, tool-visibility check)
- C5: relocate memory storage from ~/.kilo to Global.Path.data, delete the
now-dead needsDependencyInstall guard, add /memory status (root path) and
/memory edit ($VISUAL/$EDITOR + auto-rebuild)
- C6: replace the hardcoded English stopword list with corpus-derived
ubiquitous-term filtering (df across the user's own entries) and the
English suffix stemmer with suffix-tolerant term matching, so recall
noise-filtering works in any language
- C8: delete the CORRECTION_INTENT English regex; echo turns now run typed
capture (digest stays echo-gated), bounded by the interval throttle, with
the typed prompt as the language-agnostic content filter
- C9: exclude generated paths (dist/build/coverage/*.gen.*/*.map/snapshots)
from the durable-diff churn fallback so generated churn can't burn a
consolidation call
- C11: fix duplicated assert in httpapi-memory test; assert the error body
- kilo-code-bot batch: clause-boundary regex fix, byte-safe catalog
truncation, max-length guards on remember/correct/forget payloads (text,
query, key, sessionID), trim consistency in reconcile, param-shadowing
rename, missing doc entry for kilo_memory_recall, dead-code removal,
dialog UI fixes, memoryEnabledCache eviction bound, dedicated Configure
schema, recall permission renderer, covered-session pointer cap, redact
chat transcript before the consolidation model call, split configProtected
metadata from disableAlways so memory-save prompts don't show config-file
copy, drop unused MemoryService.layer provide from tool registry
- redact colon-separated low-entropy secrets too (password: hunterx),
accepting the prose false-positive tradeoff (secret: enabled) in favor of
not missing a real secret
- rename lastConsolidatedAt -> lastTypedConsolidationAt to make its narrow
scope (typed-consolidation throttle clock) explicit; regen openapi/SDK
- drop now-dead home/config fields from MemoryPaths.Host after the data-dir
relocation; add Process.splitCommand for quoted $EDITOR/$VISUAL paths with
spaces, used by /memory edit and the pre-existing Editor.open utility
* refactor(memory): shared client helpers, capture hardening, /memory UX rework
- extract client-side derivations into kilo-memory so both frontends share
one implementation: MemoryDecisions.summarize (decision-log summary),
MemoryAutosaveStatus.summarize (autosave-status semantics), and
MemoryMarkerMeta (marker wire contract encode/decode)
- match exact-key upserts via the canonical stored id (slugged key,
normalized section) so a re-emitted spaced/uppercase key updates the
entry instead of falling to fuzzy dedupe
- salvageTyped throws on valid JSON without an operations array so the
caller's fallback path records a parse error instead of a silent
zero-op success
- rename memory tool metadata files -> sources (stripPartMetadata rewrites
tool-part metadata.files assuming apply_patch records, mangling string[])
- read state instead of status for tool enabled checks; dedupe TUI helpers
(errorMessage, shared route(), Locale.number, relativeTime)
- /memory UX: bare /memory opens a help modal driven by a structured
command catalog in kilo-memory; /memory on|off become the canonical
toggle verbs (enable/disable kept as quiet aliases); /memory status opens
a clean overview dialog (root path, autosave, startup context, source
counts, index size) instead of a toast; /memory show is the single full
audit view (inspect removed)
Move the vim modal editing engine and prompt integration out of shared
upstream files and into src/kilocode/ mirrors per the Fork Isolation Rule:
- src/kilocode/cli/cmd/tui/component/prompt/vim.ts (engine, moved)
- src/kilocode/cli/cmd/tui/component/prompt/index.tsx (new mirror with
useVim, VimModeIndicator, vimToggleCommand)
- test/kilocode/cli/cmd/tui/prompt/vim.test.ts (moved)
The shared prompt/index.tsx now calls into the mirror behind minimal
kilocode_change markers instead of inlining ~200 lines of vim logic.
Adopt the file-scoping approach from #11883: untrusted project config may still read {file:...} as long as the target stays inside the project root (absolute paths, ../ traversal, and symlink escapes are rejected via realpath). Keep {env:} fully blocked in project config (no safe scoped form). Make the /proc/self/fd guard cross-platform. Thread fileScope through config.ts, agent.ts, tui.ts, and overlay.ts. Update docs and changeset.