feat(soul): surface the most recently active desire as current#250
Merged
Conversation
The "current desire" shown in the room/island ticker (and now `status`) was `desires.find(d => d.actionable)` over fs.readdir order — an arbitrary, static pick decoupled from recency and relevance. So even once desires started changing, the surfaced one didn't. Add two store helpers: - pickCurrentDesire(desires, activityAt?) — pure. Prefers actionable desires; within the pool, most-recently-active wins; falls 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 (add/revise/pursue/close) — fidelity, not per-request flicker. - desireActivity(desires) — cheap fs.stat over each <slug>.md and <slug>.progress.md, so activity reflects both authoring and pursuit. Wire into GET /api/island/ping (which the room, island, and web client all read, so all three update at once) and mark the pick in `lisa status`. PR 3 of 3 from docs/PLAN_DESIRE_EVOLUTION_v1.0.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pick-desire.test.ts covers the pure pickCurrentDesire; this covers the I/O half — desireActivity reading real desire/progress file mtimes off disk (pinned via fs.utimes) and feeding pickCurrentDesire end-to-end, including the pursuit-refreshes-an-old-desire case and the bornAt floor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tie-break Per review of #247: desireFile()/desireProgressFile() run assertSafeSlug, which throws on an unsafe slug, but listDesires() derives slugs from raw filenames without validating. A stray file (e.g. a macOS `._x.md` AppleDouble in the git-synced soul dir) made desireActivity reject — which /api/island/ping caught (→ null) but `lisa status` did NOT, aborting the command. Move the path construction inside the try so such a desire is skipped, flooring at bornAt. Also give pickCurrentDesire a deterministic secondary sort key (slug) so an exact recency tie no longer falls back to fs.readdir order — the accident the PR removes. Adds an unsafe-slug regression test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
oratis
added a commit
that referenced
this pull request
Jul 15, 2026
…cus gate (#251) From the #248/#249 reviews (the two MED items not already handled by #250's review fix, which added the withSoulLock + desireActivity guard): - Persisted CLOSED marker (#249): DesireEntry gains `closed`; writeDesire serializes `closed: yes` and now keeps heartbeatPrompt/pursuit even when not actionable, so closing preserves them for a clean re-open. closeDesire flips closed on (+ actionable off) and is idempotent — an already-closed desire is a no-op, so it can't append duplicate [DESIRE_CLOSED]/[CLOSED] notes. reviseDesire clears closed when it re-opens (actionable:true). Closed desires are filtered out of the reflector's "revise or close" block (so the list actually shrinks) and never surface as her current/focused desire (ping + `lisa status`). - Restart-safe focus freshness (#248): the intra-session focus gate keyed on reflectClock.idleFor(), which reads "fresh" right after a launchd restart and would pin focus onto a stale resumed conversation. Gate on a new lastUserMessageAt (0 until a real POST /chat arrives, so a restart closes the gate until the user actually talks). Adds closed-marker/idempotency/re-open tests. Full suite green (1013 pass). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR3 of the desire-evolution plan (#242 → #249 → this). Supersedes #247, which GitHub auto-closed when its base branch
claude/desire-reflect-evolvewas deleted on #249's merge (can't reopen a closed PR whose base is gone). Same branch, rebased onto main, plus the review fix below.What
pickCurrentDesire(desires, activityAt?)— pure. Prefers actionable; within that pool most-recently-active wins; falls back to most-recently-born. Order-independent, stable between events.desireActivity(desires)—fs.statover each<slug>.md+<slug>.progress.md, so "activity" tracks both authoring (add/revise) and pursuit (heartbeat progress).GET /api/island/ping(room + island + web client all consume it) and marked← currentinlisa status.Review fixes folded in
desireActivityno longer rejects on an unsafe slug (a stray._x.mdAppleDouble in the git-synced soul dir would abortlisa status) — path construction moved inside the try; regression test added.pickCurrentDesiregiven a deterministic slug secondary key so an exact recency tie doesn't fall back tofs.readdirorder.Reviewed + tested:
pick-desire.test.ts+desire-activity.test.tsgreen, typecheck clean.🤖 Generated with Claude Code