* feat(opencode): remote create_session fields, rename adoption, title sync
Extend create_session wire with optional agent/model/orgId (strict v1,
old-CLI degrade via client retry); claim org via session metadata
(metadata > KILO_ORG_ID > auth); adopt system session.renamed via
setTitle with consume-on-failure adoption marks; POST generation-aware
title changes through readiness (auto-titles marked by ensureTitle,
same-title Updated consumes pending adoptions).
* test(opencode): prove cancel→reprompt reaches idle; lock exit survivor
Item 14 CLI prove-it at SessionPrompt level: cancel-when-idle,
mid-stream, mid-tool, queued follow-up (deterministic queue wait), and
abortIntakes all settle to idle and reprompt completes — no production
hang found, no src change. Item 8: lock survivor session send_message
after sibling exit_cli.
* test(opencode): drop AppRuntime spy from create_session default test
Satisfies check-opencode-promise-facades while still proving the
production default forwards {agent, model, metadata} into
Session.Service.create.
* fix(opencode): bound rename marks, wire title report path, harden title tests
Kilobot review on #12704: adoption/auto-title maps now carry timestamps,
prune on write (60s TTL), and clear on Session.Event.Deleted (exported
clear/clearAll); the Updated watcher calls the interface
reportSessionTitle and fullSync passes preloaded info into meta();
ensureTitle's Kilo logic lives in kilocode/session/prompt.ts behind one
kilocode_change call site; title tests poll instead of sleeping and lock
mark-before-write plus clear-on-failure for real; meta() get-failure
org fallback covered via the _metaForTests seam.
* fix(kilo-sessions): mark bookkeeping before ingest sync, AppRuntime, test cleanup
Kilobot round 2 on #12704: consume rename/auto-title marks before the
ingest.sync network hop so the 60s TTL spans only the in-process hop;
call reportSessionTitle via AppRuntime.runPromise; auth cleanup back
under Effect.ensuring; restore the upstream blank line in prompt.ts so
the fork diff is only the kilocode_change call site.
* fix(kilo-sessions): keep title report self-healing if ingest.sync fails
Advance knownTitles only after successful sync; restore consumed rename/
auto-title marks on failure so the next Updated can re-POST. IIFE keeps
const-style outcome derivation.
* fix(kilo-sessions): optimistic knownTitles with full title-path rollback
Advance knownTitles before the network hop so concurrent Updated handlers
see sameTitle and cannot POST the same title with a wrong generated flag.
Restore prev + consumed marks when ingest.sync throws or reportSessionTitle
returns not-ok, so the next Updated retries the full self-healing path.
* style(kilo-sessions): prettier title Updated handler
* fix(kilo-sessions): preserve newer title state
* refactor(kilo-sessions): simplify title reporting tests
* fix(kilo-sessions): report unseeded title updates
* fix(kilo-sessions): consume unseeded title marks
* test(kilo-sessions): cover unseeded title marks
* test(kilo-sessions): unique ids for unseeded title tests
Thread a distinct session id through unseededMockSessionLayer so
session_share Storage records do not couple the three unseeded cases.
Generation was failing. Upstream added a guard in this range that rejects
duplicate session event variants, and Kilo's tool-content codec on the shared
event schema was tripping it, so openapi.json and the SDK were stale and a temp
packages/sdk/js/openapi.json got committed by accident.
Move the codec off the event schema into core/src/kilocode/event-storage.ts,
keyed by event type and applied only at the SQL boundary. Session events are on
the wire now (SessionEvent.Durable backs /api/session/{id}/history), so a
transform there forked the generated API. Shipped readers still parse old rows
and the wire contract goes back to upstream's. Drops the consumer-side
normalizers in the TUI and sync-v2 that existed only to undo the widening.
Also: restore the Kilo HttpApi title and a few branding strings, balance five
kilocode_change markers, drop a duplicate TUI palette entry, and teach
check-model-tool-network about the LayerNode wiring that replaced
Layer.provide(ToolNetwork.httpLayer).
* feat(cli): add notify_user push-notification tool
Add a notify_user tool that lets an agent send a push notification to the
user's phone for explicitly requested pings and significant mid-run
milestones. It emits a single agent_notification item over the session's
existing authenticated ingest channel via a new result-bearing
KiloSessions.sendAgentNotification operation with a bounded readiness wait,
returns friendly failure text when the session is not connected, and never
prompts for permission. Provide KiloSessions to the tool-registry graph via
a lazy layer node to satisfy the tool's dependency without eager init.
* chore: retrigger CI and review after GitHub Actions outage
* fix(cli): store the real bootstrap promise for coalescing
trackBootstrap stored the promise from inside the Promise executor, before
the variable was assigned, so bootstrapInflight held undefined and concurrent
create()/sendAgentNotification callers could not coalesce onto the in-flight
bootstrap. Build the outcome promise as a synchronous expression and register
it before any await.
* fix(cli): hide notification tool when remote is disabled
The two compaction interruption tests ("stops quickly when aborted during
retry backoff" and "does not leave a summary assistant when aborted before
processor setup") intermittently fail on the Windows CI shard with an
uncaught TimeoutError.
Root cause: Effect 4.x changed Effect.timeout to throw a TimeoutError on
the error channel instead of returning Option (as in Effect 3.x). The
tests wrapped Deferred.await(ready) and Fiber.await(fiber) in
Effect.timeout as a guard against the fiber never reaching the trigger
state, but on loaded Windows runners the fiber can take longer than the
1 second / 250 millis deadlines to reach that state. When the deadline
expired the TimeoutError propagated uncaught and failed the test, even
though the interrupt assertions would still hold.
Swallow the TimeoutError from the ready wait so the test proceeds to
interrupt the fiber regardless of whether the trigger fired in time, and
drop the inner Fiber.await timeout since Fiber.interrupt already waits for
termination. The assertions verify the interrupt exit either way.
PR #12158 enforced read permissions for file mentions by routing
directory attachments through the permission resolver with
denyDirectory: true. That flag is set for every prompt-mention
attachment, so the read tool denied all directory listings, including
directories inside the current workspace.
Only deny directory attachments whose canonical path changed after
permission approval. A symlink swap during the permission wait moves
the resolved target, so the approved permission no longer applies and
the listing is denied. Unchanged in-workspace directories are listed
as before.
Fixes#12241
* feat: add AI image generation tool
Port the legacy generate_image tool to the opencode-based CLI as a
Kilo-owned tool gated by experimental.image_generation config flag.
- New generate_image tool with prompt/path/image/model params
- Routes through Kilo Gateway (zero-config) or BYO OpenRouter key
- Supports text-to-image generation and image editing
- Dynamic model discovery via GET /kilo/models/images endpoint
- VS Code settings toggle + live model dropdown in Experimental tab
- Writes image to disk and returns inline FilePart attachment
- Fallback model catalog for offline resilience
* refactor: change default image model to openrouter/auto
* fix: address bot review feedback
- Remove unused fetchKiloImageModels import in tool
- Normalize jpg→jpeg MIME in parser, input image, and attachment
- Replace mismatched extensions in ensureExtension (not just append)
- Add assertExternalDirectoryEffect for output path traversal guard
- Map unauthorized errors to 401 (not 400) in image models handler
- Keep last known model list on fetch failure (don't overwrite with empty)
- Fix tool description (remove false web search claim, fix grammar)
- Remove duplicated provider resolver tests
* fix: add retry for image models request to handle backend startup race
* fix(image-generation): address kilo bot review comments
- ensureExtension replaces mismatched image extensions instead of
appending (photo.jpg + PNG -> photo.png, not photo.jpg.png)
- Gateway /models/images normalizes errors to 400/401 matching every
other gateway route (was leaking undeclared upstream statuses)
- Add 401 response to openapi.json + SDK types for /kilo/models/images
to match the gateway's errors(400, 401) declaration
* fix(gateway): align /models/images error handling with other gateway routes
* refactor: switch image generation to effect HttpClient
* test: cover kilo models images endpoint in httpapi exercise scenarios
* Exclude POST-only and parameterized API endpoints from link checker
* Add ImageModelsProvider to agent manager context tree
* chore(deps): bump @openrouter/ai-sdk-provider to 2.10.0
Switches imageModel() to OpenRouter's POST /api/v1/images endpoint for
proper image usage/billing and image-specific params.
* fix: address image generation PR review feedback
- revert @openrouter/ai-sdk-provider 2.9.0->2.10.0 bump (image tool uses raw HTTP, not the SDK)
- translate image generation settings strings across all locales
- use central KILO_OPENROUTER_BASE instead of hardcoded URL fallback
- remove completed plan file
* chore: refresh source-links.md after URL refactor
* 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)
Adds .trim() before .toLowerCase() so tool names like " bash" are
repaired to "bash" instead of falling through to the invalid handler.
Adds an integration test that sends a space-padded tool name through
the full AI SDK stream path and asserts the correct tool executes.
Fixes#10140
Co-authored-by: Johnny Amancio <johnnyeric@gmail.com>