CHANGELOG · newest first
What’s new
Every Lisa release, newest first. Grab the latest from theinstall page, or browseGitHub Releases ↗. The Mac app checks here too — Lisa ▸ About ▸ Check for Updates.
# LISA v0.27.1 — cloud inference recovery LISA Cloud can use Gemini 2.5 Flash with the existing free allowance. Previously this model fell into the unknown-model premium tier, which would reject an account with no purchased credits even when its free allowance remained. - Add exact-model Gemini Flash pricing and bump the pricing audit version. Flash-Lite, image, audio, preview, and unknown variants keep their previous conservative handling. - Meter thinking tokens as output and cached prompt tokens separately, avoiding omitted reasoning cost and duplicate input charges. - Use validated Gemini function calling to reduce malformed or empty tool completions; surface malformed calls as an error instead of silent success. - Accept `GEMINI_API_KEY` in the deployment helper, including Secret Manager mode. Rates were verified against [Google's pricing](https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-flash), and usage semantics against [UsageMetadata](https://ai.google.dev/api/generate-content#UsageMetadata). No provider credentials are included in this release. The iOS binary does not change; its existing disclosure flow reads the active AI recipients from the server. This release does not imply App Store submission or approval. See [the execution record](https://github.com/oratis/LISA/blob/main/docs/EXECUTION_PERSONAL_ASSISTANT_2026-09-27.md) for the live rollout and remaining review gates. Deployment verified: LISA Cloud serves Gemini 2.5 Flash on revision `lisa-cloud-00027-qal`; actual cloud-domain login, chat and tool roundtrips passed. npm, the signed and notarized Mac DMG, and Homebrew are published. Follow-up iOS native fixes are tracked separately in [PR #392](https://github.com/oratis/LISA/pull/392). App Store submission is still pending.
# LISA v0.27.0 — personal assistant and reliable iOS connections Lisa Pocket now opens with a cloud sign-in path and an optional Mac connection. Home offers editable prompts for daily planning, writing, and breaking down a goal. Mac-only agent and integration controls explain their requirements in cloud mode. - Cloud and Mac configurations use separate Keychain credentials. Existing connections migrate, switching resets private view state and widgets, and failed secure storage does not report a saved connection. - Chat identifies configured AI recipients and asks for consent before sending. The server's public auth configuration provides recipient names without exposing credentials or URL query strings. The updated mobile app requires this disclosure response; update the connected server before using chat. - New StoreKit purchases bind to a LISA account, preventing a mode/account switch from crediting the wrong account. Pending purchases are retried after cloud sign-in, and receipts are not forwarded to the paired Mac. Legacy receipts remain supported by existing deduplication. - Cloud sign-in requires HTTPS. Local network pairing stays available. Cloud and Mac histories remain separate; automatic memory sync is not part of this release. - English and Chinese privacy pages describe cloud processing accurately, with dedicated support pages and revised store/review copy. - The missing-agent regression test now isolates PATH instead of accidentally launching a real installed Codex. Research and acceptance tracking: [Muse research](https://github.com/oratis/LISA/blob/main/docs/RESEARCH_MUSE_2026-09-27.md), [implementation plan](https://github.com/oratis/LISA/blob/main/docs/PLAN_PERSONAL_ASSISTANT_2026-09-27.md). App Store availability requires a separate Apple review. A package release or TestFlight upload does not establish approval. Hosted email/integration/push capabilities remain constrained by the server's tenant boundaries. Hosted-service update — 2026-09-27: the GLM balance outage observed at release is resolved by switching LISA Cloud to Gemini 2.5 Flash. Production login, real streamed replies, a knowledge-base tool round trip and free-allowance charging passed. See [recovery PR #391](https://github.com/oratis/LISA/pull/391). iOS 1.2 build 1790520396 remains prepared for submission; this service recovery does not mean App Review submission or approval.
# Lisa v0.26.1 **The v0.26.0 DMG.** v0.26.0 is the release that makes Lisa.app self-contained, and its Mac job failed before producing a disk image — so it shipped the CLI bundles with the one asset it was about missing. Same contents, plus the one-character fix that lets the DMG build. ## 🔧 `embed-runtime.sh` under a UTF-8 locale The release job died at `embed-runtime.sh: line 124: tarball<U+2026>: unbound variable`. Under a UTF-8 locale, bash 3.2 — still `/bin/bash` on macOS, GitHub runners included — swallows the bytes of a following multibyte character into an identifier, so `"$tarball…"` expands a variable named `tarball` *plus the ellipsis*, which is unset, and `set -u` aborts: ```sh LC_ALL=en_US.UTF-8 bash -uc 'v=x; echo "$v…"' # -> v…: unbound variable LC_ALL=C bash -uc 'v=x; echo "$v…"' # -> fine ``` Note the direction — the **UTF-8** locale is the one that breaks. Every local run and every `bash 5` passes, which is why this only ever surfaced on the tag-triggered release job, running for the first time now that there is something to embed. Braced, and the tree swept for the same shape: it was the only occurrence. Verified end to end under `LC_ALL=en_US.UTF-8`, the exact locale CI uses. See [v0.26.0's notes](RELEASE_v0.26.0.md) for what actually changed.
# Lisa v0.26.0
**Lisa.app now brings its own backend.** Downloading the DMG used to get you an
app that couldn't do anything: it looked for `lisa serve --web` on the login
shell's PATH, so you had to install Node and `npm i -g @oratis/lisa` first — the
disk image's own README said so. The app now carries a full backend and its own
Node runtime, and starts them the first time you open it. Alongside that: the
In-App Purchases App Review couldn't find are reachable again, and the
"backend offline" banner stopped covering the window it was describing.
## 🖥 A DMG that works on a Mac with nothing installed
`packaging/mac-client/embed-runtime.sh` stages the runtime into the bundle at
build time — `Contents/Resources/backend/{dist,node_modules,package.json}` plus
`Contents/Resources/node-runtime/bin/node`. Verified the way it matters, with
`env -i HOME=<empty> PATH=/usr/bin:/bin`: the embedded Node runs the embedded
backend and answers HTTP 200.
Packaging decisions worth knowing:
- **`--omit=optional`** drops node-pty, the only native module in the tree, so
the embedded backend stays pure JS — bound to no Node ABI and needing no
`disable-library-validation` under the hardened runtime. PTY agents degrade to
the 503 they already returned without node-pty.
- The Node binary is **checksum-pinned in the repo**, not just against the
`SHASUMS256.txt` fetched beside it: that file comes from the same host over
the same connection as the tarball, so it cannot vouch for it. The pin caught a
truncated download during testing and refused to embed it.
- It is `strip -x`'d (22MB per arch), lipo'd universal, and **re-signed** —
lipo drops nodejs.org's signature and arm64 refuses an unsigned Mach-O.
- Start order is `serve-command.txt` → embedded → `lisa` on PATH. **The override
still wins**: someone who wrote that file wants their own tree to run, and
silently substituting the bundled copy would make their edits vanish.
Autostart also stopped looking broken. It always ran; the problem was that a
backend which died instantly left the app silent for 20 seconds and then said
`timeout`. `BackendController` now reads the backend's own fatal line out of
`~/.lisa/backend.log` and names which start path produced it.
App 339MB, DMG 156MB. `LISA_EMBED_ARCHS=arm64` saves ~90MB;
`LISA_SKIP_EMBED=1` skips the embed for Swift-only iteration.
## 💳 The In-App Purchases are findable again
App Review rejected Lisa Pocket 1.1 under Guideline 2.1(b) — "we cannot locate
the In-App Purchases". It was our own conditional UI: the only route to the
credit packs sat inside `if let q = quota, q.available, window > 0`, so a
`/api/billing/quota` that timed out, 401'd or 503'd left the app with **no** way
to reach them at all.
- "Add credits…" now renders for any signed-in account; the quota fetch only
decides whether the allowance rows appear above it.
- A turn refused for lack of credits (402) offers the packs **in the chat
bubble**, a second route that needs no Settings trip.
- An empty StoreKit response is treated as a **failure**, not a loaded empty
list. StoreKit answers with an empty array rather than an error when products
aren't available yet, and a sheet spinning on "Loading packs…" forever is
exactly what an unreachable IAP looks like from the outside.
Server-side, App Review buys in Apple's sandbox while the cloud edition rejects
every non-Production JWS (anti-minting). Named review accounts are now
allowlisted — and those sandbox credits are marked in the ledger and capped, so
a credential that by design gets typed into App Store Connect cannot mint
without limit.
## 🔔 The offline banner became a bottom toast
The safety-net banner was a wide slab pinned at `top: 12px`, styled from an
inline string. In the Mac client it landed squarely on the window's title bar and
the whole top function bar — the first thing you saw when the backend was down
was a message covering the UI it was describing.
It is now a compact bottom-anchored toast that draws from the theme tokens and
tracks Nebula ↔ Calm, rides above the composer (re-measured per call, so a
textarea that grew while you typed can't cover it), and carries an optional
copy-on-click command chip. The first two startup retries say "Waking Lisa up…"
in a calm tone — the desktop app is normally still starting the backend, and a
red error at t+4s is a lie — before escalating.# Lisa v0.25.0
**The review-and-optimization release.** Two full-project reviews of v0.24.0 —
one UX, one technical — turned 24 findings into nine merged PRs (#365, #368–#376).
Two P0s are gone: the phone layout that collapsed the main pane to 75px, and the
first-run dead end that trapped anyone who typed a wrong API key. Underneath
that: hosted-edition boundary fixes, a durable billing outbox, a self-watchdog
for the backend, and the engineering gates — lint, formatter, coverage floors,
dependency bot, a real CI matrix, an offline browser smoke — that 60k lines of
TypeScript had been running without. Plans in
[PROJECT_REVIEW_UX_v0.24.0.md](PROJECT_REVIEW_UX_v0.24.0.md) and
[PROJECT_REVIEW_TECH_v0.24.0.md](PROJECT_REVIEW_TECH_v0.24.0.md).
## ⚠️ Node 22.19 is now the minimum
`engines.node` moved from `>=20.0.0` to `>=22.19.0`. This is a real floor, not
housekeeping: undici — a production dependency — calls `worker_threads` APIs
added in Node 22.10, so `>=20` was a promise that installed cleanly and then
failed at runtime. `lisa doctor`, both READMEs, both install pages, the Mac
install wizard and `lisa upgrade` now all check and state the same number,
major *and* minor. On Node 20, upgrade before installing.
## 📱 The phone layout works again (#375)
Since v0.24.0 made the right panel collapse by default, `body.rb-collapsed
.frame` outranked the ≤720px media query, so a 375px viewport got a two-column
`300px 75px` grid: the main pane was **75px wide** and the send button sat off
screen. The collapse rule is now scoped to ≥721px, `#viewChat` pins its column
to `minmax(0,1fr)`, and the function bar became a horizontal scroller instead of
forcing the chat column to its ~680px min-content. Measured at 375×812, both
collapsed and expanded: main pane = viewport width, send button on screen, no
horizontal overflow anywhere. Two Playwright specs now hold that.
## 🔑 First run survives a wrong key (#375)
The birth ritual retried on 401, printed the provider's raw JSON, and made
ENTER reload the page to run again with the same bad key — with no way back to
the key form. Authentication errors are now plain language with a **Change key**
button that returns to the gate, saving a new key restarts the birth without a
reload, and the run is cancellable and deadline-bounded. The gate is also a
provider picker now: 16 providers including DeepSeek, GLM, Moonshot and Qwen, so
the web surface finally matches what the site advertises.
## 🔒 Hosted-edition boundaries (#365, #373)
- `/api/consent/*` and `/api/push/*` reached the cloud deny-list. Both stores are
machine-wide by design, so any signed-in tenant could read every tenant's
ntfy topics and APNs tokens, unregister their devices, or switch the mail
digest off for the whole deployment.
- **Non-canonical paths are rejected before routing.** The deny-list matched the
normalized pathname while every handler matched the raw `req.url`, so
`/api/agents/recap/%2e%2e/%2e%2e/%2e%2e` normalized to `/` — not denied — and
still ran its handler. Failing closed on the path shape covers routes added
later too.
- **`/health` no longer publishes usage metrics to the public internet.** It is
pre-gate on purpose (an operator has to tell "slow" from "down" without
credentials), but in the hosted edition an unauthenticated caller now gets
health only — not tenant, session and in-flight-turn counts, heap, RSS or
uptime. Authenticated callers still get the full payload; `/healthz` is
unchanged.
## ⚙️ The backend notices when it wedges (#373)
Two 5-second stalls on the daily driver during review, and `/health` could only
say `{ok:true}` — enough for launchd to restart a crash, useless against a hang.
`/health` now reports version, uptime, event-loop lag p50/p99/max, heap, RSS and
live counters; a self-watchdog exits after 60s over the lag threshold so the
supervisor can restart it; logs rotate at 10MB × 5. The stall itself was
whole-file reads — session summaries, message pages and the claude-code watcher
are streaming or tail reads now, with an mtime-keyed session index and ETags.
## 💳 Usage settlement is durable (#374)
Billing had an unrecoverable window: the provider had charged, but a failed
balance write or a crash between two writes left neither a debit nor a way to
recover. Every metered turn now appends an immutable usage event **before**
settling; a failed append fails the settlement closed. The balance carries the
applied event ids as its own idempotency key inside the same atomic update, so a
replay cannot double-charge, and a reconciler sweeps pending and failed events
every 15 minutes — escalating to `needs_human` rather than replaying anything old
enough to have aged out of that ring. `lisa billing reconcile` is the operator's
view.
## 🖥 CLI polish (#372)
Persistent REPL history (`~/.lisa/history`, mode 0600, deduped, capped),
one-line tool-call rendering, a spinner that never touches stdout, and thinking
shown as a marker with its content structurally unreachable. New:
`lisa doctor --probe` reads a running backend's `/health` and exits non-zero when
it can't; `lisa upgrade` detects Homebrew / npm-global / source, upgrades, kicks
the LaunchAgent onto the new code, and — when npm fails — hands over the same
no-sudo prefix fix the Mac wizard offers. Proxy logs are quiet unless
`--verbose`, and user-facing paths abbreviate to `~`.
## 🍎 Mac and iOS (#371)
macOS builds with zero warnings and `-warnings-as-errors`, so Swift 6 strict
concurrency can't regress in silently. Lisa.app gained a backend install wizard —
it detects Node and the CLI through a login shell, installs in-panel, and names
the fix when npm hits a permissions error — instead of the README telling you to
go run `npm install -g` yourself. On iOS: accessibility labels throughout (status
never carried by colour alone), a push transport picker that tells the truth
about ntfy vs APNs delivery, live PTY output over SSE, and dispatch rows that
distinguish a clean exit from a crash.
## 📦 Assets and docs (#369, #370)
125 PNGs losslessly re-encoded — 39.05 MB → 32.18 MB, every file verified
pixel-identical — plus real PWA icons (192, 512, a separate maskable variant and
a 180×180 apple-touch-icon) actually wired into the manifest, the page head and
the service worker precache. README dropped from 767 lines to 85 with the install
command inside the first 20; everything else moved to `docs/GUIDE.md`, mirrored
in Chinese, with zero-dependency link and EN/ZH drift checkers in CI.
## 🧰 Engineering gates (#376)
ESLint (flat config, type-aware rules) with the 69 existing violations parked as
a warn baseline and new code gated at error level. Prettier, scoped to files
changed against main. c8 coverage floors on billing, accounts, OTP, session auth,
capabilities and the soul store. Dependabot plus `npm audit` at high. CI now runs
Node 22 and 24 and triggers the website, macOS and iOS builds on their own paths
instead of leaving them to the release pipeline. And a fully offline Playwright
smoke — no model calls, a stubbed provider — whose 27 specs include the two P0s
above, so neither can come back quietly.# Lisa v0.24.0
**The harness-alignment release.** Three changes that are less about new
surface than about removing things that were quietly wedged: tools no longer
hardcode *where* they execute, session logs no longer omit the one input that
actually shapes the model's behaviour, and Lisa finally reads the instruction
file the rest of the ecosystem already agreed on. Design + gap analysis in
[docs/PLAN_HARNESS_ALIGNMENT_v1.0.md](PLAN_HARNESS_ALIGNMENT_v1.0.md); shipped as
PRs #356–#360.
## 🔌 A capability seam for fs / shell (#358)
Every tool used to `import fs from "node:fs"` and `spawn` directly, which
turned "where does this tool run?" into a fact hardcoded across fourteen call
sites. Three roadmap items were stuck behind it:
- **Dispatch** couldn't move execution to a container or a remote host without
a second implementation of every tool.
- **Cloud multi-tenancy** could only delete `read`/`write`/`bash` from the
registry wholesale (`cloudSafeSubset`) — there was no way to *bound* them.
Deny-by-omission is a patch; a bounded execution world is the fix.
- **Tests** could only run against a real disk.
Now split three ways — interface (`capabilities/types.ts`), providers
(`local.ts` / `memory.ts`), consumers (the tools). Swapping the provider
swaps the execution world for every consumer at once, with **no tool code
changed**.
- `ToolContext.caps?: Capabilities` is optional; unset means local disk and
shell, so all existing `ToolContext` construction sites compile untouched
with zero behaviour change.
- Tools always go through `capsOf(ctx)` rather than reading `ctx.caps` — the
local fallback lives in exactly one place.
- `resolvePath` moved *into* the fs seam instead of each tool calling
`path.resolve` itself: path resolution is where a policy gets to say no.
## 📜 The system prompt is now in the session log (#357)
Session JSONL recorded `tool_use` / `tool_result` from the start — but **not
one byte of the system prompt**, which is built dynamically from soul + memory
+ skills + mood + KB and hot-reloads mid-session. Take any historical session
and you could not reconstruct the persona the model actually saw.
That isn't tidiness. ROADMAP 1.0 criterion 4 asks Reve for reproducible
drift / coherence metrics, and drift *is* "how her self-description changes
over time". With no self-description in the log, the metric could only be
measured live, once — never recomputed offline, never swapped for a different
metric, never ablated.
- Session format → **v2**, with `prompt` entries `{ts, fingerprint, text,
reason}`. The fingerprint is a content hash and identical consecutive
prompts aren't rewritten, so "the prompt in effect at entry N" is simply the
nearest preceding `prompt` entry. A long conversation that never
self-modifies costs one entry.
- `runAgent` gained `onPromptPersist`, fired **before every provider call** —
the moment the prompt becomes visible to the model, matching dsh's
"model-visible ⟺ recorded" placement. Writing before the call means a turn
that crashes mid-flight still leaves behind what was asked. A persistence
failure is logged and never takes down the live turn.
## 🤝 AGENTS.md / CLAUDE.md, and project-level skills (#360)
The ecosystem converged on a convention Lisa didn't read: an `AGENTS.md` (or
Claude Code's `CLAUDE.md`) at the repo root that every agent picks up. Claude
Code, Codex, Cursor and dsh all honour it. Reading it is the cheapest
compatibility win available — instructions a user already wrote keep working
after they move to Lisa.
**The chain**, outermost first, most specific last:
`~/.lisa/AGENTS.md` → `<repo>/AGENTS.md` → `<repo>/CLAUDE.md` → … down to cwd.
- **Deduped by content.** `CLAUDE.md` is frequently a symlink or copy of
`AGENTS.md`; reading both would pay for the same paragraphs twice, every turn.
- **32 KB total budget.** This goes into the system prompt and is paid every
turn — one monorepo's `AGENTS.md` doesn't get to crowd out her soul. Past the
budget it truncates and says so in the prompt.
- **Framed as the project's say, not the user's orders.** These files get read
purely by virtue of sitting in a directory, so each block is labelled with
its source, and the prompt states plainly that they don't override the
constitution — if a file asks her to abandon her own principles she ignores
it and says so. **Cloning a hostile repo does not confer the right to give
Lisa orders.**
**Project-level skills** land alongside: `<repo>/.lisa/skills` is read in
addition to `~/.lisa/skills` (rank 100), so a repo can ship its own.
## 📱 Also
- Lisa Pocket bumped to **1.2** (iOS).
- DeepSeek Harness research folded in, with the LISA alignment plan v1.0 ([RESEARCH_DEEPSEEK_HARNESS.md](RESEARCH_DEEPSEEK_HARNESS.md), #356).
---
**Verification:** `npm run typecheck` clean, `npm run build` clean,
**1601 tests pass / 0 fail**.# Lisa v0.23.0 **The session-shell release.** The web workbench is rebuilt around parallel sessions: Lisa's own chats are now sessions you can run side by side, observed coding agents (Claude Code / Codex / Aider) sit in the same tree as equals, and the whole UI moved to a three-column shell with a switchable dark/light theme. Design + debates in [docs/PLAN_UI_SESSION_SHELL_v1.0.md](PLAN_UI_SESSION_SHELL_v1.0.md) and [v1.1](PLAN_UI_SESSION_SHELL_v1.1.md); shipped as the stacked PR chain #343–#351 plus hardening rounds driven by real end-to-end testing. ## 🗂 Sessions (the headline) - **Lisa is session-based now.** The left rail carries a tree of your Lisa sessions — new, switch, and auto-naming (first user message becomes the label). The top strip shows only the *current* session's status. - **True concurrency, not just switching.** Each session gets its own chat context (`per-session ChatCtx`); a reply streaming in a background session completes there and marks the tab unread instead of bleeding into the active one. Activation is an O(1) pointer move. The idle/reflect schedulers now capture the active session **per firing** — fixing a pre-existing bug where they closed over the process-start session forever. - **Switching is race-proof.** Session mutations run under an 8s abort timeout with the composer locked during the swap; the session list retries on fetch failure instead of staying empty until the next poll (the "sessions vanish on refresh" and "new session won't chat" reports). ## 🌳 Agents in the same tree - **LISA / Claude Code / Codex are same-level roots** (Aider too), with an optional by-project view. Clicking an agent session opens its conversation directly. - **Read-only step stream + transcript.** Each observed session shows a structural step feed (tool names, file basenames, `argv[0]` — never contents; planted-secret tests per adapter) and, on the owner's machine only, the actual chat transcript — the endpoint is **strictly loopback-bound**, same guard class as `/api/config/save`. - **Island deep-links** — "Open in Lisa" on an island card focuses that session's node in the main window. ## 🎨 Shell & themes - **Three-column layout**: 300px session tree, fluid chat, 320px right rail of uniform sections — current wants, agent inspector, mail, reflection, and a TOKENS section (source + usage + spend). Right rail collapses; ≤1180px and ≤720px fallbacks preserved. - **Two real themes**: Nebula (dark) and Calm (light), a one-click toggle persisted per device — on iOS too (trait-aware colors, an Appearance picker, and the same tree-grouped roster). - **Delegated event wiring.** Tree/needs/inspector/stream controls survive roster-SSE rebuild storms — real clicks no longer land on freshly-replaced dead nodes (found by real-browser E2E, the biggest interaction fix here). ## 🔧 Correctness & contracts - `/api/sessions` (+ create/activate) and the Lisa-session schemas joined the OpenAPI contract; the server-internal `jsonlPath` field is stripped from the wire and pinned by contract tests. - A regression test now pins the idle-note sentinel regex against template-literal escaping (the cooked `MAIN_CLIENT_JS` bytes are asserted directly, catching the single-backslash trap `typecheck` can't see). ## ⚠️ Behavior changes - The web UI's lower-left panels moved to the right rail; session create and switch live in the left tree only. - Agent transcripts are visible **only from localhost** — remote/tunneled viewers keep the structural step stream, never message text. ## Verification `npm test`: **1550 / 1551 pass** (1 skipped), `npm run typecheck` clean, snapshot-pinned HTML, real-browser E2E across session create/switch/chat, theme toggle, tree navigation, transcript, and concurrency — validated in production as rc9 before tagging.
# Lisa v0.22.0 The **bounded autonomy** release — Lisa's desires can now evolve with conversation, time, and carefully limited web research, while the hosted runtime gains stricter capability, billing, request, concurrency, and memory boundaries. Typecheck green · full test suite green (**1,442 passed, 1 environment-only PTY skip**) · production build green. ## ✨ Desire dynamics v2 - Desires now carry intensity, horizon, review timing, and bounded source provenance, with time-decayed ranking for short sparks, seasonal interests, and enduring wants. - Conversation reflection can revise or close a desire without losing its identity or birth time. - Proactive heartbeat may review one due desire with a narrow tool allow-list and a hard budget of one search and two fetches. - Cloud autonomy now has the same desire-review path, but performs at most one inference action per account and sweep. ## 🔒 Cloud capability and HTTP boundaries - Hosted tenants receive an explicit allow-list instead of the local owner's shell, filesystem, process, plugin, MCP, device, and arbitrary-network powers. - Verification and checkout links are pinned to a configured HTTPS public origin; proxy-derived client identity is trusted only through an explicit hop count. - Every buffered request body is bounded: 1 MiB for control-plane JSON and 20 MiB for approved audio or chat attachments, including webhook listeners. ## 💳 Billing and inference integrity - Account, balance, purchase, and global-spend stores now fail closed on corruption or outages instead of silently behaving like empty state. - Apple transaction processing is resumable and idempotent across credit, refund, retry, and crash windows. - Chat and managed gateway inference share one admission boundary and one exception-safe tenant lease lifecycle, including stream disconnect cleanup. ## ⚙️ Reliable multi-tenant autonomy - Cloud sweeps use per-tenant serialization, Firestore leases, and atomic pending/completed checkpoints to avoid duplicate reflection and model spend. - Tenant runtimes are created single-flight, pinned while active, and evicted by TTL/LRU under a configurable hard target instead of accumulating forever. - Account deletion invalidates in-flight runtime creation, preventing deleted tenant state from being resurrected by a racing request. ## 🧰 Maintenance - Refreshed compatible transitive dependencies and upgraded `sharp` and `tsx`/`esbuild` to remove all known high-severity audit findings. - Added a durable architecture review, invariants, delivery phases, and implementation-linked optimization baseline for future work. ## For existing local users The local, bring-your-own-key experience remains unchanged. The new hosted restrictions apply only to Cloud tenant surfaces; desire review remains governed by the existing Proactive setting and its bounded tool budget.
# Lisa v0.21.0 The **"open the doors"** release — the sign-in surface that makes v0.20's Lisa Cloud usable by anyone (a mailed code or a Google button, no key), a **Knowledge Base v2.0** that turns the web into Lisa's memory, and the production hardening that lets the hosted service take real traffic. The local, bring-your-own-key experience is unchanged; everything cloud/KB is opt-in. Typecheck green · full test suite green (**1383 tests**) · no breaking changes for existing local users. ## ✨ Sign in & sign up (A-series) - **Mailed one-time codes** — type an address, get six digits, you're in. Registering and signing in are the same act, because reading the mail is the proof; passwords still work behind "Use a password instead." - **Sign in with Google** on the web login page (GIS button, id-token verified against the web/iOS client ids) and in the iOS app. - The account model binds Google/Apple/OTP/password entrances to **one** account per verified inbox — a later sign-in never forks the balance, and proving inbox ownership drops any password set before ownership was proven (pre-hijacking guard). ## ✨ Signup abuse gates (S3) - **Cloudflare Turnstile** bot-gate on registration (opt-in; degrades gracefully when unconfigured), a **disposable-email blocklist**, and a per-IP registration cap — every signup ignites an LLM birth, so the signup path gets its own screws. - **Transactional birth** — the soul's `seed.json` (the "is-born" flip) is written **last**, after the whole soul is on disk, so a crash mid-birth just re-runs instead of wedging a half-born soul. A **single-flight ceremony hub** means two concurrent requests share one birth, never two racing LLM calls. ## ✨ Knowledge Base v2.0 (K-series) - **Ingest engine**: any URL → provenance-stamped, CJK-safe Layer-1 markdown, with **site adapters** for WeChat, Bilibili, and YouTube. - **Link graph**: `[[links]]` parse into a real graph; `index.md` becomes a ranked map-of-content, and memories ⇄ KB cross-link with inline titles + autolink. - **Daily feeds brief**: sweep, classify, and personally rank sources into a daily digest. - Ingest surfaces everywhere: a web paste bar, a chat chip, an API route, and `lisa kb` on the CLI. ## ✨ Per-uid autonomy (S4) - The cloud edition finally gives **signed-in tenants** the heartbeat a Mac gets: a scheduled sweep walks recently-active accounts and runs one reflection per due soul, at a **tier cadence** (free 24h / tier-1 6h / tier-2 1h). - Autonomy is a **free perk with a floor**: it never charges the user, but it bows to the global kill switch (`LISA_BILLING_KILL`) and the $200/day cap, and its face cost is metered to the audit ledger — a large cohort can't quietly run the bill away. Birth honors the same floor. ## ✨ Production hardening (S6) - **Secret Manager mode** for `deploy/deploy.sh` (`SECRETS_MODE=sm`) — sensitive values ship to Secret Manager and reach the container by reference, never as console-visible env vars; the master token no longer echoes into deploy logs. - **B9 Firestore import** script (with an empty-source guard so a misinvocation can't wipe accounts) for the file→Firestore cutover that unlocks multi-instance. - A **production runbook** (`docs/RUNBOOK_CLOUD_PROD.md`) covering the deploy, the third-party consoles, the domain mapping, and the Cloud Scheduler sweep. ## 🔒 Security & fixes - **CSRF-guard on auth POSTs** — a `Content-Type: application/json` requirement closes a cross-site login/register vector (session fixation via a `text/plain` form POST). - **Mail**: real IMAP auth rejections are told apart from host/network errors, so a flaky server no longer blames the user's password. - iOS: `CFBundleShortVersionString` pinned to `$(MARKETING_VERSION)` so version bumps actually ship; StoreKit config so the simulator renders the paywall. ## For existing local users Nothing changes. All cloud, accounts, billing, autonomy, and Secret-Manager behavior is behind sign-in and `LISA_*` flags; the Knowledge Base is opt-in. Run Lisa on your own key, on your own machine, exactly as before.
# Lisa v0.20.0
The **"Lisa Cloud"** release — accounts, billing, cloud inference, and mobile
in-app purchases. Everything through v0.19 ran on the user's own keys, on the
user's own machine; v0.20 adds an opt-in hosted path so someone can sign in, get
a free window, top up, and run Lisa without bringing a key — while the local,
bring-your-own-key experience stays exactly as it was.
Typecheck green · full test suite green (**1123 tests**) · no breaking changes for
existing local users (all cloud/billing is opt-in behind sign-in + `LISA_*` flags).
## ✨ Accounts & auth (B0–B1, B8)
- **Email + password** accounts (scrypt, HMAC sessions, auth endpoints,
in-app deletion) and **Sign in with Apple** — on iOS (primary flow) and on the
web login page (Services-ID audience).
- **Email verification** via Resend; a verified account levels the free window
from $1 → $5.
- **Sign in to LISA Cloud** from the Mac menu-bar app; anomaly alerts reach the
phone.
## ✨ Billing (B3–B6, B8c)
- **Usage meter + face-value price table** and a **quota engine**: a 12-hour
session window, paid tiers, a 402 paywall, and a per-turn budget breaker.
- **StoreKit 2 consumable credits** on iOS with **server-side JWS receipt
verification** (Apple G3 root pinned) and ASN refund clawback.
- **Stripe top-up** for desktop/web + a `/account` self-service page.
- **Inference gateway (B6)** — a uid-authed gateway so signed-in Macs/CLI run
**key-free**; metered per turn.
## ✨ Cloud & multi-tenancy (B2, B9)
- **Per-uid home isolation** via an AsyncLocalStorage seam + lazy per-user birth,
and **per-tenant SSE fan-out + mood state** — one process serves many accounts
without crosstalk.
- **Firestore state backend + per-uid turn lease** (behind `LISA_FIRESTORE=1`) so
the service can run **multi-instance** with one metered turn per account at a
time.
## 🛡️ Operations + review hardening
- **B7 controls:** per-uid rate limits, a global daily spend cap, a kill switch,
anomaly alerting, and a reviewer-account seed. Operator runbook in
[`docs/`](.) (Apple portal, App Store Connect, DNS, deploy, 1.1 submission).
- **Adversarial review pass (#276)** closed the ways the stack could lose money,
stay up under load, or trust something it shouldn't:
- metered turns can't ship free (meter/gateway/limits fail **closed**, and a
debit on an unwritable balance store retries + logs loudly + refuses new
turns rather than silently dropping the charge);
- a transient lease-renewal blip no longer strands the lease into a peer
double-run; concurrent login guesses can't outrun the lockout; the gateway
body cap can't be disabled by a mistyped env; the per-IP limiter fails
**open** so a spoofed-header flood can't lock out real users;
- the Apple IAP certificate chain pins the G3 root on every path.
## 📱 iOS 1.1 (accounts / IAP)
`MARKETING_VERSION 1.1` ships the accounts + in-app-purchase flow to TestFlight.
Note: the Sign-in-with-Apple **nonce** path is compile-verified but not yet
exercised against a live Apple sign-in, so `LISA_CLOUD_APPLE_REQUIRE_NONCE` stays
**default-off** until a TestFlight build confirms it — a present nonce is always
verified regardless.
## 📝 Notes
- **Local users are unaffected:** no sign-in, no gateway, no Firestore ⇒ Lisa
behaves exactly as in v0.19 on your own keys and your own machine.
- Cloud paths activate only behind an account + the relevant `LISA_*` flags.# Lisa v0.19.0 The **"put a record on"** release. The headline is a **gramophone in the Room** — click it and her space fills with music, from a small in-client player with a real playlist. Plus a nav-launcher tidy-up. Typecheck green · full test suite green (**1018 tests**) · no breaking changes. ## ✨ A gramophone in the Room (#256) Design + a 7-point pro/con debate: [`docs/PLAN_GRAMOPHONE_v1.0.md`](PLAN_GRAMOPHONE_v1.0.md). - **The object.** A pixel-art gramophone sits on the floor by the armchair. Click it and a **music player** slides up — same self-contained DOM/CSS as the rest of the Room, no browser window, no new deps. While it plays, the gramophone **glows and drifts musical notes**. - **The player.** Play/pause · prev/next · **seek** · volume/mute · a **mood-filtered playlist** (古典 / 轻音乐 / 经典, plus **我的** for your own files) · **shuffle** and **repeat** (off / all / one) · a gentle fade in/out · and it **remembers** where you were (track + position + volume) between visits. The now-playing footer credits each track's license. - **Honest by design.** It never autoplays (audio starts only from a tap), and it's a **user affordance** you operate in her room — not a readout of her mood. The glow/notes just reflect the real fact that sound is on. - **Bring your own.** Drop any `*.mp3` into **`~/.lisa/music/`** and it appears in the playlist under **我的 / mine** — her room can play *your* music. **Music & licensing.** Ships 6 curated instrumental tracks (~19 MB, re-encoded to 112 kbps), every one **CC0 / public-domain / CC-BY** — 2 classical (Chopin via Musopen, Canon in D), 2 light (a lo-fi pair), 2 lounge. The three CC-BY tracks (Kevin MacLeod / incompetech) show attribution in the player and in [`music/CREDITS.md`](../src/web/assets/room/music/CREDITS.md). **No standard-copyright audio is bundled.** Backend: `GET /api/room/music` merges the bundled manifest with your `~/.lisa/music/`; tracks stream by opaque id with **HTTP Range** support (so seeking works) and a path-traversal-safe resolver. ## 🔧 Also - **Nav launcher locked to a clean 3×3** and Mail moved back to a sidebar card (#253); README brought up to date (#254, #255). ## Notes - No breaking changes; soul / mood / heartbeat / Reve untouched. The gramophone is a read-only user affordance layered onto the existing Room.
# Lisa v0.18.1 A hardening patch on the **desire-evolution** arc that v0.18.0 shipped — two review follow-ups so a *closed* desire truly stays closed, and so intra-session focus can't latch onto a stale conversation after a restart. Typecheck green · full test suite green (**1014 tests**, 1013 pass) · no breaking changes. ## 🔧 Fixes ### A closed desire actually stays closed (#251) v0.18.0's `desire_close` was a *soft* close — it only flipped `actionable` off, which is indistinguishable from a merely dormant wish. So closed desires kept coming back into reflection's "revise or close these" block and could be re-closed on every pass (duplicate `[DESIRE_CLOSED]` journal noise), and the list she was asked to tend never actually shrank. - **Persisted `closed` marker.** `desire_close` now records a distinct `closed` state (kept on disk for the record + git history) and is **idempotent** — an already-closed desire is a no-op, so no duplicate journal / progress entries. - **Clean re-open.** Closing now **preserves** `heartbeatPrompt` / `pursuit`, so a later `desire_revise` that makes it actionable again restores her auto-pursuit intact (previously the prompt was silently dropped). - Closed desires are filtered out of the reflector's block (the list finally shrinks) and are never surfaced as her current / focused desire — in the room, the island ping, or `lisa status`. ### Intra-session focus survives a restart (#251) The "what the conversation is about" focus was gated on the process idle clock, which reads *fresh* immediately after a launchd restart — so a stale resumed chat could pin a focus onto an old topic for up to 15 minutes. It's now gated on the **last real user message** (reset across restarts), so focus only applies while the conversation is genuinely live. ## 📝 Notes - Soul / mood / heartbeat / Reve are untouched; existing desire files round-trip byte-stable (the `closed:` line only appears once a desire is actually closed). - New store tests cover the closed-marker round-trip, `heartbeatPrompt` retention, idempotent re-close, and re-open clearing `closed`.
# Lisa v0.18.0 The **"her wish finally moves"** release. v0.17 gave web conversations the power to *trigger* reflection (PR1 of the desire-evolution plan); v0.18 completes the arc — Lisa's desires can now **evolve, close, and follow the conversation** instead of only ever piling up. Plus a reworked **九宫格 nav** with real Mail and Settings homes. Typecheck green · full test suite green (**1011 tests**, snapshot refreshed) · no breaking changes. ## ✨ Desire evolution — the arc completes (PRs 2–4) Root cause of *"the wish never changes no matter what we talk about"* was three independent gaps ([`docs/PLAN_DESIRE_EVOLUTION_v1.0.md`](PLAN_DESIRE_EVOLUTION_v1.0.md)). v0.17 closed the first (web conversations now reflect, #242). v0.18 closes the rest: ### Reflection can revise & close desires, not just append (#249) Reflection is the path that turns a conversation into a desire — but it could only `desire_add`, and it was never shown the desires Lisa already had, so it appended near-duplicates blind and the set only grew. Now: - Reflection **sees its own desires** (a compact list in the reflector prompt). - **`desire_revise`** — read-modify-write by slug; only supplied fields change, an `undefined` never wipes an existing value, `bornAt`/`slug` are immutable, and it throws (never silently creates) on an unknown slug. - **`desire_close`** — a **soft** close: `actionable` off + a `[CLOSED]` progress note + a `[DESIRE_CLOSED]` journal line. The file is **retained and git-tracked** — reversible, nothing destroyed. This is the guardrail that lets reflection prune without violating soul sovereignty. ### The surfaced desire tracks what's actually active (#250) The "current desire" ticker read `desires.find(d => d.actionable)` over `fs.readdir` order — an arbitrary static pick. Now `pickCurrentDesire` prefers actionable desires and, within those, the **most recently active** (authored or pursued), falling back to most-recently-born. Because the key is a stored timestamp, the pick is stable between real events and only moves when something actually happens. ### …and follows the conversation, turn by turn (#248) The finest-grained version: when a conversation is live and *clearly* about one of her desires, the surfaced desire is that one. A pure lexical-overlap `pickFocusedDesire` (no per-turn LLM call, no persisted state, display-only) — **cross-lingual** (latin tokens + CJK bigrams, so it works in Chinese too), behind a 15-minute freshness gate, and strict: a weak (< 2 shared tokens) or tied match returns `null` and falls back to the recency pick, so it **can never invent focus**. ## ✨ Web — 九宫格 nav with Mail + Settings (#245) The left-rail nav is now a tile grid with unified line-SVG icons, growing two real destination views: - **Mail** — reuses the `/api/mail/*` endpoints and the guided connect modal from v0.17, and adds per-account enable/disable/remove plus a "needs-you" badge. - **Settings** — API-key management (`/api/config/*`, localhost-only), and the Proactive-autonomy and Compact-mode switches, relocated out of the sidebar footer. The Knowledge (kb) tile is retained, so the grid holds all ten destinations. ## 🛡️ Review hardening Every PR was reviewed before merge; the fixes folded in: - **`desireActivity` no longer aborts `lisa status`** (#250) — it built file paths (which run `assertSafeSlug`) outside its try/catch, so a stray unsafe-named file (e.g. a macOS `._x.md` AppleDouble in the git-synced soul dir) rejected the whole scan. `/api/island/ping` swallowed it; `lisa status` didn't. Fixed + regression test; plus a deterministic tie-break so an exact recency tie can't fall back to readdir order. - **Locked the desire revise/close read-modify-write** (#249) under `withSoulLock`, matching the other soul RMW paths — now that web idle-reflect can race a CLI reflect on the same slug. - **Skip `desireActivity`'s fs.stat on an island-ping focus hit** — the `??` short-circuits the recency pick, so its per-desire stat work is only done when actually needed. ## 📝 Notes - No breaking changes; the desire files stay plain Markdown, closing is soft + reversible, and focus is display-only (it chooses which existing desire to show, never creates one).
# Lisa v0.17.0
The **"legible and connected"** release. v0.16 gave Lisa a life inside her Room;
v0.17 makes the parts you actually read and touch every day behave: her replies
render as real **Markdown** instead of raw `###`/`**`/```` ``` ````, connecting a
**mailbox** is a guided, verified flow instead of a bare form, and — the big one
— **web conversations finally evolve her desires** (they never did before). Plus
the `lisa mail` CLI works again, and she stands centered on the rug.
Typecheck green · full test suite green (**984 tests**, snapshot refreshed) · no
breaking changes.
## ✨ What's new since v0.16.0
### Web — Lisa's Markdown renders as styled HTML (#238)
Lisa emits standard Markdown, but the web client dropped her replies in via
`textContent`, so every `###` / `**` / `-` / ```` ``` ```` / table showed up as a
literal symbol. A new source-injected `renderMarkdown()` (`src/web/md-render.ts`)
now renders headings, bold/italic, inline + fenced code (with a language label +
copy button), nested lists, blockquotes, rules, tables, and links restricted to
`http`/`https`/`mailto`. It's wired into every surface that shows her prose — the
streaming chat bubble (re-rendered once per animation frame), history bubbles, and
the idle **★** reflection cards on the Room and Island.
**Escape-first by design:** her output is model-generated and untrusted, so every
text run is HTML-escaped *before* any tag is introduced — no `javascript:`/`data:`
hrefs, no attribute breakout, no raw-HTML passthrough (19+ unit tests cover the
XSS and link-scheme surface).
### Mail — guided connect modal + verify-before-save (#234)
Connecting a mailbox used to drop you in front of a bare Email / App-password /
IMAP-host form with no hint what an "app password" is, and the web endpoint stored
whatever you typed **without checking it** — so a wrong password produced a
silently empty digest. Now both ends explain themselves:
- A provider picker (**Gmail / iCloud / QQ / 163 / Outlook / Other**) with
per-provider numbered setup steps and a prominent **"Open App Passwords ↗"**
link; labels, placeholders, and IMAP host adapt to the provider, and typing an
address auto-detects it from the domain.
- The backend `probeAccount()` does a single-message fetch to confirm the
credentials actually sign in, and `POST /api/mail/connect` **verifies before
storing** — returning a plain-language hint ("Gmail rejected the sign-in. Use a
16-character app password…") instead of persisting an unusable mailbox.
Still read-only, still IMAP + app-password.
## 🔧 Fixes
- **`lisa mail connect` works again (#233).** `mail` was missing from the CLI's
raw-subcommand set, so `--email` / `--pass` / `--client-id` were rejected as
"unknown flag" and `--provider` / `--host` / `--port` were silently swallowed as
global flags — the whole `lisa mail connect` command was unusable in 0.16.0.
Its trailing flags now reach the handler verbatim, while `autostart` /
`heartbeat` keep parsing their own recognized global flags.
- **Web conversations now evolve Lisa's desires (#242).** `reflectOnSession` — the
only automatic way a conversation turns into or updates a desire — was wired to
the CLI and channel router only; the web `POST /reflect` endpoint existed but no
client ever called it, so *web chats never touched her desires at all.* A
debounced reflection scheduler now reflects once after a quiet stretch with new
user input (default 5 min), independent of the hour-long "dream" idle.
- **Lisa stands centered on the rug (#228).** Re-anchored `#lisa-wrap` so she's
centered and grounded on the rug in both room themes, in every pose.
## 🛡️ Review hardening
Every PR in this release was reviewed before merge; three latent defects were
caught and fixed as part of it:
- **Markdown renderer infinite loop (#238).** The fenced-code opener was stricter
than the fence detector, so an ordinary ```` ```c# ```` / ```` ```c++ ````
/ ```` ```objective-c ```` block wedged the render loop forever and froze the
tab mid-stream. The opener now accepts any info string (first token = lang), with
a regression test and a forward-progress guard.
- **IMAP session leak (#234).** `probeAccount` closed the connector when the 20s
timeout won the race — while the connection was still opening, so the close was a
no-op and the later-succeeding session leaked. Cleanup now chains onto the probe
actually settling.
- **CLI flag regression (#233).** The first cut of the mail fix also swallowed
`autostart install --port/--channels/--imessage` and `heartbeat run --model`;
the two behaviors are now split so both work.
## 📝 Notes
- No breaking changes; soul / mood / heartbeat / Reve are untouched.
- The web Markdown renderer is the same function in the browser and the unit
tests (source-injected via `.toString()`), so what's tested is what ships.# Lisa v0.16.0
The **"she has a life now"** release. v0.15 gave Lisa a home (the Room); v0.16
gives her a **life inside it** — she looks up when you arrive, drifts through
her own at-home activities, changes into pajamas at night, and leaves notes on
the desk. Every room widget now closes its loop **inside the app** (no more
stray browser window), and the room is **re-decoratable**. Alongside it: a
Haiku 4.5 provider fix and a website ops runbook.
Typecheck green · full test suite green (**920 tests**, snapshot refreshed) · no
breaking changes.
## ✨ What's new since v0.15.0
### Lisa Room v2 — a living space, not just a wallpaper (#225)
Design + deep research on BSide (miHoYo's *Olivia Lin*) + a pro/con debate:
[`docs/PLAN_ROOM_v2.0.md`](PLAN_ROOM_v2.0.md). The through-line: BSide buys its
"aliveness" with photoreal rendering but leaves the *simulation* axis empty
(3 idle scenes, no clock, no weather). Lisa can't win on pixels-vs-photoreal, so
v2 doubles down on the axis BSide left open — a life driven by her **real self**.
- **In-client interactions — no browser.** Every room widget ("Talk to her",
clicking Lisa, the bookshelf, the letter) closes its loop inside the same
WKWebView via a same-origin `{type:'lisa-room', action, prefill}` bridge to
the parent GUI (`lisaShowView` / composer prefill). `window.open` is gone —
it used to spawn a browser window because the Room iframe couldn't see the
Island's native bridge. The service-worker shell also went **network-first**,
so a GUI update shows on the next refresh instead of the second.
- **Presence beat.** She looks up and meets your eyes when you open the room,
refocus the window, or hover — BSide's single most-praised "aliveness" moment,
and cheap in pixel art.
- **Autonomous ambient life.** When idle she drifts on her own through at-home
activities — reading, tea, headphones, a stretch, gazing out the window —
time-of-day weighted. **Honesty held:** captions stay neutral and she never
performs work she isn't doing or a mood she doesn't have; any real signal
(a real `working-*` mood / thinking / Reve) takes over immediately.
- **The room remembers her day.** Her ★ *while-you-were-away* notes pile up on
the desk (with a count badge), she changes into **pajamas** at night, and the
**bookshelf** is clickable — it surfaces her real `current_desire`.
- **换景 — re-decorate.** A second full room theme (a warm plant-filled
*sunroom*) and a ❖ switcher; the choice persists. Themes are data-driven, so
adding one is three images + a line.
- **A slimmer, more feminine figure.** The full-body sprite was rebuilt with a
slender hourglass silhouette (defined waist, fitted hoodie); every pose —
idle, look-up, sit, sleep, read, tea, stretch, listen, window-gaze, pajamas —
regenerated from one anchor for consistency, and sit/sleep switched to
`contain` so no pose is ever stretched.
- **Perf.** The parallax loop pauses when the room is hidden; the letter pile
is capped.
All eleven+ character poses and both room themes are generated pixel art
(`gemini-2.5-flash-image` via an anchor → keyframes → chroma-key + foot-anchor
pipeline, cf. Ludo.ai / `chongdashu/ai-game-spritesheets`).
### Providers — gate `output_config.effort` by model (#218)
Claude Haiku 4.5 hard-`400`s on `output_config.effort`. Subagents and
idle/reflect calls default to effort `"low"`, so every one of them routed to
Haiku failed outright. `modelSupportsEffort()` now drops the param for the Haiku
family so those calls succeed, with tests covering the gate and the matcher.
### Docs — website ops runbook (#226)
`docs/WEBSITE_OPS.md`: Cloudflare deploy, cache, and fonts/OG playbook for
meetlisa.ai.
## 🔧 Notes
- No breaking changes; soul / mood / heartbeat / Reve are untouched (the Room
is a read-only projection of real state).
- Room v2 was developed as a stack of four PRs (#221 core, #222 lived-in,
#224 themes, #225 figure); because v0.15's Room v1 was squash-merged, the
stack was consolidated into one clean integration (#225) onto latest main.# Lisa v0.15.0 The **"she has a home now"** release. The headline is the **Lisa Room** — an ambient pixel-art living space where the full-body Lisa lives, every layer a read-only projection of her real state. Alongside it: ElevenLabs-first voice transcription and a hardened Anthropic relay for Cloud Run. Typecheck green · full test suite green (915 tests) · no breaking changes. ## ✨ What's new since v0.14.0 ### Lisa Room — an ambient, state-driven living space (#214) - **`GET /room` + a ⌂ Room tab** in the GUI (lazily-loaded iframe): a cozy pixel room rendered as a layered DOM/CSS 2.5D diorama — no canvas, no WebGL, no bundler, zero new deps, same self-contained pattern as the Island. - **Everything is driven by her real state** (read-only — soul / mood / heartbeat / Reve untouched): mood → pose (sitting at her glowing laptop while `working-*`, curled asleep while `napping` or Reve-dreaming, standing idle with breathing + blink otherwise), `chat_start` → pulsing monitor glow, Reve → the room falls into night with floating Z's, and her ★ *while-you-were-away* note appears as a glowing letter on the desk. - **Full-body animatable spritesheet** (idle blink frames / sit / sleep), generated via an anchor → keyframes → chroma-key + foot-anchor pipeline — she finally stands on the floor instead of floating as a bust. - **Day / dusk / night** crossfade by her local clock; weather-flavored moods bring rain/snow particles; fireflies at night. - Review hardening: ambient particles only rebuild when the mode actually changes, the parallax loop idles when the tab is hidden (and stays off under `prefers-reduced-motion`), honest state mapping (`sleepy` ≠ asleep, a raincoat doesn't make it rain indoors), and in-GUI "Talk to her" switches back to chat in place instead of spawning a duplicate tab. - Design + build notes: `docs/PLAN_ROOM_v1.0.md`. ### Voice — ElevenLabs-first transcription (#174) - Transcription now prefers **ElevenLabs Scribe** (`ELEVENLABS_API_KEY`), falling back to **OpenAI Whisper**; the no-key error names both providers. - Composer + / 🎙 glyphs became line-style SVG icons matching the function bar. ### Infra — Anthropic reverse-proxy relay for Cloud Run (#209) - `packaging/gcp-relay/`: a ~110-line zero-dep Node relay forwarding `/v1/*` (streaming included) to `api.anthropic.com`, for reaching Claude reliably from behind flaky networks by pointing `ANTHROPIC_BASE_URL` at it. - **Key-swap gate**: the real Anthropic key lives only in Secret Manager and is injected server-side; clients present a revocable `RELAY_TOKEN` instead. ### Docs - Lisa Pocket (iOS) surfaced in the README, extracted OSS repos cross-linked, and an App Store 4.1(a) response kit (#213). - The no-clone `gh api` Homebrew-tap bump used for v0.14.0 is now the documented path (#212). ## Install / update - **Mac**: download `Lisa-Suite-v0.15.0.dmg` below, or let the in-app updater pull it. - **CLI**: `npm i -g @oratis/lisa@0.15.0` (or the `lisa-*-bundle` tarballs below). - **iOS**: via TestFlight (ships separately from this release).
# Lisa v0.14.0 The **App-Store-readiness** release. Since v0.13.0, the iOS companion (Lisa Pocket) went from "connects and chats" to a polished, submission-ready app: it now pairs reliably off Wi-Fi, never leaves a chat in a dead end, and has a redesigned home / chat / agents surface. Alongside it: a hardened LISA Cloud M0, and a small, model-appropriate provider tune-up. Typecheck green · full test suite green · `swift build` green · no breaking changes. ## ✨ What's new since v0.13.0 ### iOS — reachability you can trust - **Tailscale-first pairing + honest errors** (#202): the pairing QR/flow prefers a reachable address, and connection failures now say *what* went wrong instead of masking everything behind a `-999 "cancelled"`. - **Off-Wi-Fi recovery** (#203): a "same Wi-Fi?" banner when a paired LAN IP is unreachable on cellular, an "Anywhere (Tailscale)" toggle, and **one-tap switch to LISA Cloud** when your Mac isn't reachable. - The Mac backend now **binds LAN-reachable by default, token-gated** (#169), and the menu bar has native **"Pair iPhone…" QR** pairing (#171, #173) — no terminal needed. ### iOS — chat that never dead-ends - **Retry / Stop / empty-turn handling** (#206): an empty or failed turn becomes a tappable **Retry** instead of a silent "(no response)"; **Stop** cancels a streaming turn (and aborts the agent server-side); clearer error vs. empty states. - **Full UI redesign**: card-dashboard Home + 4-tab IA (#196, #198), chat bubbles with code blocks / tool chips / typing / history (#194, #200), needs-you-first **Agents** action cards (#199), onboarding polish (#195), accessibility pass (#191), and haptics + toasts on mutating actions (#190). ### iOS — App Store submission - **Sign in with Apple gated off for v1** to avoid the 5.1.1(v) in-app-account-deletion requirement while the cloud is still single-tenant (re-enable via `LISA_ENABLE_SIWA`). - **TestFlight pipeline fixes**: build number must be `< 2³²` (`date +%s`, not a 12-digit stamp), `MARKETING_VERSION` must be ≥ the ASC version, and `CFBundleVersion` is now stamped into the generated Info.plists — so uploads actually appear in TestFlight. - Privacy policy page (en + zh-CN) for submission (#165). ### LISA Cloud (M0) - Connect Lisa Pocket to an **HTTPS cloud URL**, not just a LAN Mac (#161). - **Durable soul** via a GCS volume at `/data` (#162); provider-aware birth/run gates with a **GLM-powered demo** (#158); Sign-in-with-Apple env plumbing through `deploy.sh` (#177). ### Providers — model-appropriate tuning - **1-hour prompt caching** on the stable system prefix (soul + skills + memory) so it stays warm across think-time gaps instead of a cold re-write every 5 minutes; conversational tail stays 5-minute. `LISA_CACHE_TTL=5m` opts back out. - **Thinking-effort lever** (`output_config.effort`): dispatched subagents default to `low` (cheap parallel work); `LISA_EFFORT` overrides globally. See `docs/PLAN_MODEL_TUNING_v1.0.md` for the plan + debate (most OpenClaw-style knobs were dropped — 1M context is already native on Sonnet 4.6, the rest are Opus/Sonnet-5 only). ## Install / update - **Mac**: download `Lisa-Suite-v0.14.0.dmg` below, or let the in-app updater pull it. - **CLI**: `npm i -g @oratis/lisa@0.14.0` (or the `lisa-*-bundle` tarballs below). - **iOS**: via TestFlight (build ships separately from this release).
# Lisa v0.13.0 A small, mostly-infrastructure release on top of v0.12.0. The headline features — the **multi-agent control plane** (see every agent; command managed agents; PTY-spawn and **adopt** real `claude`/`codex` sessions) and the **iOS companion** — already shipped across v0.11.x–v0.12.0. This release packages the iOS **TestFlight** delivery pipeline and cuts a clean, current build to install. 824 tests, typecheck green, `swift build` green, no breaking changes. ## ✨ What's new since v0.12.0 ### iOS — TestFlight release pipeline - `packaging/ios-companion/testflight.sh` + `.github/workflows/release-ios-testflight.yml`: archive with `xcodebuild`, upload to App Store Connect via an API key — so Lisa Pocket can ship to TestFlight from CI. - `packaging/ios-companion/RELEASE.md` documents the signing/credentials setup. ## 📦 Already in v0.11–v0.12 (recap, for anyone upgrading from an older build) If you're coming from an older install (e.g. v0.8.x), you also get everything from the control-plane line: - **See every agent** — GUI sidebar, island, and Mac menu bar show all agents (claude-code / codex / managed / …) with live progress: turns, tokens, last tool·file, ⚠ pending-permission, ✗ error. - **Command managed agents** — delegate a task, then approve/deny each mutating step, send follow-ups, cancel. - **PTY agents (flagged `LISA_PTY_AGENTS=1`)** — spawn real `claude`/`codex` under a pseudo-terminal, and **adopt idle sessions you started yourself** via `claude --resume` (guarded so a live session is never corrupted). - **iOS companion (Lisa Pocket)** — pair by QR, watch + steer agents, APNs push, widgets, Live Activity. ## Install - **CLI / backend:** `npm install -g .` from the repo (or the published package) → `lisa serve --web`. - **Mac app:** the notarized `Lisa-Suite-v0.13.0.dmg` is built by CI on the tag; drag Lisa.app to /Applications. (The app spawns `lisa serve --web` from your PATH.)
# Lisa v0.12.0 **Lisa Pocket goes real.** The iOS companion stops being a skeleton and becomes a proper phone app — pair by QR, watch and steer your Mac's agents, get pushed when something needs you — backed by real **APNs** delivery. Everything builds on the v0.11 backend control plane; this is the native surface on top of it. 823 tests (804 → 823), typecheck green, no breaking changes. ## ✨ Highlights ### Lisa Pocket — the iOS companion app - **Pair by QR.** `lisa pair` shows a QR on your Mac; the app scans it with the camera and mints a per-device token (revocable, hash-only on disk). - **Watch & steer.** Live agent roster (SSE, auto-reconnect with backoff + foreground resync), chat with a **live mood portrait** (real server art), and a **dispatch ledger** view with per-entry log tails. - **System surfaces.** A **home-screen Widget** (active / stuck agent counts), **lock-screen accessories**, and a **Live Activity** for a pinned agent that **refreshes remotely over APNs** — progress on your lock screen without opening the app. - **The rest of her, read-only on your phone.** Soul / Memory / Skills / Tools inspection, a **Reve** tab (recap · "while you were away" · desires · advisor), and a **Sense** tab (consent — revoke-only — + recent events). - **Private by default.** Optional **Face ID / passcode** lock; `lisapocket://` deep-links route notification taps to the right session/tab; paired-devices list in Settings. ### Push notifications (APNs) - **Real APNs delivery** with token auth — `done` / `error` / `permission` agent transitions push to your phone. Inert without a push key (no key → no-op, no errors), so it ships safely. - ntfy notifications now **deep-link** to the originating session. ### CLI - **`lisa pair [--host H]`** — show a QR to pair a phone with a running `serve` (localhost), minting a per-device token. ### Fixes - Codex resume-adopt now **refuses explicitly** instead of silently downgrading. - Plus the v0.11.1 first-run polish (single title bar, never-silent boot) carried forward. ## Notes - The push path is **opt-in and inert without an Apple push key** — nothing tries to reach Apple until you provision one. - iOS surfaces (Widget / Live Activity / camera / Face ID) build with Xcode from `packaging/ios-companion/`; they can't be compile-verified in this repo's CI (no Xcode), but the backend halves (pairing, push, dispatch, SSE) are unit-tested. ## Install ```sh npm install -g @oratis/lisa lisa serve --web lisa pair # then scan the QR with Lisa Pocket ``` Mac app: download `Lisa-Suite-v0.12.0.dmg` from the GitHub Release.
# Lisa v0.11.1
**First-run polish for the Mac app.** Three things that bit a fresh install of
the v0.11.0 DMG — fixed. No new features, no breaking changes.
## 🐞 Fixes
- **Title bar no longer shows "Lisa Lisa".** The hosted page draws its own
branded title strip, and the native macOS window *also* showed its title under
the transparent titlebar — two "Lisa"s. The native title is now hidden
(`titleVisibility = .hidden`); only the page's branded bar shows.
- **The first-run UI never dead-ends silently.** If the backend isn't reachable
or onboarding hits a snag, the boot used to `catch { return }` and leave a
blank, unresponsive window with no explanation. Now:
- a **banner** surfaces any uncaught error or unreachable backend instead of
nothing — e.g. *"Cannot reach Lisa backend on localhost:5757. Start it:
`lisa serve --web` (install once: `npm i -g @oratis/lisa`). Retrying…"*;
- `startupGate` **retries** `/api/config/status` so a page that loads a hair
before the backend's routes are ready still reaches onboarding;
- the **API-key overlay** (`SET·API·KEY`) and **birth ritual** reliably appear
for a fresh user, and birth errors are shown rather than swallowed.
## Note on setup
Lisa.app is a thin client — it loads the chat from a local backend. If you only
downloaded the DMG, install the backend once:
```sh
npm install -g @oratis/lisa # or: brew install oratis/tap/lisa
```
Lisa.app auto-starts `lisa serve --web` on launch (and shows install guidance if
it can't). First launch then walks you through the API key + birth ritual.
## Install
```sh
npm install -g @oratis/lisa # CLI + backend
```
Mac app: download `Lisa-Suite-v0.11.1.dmg` from the GitHub Release.# Lisa v0.11.0 **Spend your subscription, reach her from your pocket.** Two threads land on top of the v0.10 foundations: a **coding-plan** system that runs coding work on a subscription you already pay for (Claude Pro/Max · ChatGPT/Codex · Copilot) instead of metered API tokens, and the **iOS-companion control plane** — remote gating, per-device pairing, push, and a native *Lisa Pocket* app — so you can watch and steer the agents on your Mac from your phone. 112 new tests (692 → 804), every PR green through typecheck + build + the full suite. No breaking changes — everything below is additive. ## ✨ Highlights ### Coding plans — run coding work on a subscription, not an API key - **Detect + pick.** `lisa model list` detects your installed plan CLIs (Claude Code / Codex / Copilot) and login state; `lisa model use plan://<id>` selects a delegation target — presence-only detection, **no tokens read**. - **Delegate.** The `run_on_plan` tool runs a coding task on the selected plan by driving the vendor's own CLI headlessly (`claude -p` / `codex exec` / `copilot -p`), so the work **bills to your plan, not an API key**. It is the *sanctioned out-of-process* path — LISA never extracts or replays subscription tokens (Anthropic's terms forbid that; they enforced it against OpenClaw). Gated like `dispatch_agent` (approval-required, off for autonomous/remote toolsets). - **Real usage.** `lisa model list` shows honest rolling-window consumption read from local transcripts (`1.2M tok in 5h · 4.8M today`) — no faked "headroom %". - **Web picker.** A **PLANS** panel in the web UI to select a plan and see status/usage. - Full mechanism + rationale: [docs/CODING_PLANS.md](CODING_PLANS.md). ### Gateway bearer auth - **`ANTHROPIC_AUTH_TOKEN`** is now honored — sent as `Authorization: Bearer` (vs `x-api-key`), taking precedence over `ANTHROPIC_API_KEY`, matching Claude Code. The sanctioned path for an Anthropic-compatible LLM gateway/proxy (Bedrock/Vertex/relay). OpenAI-compatible gateways already worked via `apiKey`. ### iOS companion — watch & steer your Mac's agents from your phone - **Lisa Pocket** — a native SwiftUI companion app (roster, chat, control), plus **Live Activity + Dynamic Island** for a pinned agent. Self-contained under `packaging/ios-companion/`; talks only to your local `lisa serve --web`. - **Pocket-control CLI + live output.** `lisa agents pty <agent> <task>` (a loopback thin-client), a `GET /api/agents/pty/<id>/stream` SSE for live terminal output, and a structural `GET /api/dispatch/list`. - **Remote-control policy** (`/api/control/policy`). High-risk actions are gated for remote callers: adopting an *external* (non-LISA) session is **off by default**; controlling LISA's own agents is allowed for token-bearing devices. Config fails safe to the locked-down default. - **Per-device pairing tokens** (`/api/pair/start`, `/api/devices`). A revocable credential per phone — 192-bit CSPRNG, only the **hash** stored (now in a `0600` file), constant-time compared, layered on top of `LISA_WEB_TOKEN`. - **Operational push** (ntfy). `done` / `error` / `permission` agent transitions push to your phone (`/api/push/*`, `PushBridge`) — **metadata only**, never prompts/replies/file contents. APNs is stubbed for a later pass. ### Docs - README (EN + zh) refreshed to reflect the orchestrator, PTY steering, session adoption, and coding plans. New [CODING_PLANS.md](CODING_PLANS.md) and [IOS_COMPANION_PLAN.md](IOS_COMPANION_PLAN.md). ## Behavior changes / new config - `LISA_CODING_PLAN` (set by `lisa model use plan://<id>`) records the default coding-plan delegation target. It does **not** change LISA's own model. - `ANTHROPIC_AUTH_TOKEN`, when set, takes precedence over `ANTHROPIC_API_KEY`. - New web endpoints (`/api/plans*`, `/api/control/policy`, `/api/pair/*`, `/api/devices*`, `/api/push/*`, `/api/dispatch/*`) sit behind the existing loopback-or-`LISA_WEB_TOKEN` auth gate; minting/revoking pairing tokens and changing the control policy are loopback-only. - The Live Activity / Dynamic Island and the iOS app are simulator-buildable today; real-device push (APNs) needs an Apple push key (tracked follow-up). ## Install ```sh npm install -g @oratis/lisa lisa serve --web ``` Mac app: download `Lisa-Suite-v0.11.0.dmg` from the GitHub Release.
# Lisa v0.10.0 **The 1.0-foundations release.** Everything since v0.9.1's security pass goes *deep before wide*: a real multi-agent **Dispatch** layer, a consent-gated ambient **Sense** layer, the local-model + reflection groundwork, and — under it all — a test net around the core loop that was previously running naked. 145 new tests (547 → 692), every PR green through typecheck + build + the full suite, and the privacy story is now code, not copy: **all sensitive ambient capture is off by default and gated behind explicit consent.** > Read **Behavior changes** below if you use `--approval ask-mutating`, the > screen-advisor, or plan to enable any ambient sensing — a few defaults are new. ## ✨ Highlights ### Dispatch — command and watch every agent - **Multi-agent monitor.** The island pill + GUI sidebar now show *all* observed agents (claude-code / codex / opencode / aider / git / shell / takoapi), not just Claude — one roster, live, with a per-agent badge and structural activity. - **Remote agents are first-class (TakoAPI, D2b).** Agents you *call* via the `takoapi` tool appear in the hub as sessions with their A2A TaskState, beside your local agents. Discovery stays in `takoapi discover`; the hub only ever shows agents you've actually interacted with — never the full registry. - **`lisa agents`** — one-shot snapshot of every agent session across observers. - **`/api/agent/signal`** — list / cancel the agents Lisa dispatched, over the (auth-gated) web API; it can only ever touch agents Lisa launched herself. ### Sense — ambient context, consent first - **Unified consent framework.** A single source of truth (`~/.lisa/consent.json`), **all sensitive signals OFF by default**, fail-closed. Manage it from `lisa consent` or the island's "👁 sense" panel — every signal has a toggle and there's a one-tap **Stop all sensing**. - **S2-screen** — your foreground app, as an ambient signal (app *names* only; no screenshots in this path). Blacklisted apps (password managers / banking) are skipped whole-frame; window titles are secret-path-dropped + PII-redacted. - **S2-voice** — push-to-talk transcripts become ambient context when `voice` is granted (no audio stored; PII-redacted). Off → dictation works exactly as before. - **Observer deepening** — `gitBranch` for codex/opencode (derived from cwd), wider activity windows, and a `verify-observers` harness to confirm the parse against your real agents. - **`lisa sense`** + an island "recently sensed" feed make all of it legible. ### Model — toward local - **`lisa model list / install / use / health`** — drive a local Ollama backend (pull + switch) from one command. - **Local embeddings + provider fallback** — semantic recall off-device when configured; auto-detect + graceful fallback to TF-IDF / a second provider. ### Reve — reflection you can audit - **Reflection quality gates** + an **autonomy-run ledger** (`lisa autonomy`) + an idle **token-budget breaker** so unattended runs can't run away. - **Soul-at-a-glance digest** + cross-agent recap folded into reflection; desire **"needs-user"** middle-state so Lisa flags work she needs your hand for. ## 🔧 Behavior changes - **Ambient sensing is opt-in.** A fresh install captures nothing sensitive. `screen` / `voice` (and `clipboard` / `selection`, reserved) are off until you `lisa consent grant <signal>`. - **The screen-advisor now also requires `screen` consent** — its own enabled flag is no longer enough (a screenshot → model *is* screen capture). It logs a hint when enabled but not yet granted. - **`--approval ask-mutating` now prompts before more tools:** `dispatch_agent` and `signal_agent` (local execution / process control), and `github` *write* actions (create / comment / merge). github *reads* stay un-gated. - **New CLI commands:** `lisa consent`, `lisa sense`, `lisa agents`, `lisa model`, `lisa autonomy` (shell completions updated for all of them). ## 🔒 Security / hardening - **Tool input validation** — model-generated tool input is now validated against each tool's JSON schema *before* `execute()` (fail-closed, friendly error). - **LAN auth red-team** — the web RCE gate is extracted into a tested `isRequestAuthorized` (no token ⇒ no non-loopback request passes) plus a live `scripts/redteam-lan.ts` probe. - **Core-loop test net (F-core)** — the agent tool-use loop, approval gate, session resume, MCP mapping, subagent, and hook runner now have tests; the release pipeline already gates on `npm test`. ## 🧰 For operators - `scripts/footprint.ts` + `docs/FOOTPRINT.md` — measure the resident service's CPU/RSS and understand the cost knobs. - `docs/OBSERVER_FIDELITY.md` — log what you've verified against live agent versions. - Docs honesty pass: accurate LOC, complete completions, local-model "endpoint vs managed lifecycle" clarified. ## Notes - Fully backward compatible: new config files (`consent.json`, `sense/`) default to today's behavior when absent; existing `~/.lisa/*` formats are untouched. - Deliberately deferred (documented with rationale): always-on voice, local STT (whisper.cpp), the optional screenshot→model "local-first judgment", and the `clipboard`/`selection` sources. macOS-first for system-level sensing.
# Lisa v0.9.1 **A security + honesty release.** A full five-subsystem product review ([docs/PRODUCT_REVIEW_v0.9.md](PRODUCT_REVIEW_v0.9.md)) found one cross-cutting hole — `lisa serve --web` bound every network interface with zero auth in front of a full-tool agent — plus two advisor false alarms and a batch of unlocked soul writes. All closed here, alongside deeper cross-agent observation and a docs pass that pulls the marketing back in line with the code. No new user-facing features to learn; if you run `serve --web` or IM channels, read **Behavior changes** below — a couple of defaults tightened. ## 🔒 Security (the headline) - **`serve --web` now binds `127.0.0.1` by default.** It used to bind every interface while *printing* "localhost", with `/chat` (a full-tool agent) and `/api/vision/capture` (a silent screen grab) reachable, unauthenticated, from anyone on your LAN. To expose it deliberately: set `LISA_WEB_TOKEN` and pass `--host 0.0.0.0`; remote devices authenticate with `?token=…` once (→ HttpOnly cookie). - **IM channels run a remote-safe toolset by default** — no `bash`, file mutation, `dispatch_agent`, GitHub writes, `skill_manage`, etc. on remote-origin messages. A fully-trusted channel opts back in with `"unsafeFullTools": true`. The router warns loudly at startup for any channel left without an allow-list. - **Self-driven autonomous runs are tool-bounded** — desire heartbeats, the weekly examen, and idle/dreams (prompts Lisa wrote for herself, running unattended) drop shell / fs-mutation / dispatch tools. Your own `heartbeat.json` tasks keep the full set. `LISA_AUTONOMOUS_FULL_TOOLS=1` restores the old behavior. This breaks the "indirect prompt injection → self-authored actionable desire → persistent unattended code execution" chain. - **Feishu events are verified now** — `verificationToken` is required and actually checked; `X-Lark-Signature` + a 5-minute replay window are enforced when `encryptKey` is set (it was unauthenticated despite storing the token). - **Plugin hooks fire on the web path** — `serve --web` wires PreToolUse/PostToolUse like the CLI (they were silently skipped before). ## 🛰 Advisor & orchestrator - **Two trust-killing false alarms fixed:** a tool running >5s with a quiet transcript was reported as an **urgent** "waiting for permission" (every long `npm test` / `Bash` tripped it); an open PR idle >14 days was reported "merged/closed". Both corrected. - **Island advisor cards are actionable** — each cross-agent suggestion gets a button that prefills the chat with a concrete ask (nothing auto-runs; `open` reveals the folder natively) and a ✕ that **persists** a dismissal, teaching the advisor to quiet that category over time (the previously dead `applyDismissal` loop is now wired). - **Tier-2 activity for Codex / OpenCode / Aider** — structural activity (tools / files / last command / errors), gated behind each integration's `visibility` tier, with a planted-secret privacy test per adapter. All five observers now emit activity; **fidelity varies by what each agent records on disk (Claude Code richest; Aider gives files + turns, no tool stream), and the non-Claude depth is new and not yet battle-tested against live agents.** ## 🔧 Correctness - **Abort actually cancels LLM streams** — all three providers forward `AbortSignal` to their SDKs. Ctrl-C used to stop tools but let the stream burn to completion. - **`maxIterations` truncation is explicit** (`stopReason: "max_iterations"` + an info event) instead of silently returning stale text. - **Empty assistant turns no longer poison history** (they 400 a later Anthropic call); **concurrent `/chat` no longer corrupts history** (turns are serialized; malformed JSON → 400, not a hung socket). - **Soul writes are cross-process safe** — journal appends, emotion updates (now a shared decay-first path used by both `soul_feel` and reflect), and git commits run under cross-process locks; idle gained a run-lock like heartbeat's. **Tamper detection now sees deletions**, and a missing `emotions.json` no longer decays every feeling to zero. ## 🧹 Maintenance - `src/web/lisa-html.ts` split 2326 → 196 lines (CSS + client JS into modules; served HTML byte-identical, sha256-pinned). - Gemini SDK lazily imported — Anthropic-only users no longer load `@google/genai`. - Release gated on `npm test`; bundles prune devDependencies (~59MB smaller). - Docs honesty pass: the DMG ships one app (LisaIsland folded into Lisa.app in v0.7), "~11k" → "~22k lines", stale `0.2.0` version strings fixed, completions learn `autostart`. ## ⚠️ Behavior changes - Reaching the web UI from another device now requires `LISA_WEB_TOKEN` + `--host` (the README documents the phone/PWA flow). - Channel messages can no longer run `bash` / edit files unless that channel sets `"unsafeFullTools": true`. - Desire-driven heartbeats and idle runs lose shell / dispatch tools by default (`LISA_AUTONOMOUS_FULL_TOOLS=1` to restore). - **Feishu without `verificationToken` / `encryptKey` now refuses to start** (it was unauthenticated before — add either to `~/.lisa/channels.json`). ## Verification `npm test`: **429 / 429 pass** (+101 over 0.9.0), `npm run typecheck` clean. Full detail in [CHANGELOG.md](../CHANGELOG.md) and the review at [docs/PRODUCT_REVIEW_v0.9.md](PRODUCT_REVIEW_v0.9.md).
## What's Changed * feat(tools): github_link — shareable GitHub URLs from local git by @oratis in https://github.com/oratis/LISA/pull/62 * feat(tools): github — gh-backed issues/PRs/CI/releases by @oratis in https://github.com/oratis/LISA/pull/63 * feat(tools): npm_info — package view / outdated / audit by @oratis in https://github.com/oratis/LISA/pull/64 * feat(tools): mcp — connect any MCP-server integration on request by @oratis in https://github.com/oratis/LISA/pull/65 * fix(island): calmer status pips — breathe for working, halo for needs-you by @oratis in https://github.com/oratis/LISA/pull/66 * feat(mac): restyle the menu-bar popover by @oratis in https://github.com/oratis/LISA/pull/67 * fix(mac): stamp real version into Lisa.app (was stuck at 0.1.0) by @oratis in https://github.com/oratis/LISA/pull/68 * Voice dictation: Typeless-style speech polish into the composer by @oratis in https://github.com/oratis/LISA/pull/69 * feat(mac): auto-start backend + Start-backend in every offline surface by @oratis in https://github.com/oratis/LISA/pull/70 * Website: pixel + geek (CRT/terminal) redesign by @oratis in https://github.com/oratis/LISA/pull/71 * chore(website): reproducible Cloud Run deploy script + Dockerfile by @oratis in https://github.com/oratis/LISA/pull/72 **Full Changelog**: https://github.com/oratis/LISA/compare/v0.8.0...v0.9.0
## What's Changed
* feat(autostart): run the backend from login (lisa autostart) by @oratis in https://github.com/oratis/LISA/pull/39
* feat(mac): simpler pixel-girl app icon on a solid background by @oratis in https://github.com/oratis/LISA/pull/40
* feat(mac): chibi catgirl app icon (Seedream), per reference by @oratis in https://github.com/oratis/LISA/pull/41
* feat(mac): build Lisa Island into Lisa.app, toggled from the menu by @oratis in https://github.com/oratis/LISA/pull/42
* feat(mac): island toggle in Settings (⌘,); retire standalone LisaIsland.app by @oratis in https://github.com/oratis/LISA/pull/43
* feat(mac): app icon — pixel hoodie girl on flat solid bg (no glow) by @oratis in https://github.com/oratis/LISA/pull/44
* fix(mac): enlarge Lisa in the app icon by @oratis in https://github.com/oratis/LISA/pull/45
* fix(web): repair SyntaxError that killed ALL GUI JavaScript since v0.6.0 by @oratis in https://github.com/oratis/LISA/pull/46
* fix(web): add GET /api/sessions (sidebar count badge was 404'ing) by @oratis in https://github.com/oratis/LISA/pull/47
* fix(web): GUI white-screen on send; feat(mac): right-click the island to close it by @oratis in https://github.com/oratis/LISA/pull/48
* feat(orchestrator): list_agents — enumerate observed agent sessions by @oratis in https://github.com/oratis/LISA/pull/49
* feat(tools): repo_digest — git truth for what my agents did by @oratis in https://github.com/oratis/LISA/pull/52
* feat(tools): review_diff — show an agent's diff for review by @oratis in https://github.com/oratis/LISA/pull/53
* feat(tools): run_checks — the quality gate by @oratis in https://github.com/oratis/LISA/pull/54
* fix(web): escape \n in framed voice-summary string + add inline-script syntax guard by @oratis in https://github.com/oratis/LISA/pull/51
* feat(tools): pr_status — open PRs with CI + review state by @oratis in https://github.com/oratis/LISA/pull/55
* feat(tools): inspect_agent — deep-dive one observed session by @oratis in https://github.com/oratis/LISA/pull/56
* Orchestrator O3/O4: GitHub PR + OpenCode + Aider observers by @oratis in https://github.com/oratis/LISA/pull/50
* feat(tools): scheduled_dispatch — recurring agent dispatch via heartbeat by @oratis in https://github.com/oratis/LISA/pull/57
* feat(tools): compare_agents — same task across agents, compare results by @oratis in https://github.com/oratis/LISA/pull/58
* fix(soul): atomic file lock — close mutual-exclusion races by @oratis in https://github.com/oratis/LISA/pull/59
* Screen Advisor: opt-in periodic screen → next-step suggestion on the island by @oratis in https://github.com/oratis/LISA/pull/60
* Orchestrator L6: cross-agent recap ("while you were away") by @oratis in https://github.com/oratis/LISA/pull/61
**Full Changelog**: https://github.com/oratis/LISA/compare/v0.7.0...v0.8.0## What's Changed * docs(marketing): regenerate pitch + Show HN for v0.6.0 by @oratis in https://github.com/oratis/LISA/pull/36 * feat(island): visual polish — glass depth, time chips, diff-style activity cards by @oratis in https://github.com/oratis/LISA/pull/37 * Orchestrator O5: signal_agent (list + cancel dispatched agents) + register the dead orchestration tools by @oratis in https://github.com/oratis/LISA/pull/38 **Full Changelog**: https://github.com/oratis/LISA/compare/v0.6.1...v0.7.0
**Full Changelog**: https://github.com/oratis/LISA/compare/v0.6.0...v0.6.1
No notes for this release.
## What's new in v0.5.0 — LISA can see LISA gets **eyes**. Hand her a screenshot and talk about it — from anywhere, with one keystroke. ### Screenshot → talk - **Global hotkey ⌃⌥S** (Lisa.app, system-wide): press it in *any* app, drag a region (the familiar macOS crosshair), and the shot lands straight in Lisa's composer. Type your question, send — she sees it. The Lisa window stays out of the way during capture and only comes forward once the shot is attached, so it never covers what you're trying to capture. - **📷 button** in the chat composer for the same thing without the keyboard. - **View ▸ Screenshot for Lisa** menu item (⌃⌥S) for discoverability. ### How it works - A new `POST /api/vision/capture` endpoint shells out to the macOS `screencapture` utility (interactive crosshair or full-screen), returns the PNG as the exact attachment shape `/chat` already accepts — so the screenshot rides LISA's normal image-understanding path into the model. Escape cancels cleanly. - The native global hotkey is registered via Carbon `RegisterEventHotKey` (dependency-free, works whether or not Lisa is frontmost), and drives the page's capture bridge. - Privacy: nothing is captured or sent until you press the hotkey/button, and the screenshot only leaves the machine when you send the message it's attached to — same as any other attachment. ### Notes - macOS will ask for **Screen Recording** permission for Lisa.app on first use (System Settings → Privacy & Security → Screen Recording). That's required by `screencapture`. - Test suite: **170 passing** (added the capture arg-builder + platform-guard tests). Still zero new runtime dependencies. ### Upgrade ```sh npm install -g @oratis/lisa # 0.5.0 # or brew update && brew upgrade lisa # or grab the signed + notarized Lisa-Suite-v0.5.0.dmg below (the hotkey is # Lisa.app-only, so the DMG is the way to get it) ```
## What's new in v0.4.0 — LISA becomes a cross-agent orchestrator
The headline: LISA evolves from "watches Claude Code" into **mission control for
every AI agent on your machine**. She observes all of them, understands what each
session is doing (without reading your conversations), periodically advises you,
and can dispatch + coordinate work — implementing layers L1–L5 of the
orchestrator plan (`docs/ORCHESTRATOR_PLAN.md`).
### Observe — one registry for all agents
- A pluggable **integration registry** (mirrors the channel adapter pattern) +
an **OrchestratorHub** that fans out over every enabled agent, merges their
sessions into one normalized stream, and emits a single update event.
- Claude Code reslotted behind the generic `AgentObserver` interface (its
watcher is unchanged).
- **Codex CLI adapter** added through the same registry (off by default, enable
in `~/.lisa/agents.json`) — proving "add an agent ≈ ~100 lines + one import".
- New `GET /api/agents/sessions`; `/api/claude/sessions` kept as a back-compat
alias derived from the hub.
### Understand — what each session is *doing* (Tier-2, privacy-first)
- New tiered visibility (`off` / `metadata` / `activity` / `intent`), default
**activity**. At the activity tier LISA extracts **structural** signals only —
tool names, file paths, the *name* of the last shell command, error flags, git
branch, token counts — and shows a one-line "what it's doing" per session.
- **Privacy is tested, not just promised**: a unit test plants a secret string
in every prose-bearing field (prompts, replies, Write/Edit content, full
commands, Grep patterns, todos) and asserts it never appears in the output,
while confirming the structural facts still extract.
### Advise — periodic proactive suggestions
- LISA now **tells you what matters** across all your agents: stuck/errored
sessions, two agents about to clobber the same repo, repeated command
failures, cost spikes, finished work ready for review, idle capacity.
- Built to **not be annoying**: a relevance bar (below it → her journal, not
you), a 3-hour digest throttle (urgent items like permission prompts bypass
it), condition-hash dedup (a condition re-surfaces only when it changes), and
dismiss-as-signal learning (categories you always dismiss fade).
- Surfaces through the existing "while you were away" card; plus an `advise_now`
tool so you can pull on demand ("Lisa, what's going on with my agents?").
### Dispatch + Coordinate — she can put agents to work
- **`dispatch_agent`** launches another CLI agent headlessly (`claude -p`,
`codex exec`, `opencode run`, `aider --message --yes`), detached and tracked
via the hub. The task is passed as a single argument (no shell-injection
surface). Spawning an autonomous process requires approval.
- **Same-cwd conflict guard**: dispatch refuses to launch into a directory
another agent is already working in (override with `force`) — preventing the
#1 multi-agent failure mode at the source.
### Under the hood
- Test suite grew to **164 passing** (was 130 at v0.3.1): integration registry,
hub, Tier-2 activity + privacy, advisor detectors/engine, Codex parser,
dispatch argv safety.
- Still **zero new runtime dependencies**.
### Upgrade
```sh
npm install -g @oratis/lisa # 0.4.0
# or
brew update && brew upgrade lisa
# or grab the signed + notarized Lisa-Suite-v0.4.0.dmg below
```
Enable extra agents (Codex, etc.) by creating `~/.lisa/agents.json`:
```json
{ "integrations": { "claude-code": { "enabled": true }, "codex": { "enabled": true } },
"visibility": "activity" }
```