The <details> htmlPrefix path was an internal experiment that
shouldn't have shipped — it replaced the working per-channel
reasoning display with silent collection and a HTML block that
doesn't render reliably in Element. Restoring the upstream
formatReasoningDisplay so reasoning shows inline as it arrives.
The 18010eb commit deleted several upstream features from main.ts:
- resolveSessionMemfs and sleeptime/memfs integration
- parseOptionalBoolean, parseHeartbeatSkipRecentPolicy helpers
- heartbeat skip-recent policy/fraction/interrupt config
- duplicate agent name and turnLogFile validation
- turn log file collection for API server
- logging and blueskyEnabled bot config
Restores all upstream code while preserving our Matrix additions:
parseHeartbeatTarget first-colon fix, MatrixAdapter callback wiring,
heartbeatTargetChatId, sessionModel, API key propagation, Olm crash fix.
Replace hardcoded @ani:wiuf.net with this.config.userId in
onMessageSent(). Remove casey-specific comment in key import.
Authored-by: Ani Tunturi <ani@wiuf.net>
Reverts Ani-specific personalizations (first-person voice, "Casey",
"Cogito ergo sum") from the default heartbeat prompt. These belong
in the user's promptFile, not upstream source.
Adds targetRoom parameter to buildHeartbeatPrompt and
buildCustomHeartbeatPrompt so the agent knows which room to
message into.
Authored-by: Ani Tunturi <ani@wiuf.net>
Matrix !commands that duplicated upstream bot logic (!new, !status,
!showreasoning, !restore) are removed. !reset, !cancel, !status,
!model, and bare !heartbeat now delegate to the upstream /reset,
/cancel, /status, /model, /heartbeat handlers via onCommand callback.
This fixes !new (now !reset) which only invalidated the session
without clearing the conversation from the store, causing stuck
processes to reconnect to poisoned conversations.
Matrix-specific commands (!pause, !resume, !ignorebot-*, !turns,
!timeout, !heartbeat on/off) remain local since they manage
per-room state with no upstream equivalent.
Authored-by: Ani Tunturi <ani@wiuf.net>
Extracts a DisplayPipeline async generator that wraps the raw SDK stream
and yields clean DisplayEvent types. Refactors processMessage() to consume
pipeline events instead of raw StreamMsg objects.
- Locks foreground on first substantive event (reasoning/tool_call/etc),
eliminating buffering delay for real-time display
- Filters pre-foreground error/retry events to prevent false approval recovery
- Re-throws 429 in rejectApproval to prevent rate-limit loops
- Gates reasoning log on display config
- 12 pipeline unit tests + updated integration tests (56 total)
- Net -224 lines from bot.ts
Written by Cameron ◯ Letta Code
"The purpose of abstraction is not to be vague, but to create a new
semantic level in which one can be absolutely precise." -- Edsger Dijkstra
Address review findings from self-review and codex:
- Commands (/reset, /cancel) now receive forcePerChat from the Discord
adapter and resolve the correct per-thread conversation key
- Group batcher propagates forcePerChat to synthetic batch messages so
debounced thread messages don't fall back to shared routing
- Reaction handler sets forcePerChat for thread-only reactions
- Session LRU eviction fires regardless of conversationMode so
forcePerChat sessions don't accumulate without bounds
- Docs now correctly state thread-only overrides shared/per-channel
modes (not disabled mode)
Written by Cameron ◯ Letta Code
"The best way to predict the future is to implement it." -- David Heinemeier Hansson
Thread-only mode previously relied on the global conversationMode to
determine whether threads got separate conversations. In shared or
per-channel mode, all threads shared one conversation causing crosstalk.
Add forcePerChat flag on InboundMessage that the Discord adapter sets
when thread-only mode is active. resolveConversationKey treats flagged
messages as per-chat regardless of the configured mode, giving each
thread its own isolated conversation while still sharing agent memory.
Written by Cameron ◯ Letta Code
"Time is an illusion. Lunchtime doubly so." -- Douglas Adams