Commit Graph

27 Commits

Author SHA1 Message Date
Ani Tunturi
7c346d570b feat(conscience): Aster reset commands, subagent thread chunking, conscience wiring
[IN TESTING — production on ani@wiuf.net, treat as experimental]

bot.ts — !reset aster cycles only Aster's conversation (leaves Ani's alone),
patches the systemd service file in place so restarts also use the new conv ID.
Full !reset now co-cycles Aster's conversation alongside Ani's so failure
notifications target the active context. Both commands write through to
lettabot-agent.json and daemon-reload immediately.

bot.ts — subagent thread results are now chunked at 16KB before posting to
Matrix threads. Previously truncated at 800 chars, cutting results mid-sentence.

store.ts / letta-api.ts — createConversationForAgent exposed for use by reset
commands. Store gains setAgentField for targeted JSON updates without clobbering.

config/types.ts, channels/factory.ts — conscience env var plumbing (CONSCIENCE_AGENT_ID,
CONSCIENCE_CONVERSATION_ID) wired through the config surface.

memfs-server.py — git sidecar for local memfs serving (port 8285). Serves bare
repos from ~/.letta/memfs/repository/ over HTTP. Required by letta-code memfs
in self-hosted mode.
2026-03-26 23:25:27 -04:00
Ani Tunturi
9af2d2625f fix(letta-api): restore 429 rethrow in rejectApproval
Re-throws rate limit errors so callers bail out early instead of
hammering the API in a tight loop. This was accidentally dropped
during the merge.
2026-03-17 13:43:17 -04:00
Ani Tunturi
7a7393b8c1 merge: incorporate upstream fixes (cancel invalidation, approval dedup, cron logging) 2026-03-16 15:01:27 -04:00
Cameron
695c5bc665 fix: deduplicate approval requests across runs (#601) (#602) 2026-03-15 22:45:15 -07:00
Ani Tunturi
18010eb14f feat: Matrix adapter with E2EE, TTS/STT, reactions, and heartbeat routing
Full Matrix channel integration for LettaBot:

- E2EE via rust crypto (ephemeral mode, cross-signing bootstrap)
- Proactive SAS verification with Element clients
- TTS (VibeVoice) and STT (Faster-Whisper) voice pipeline
- Streaming message edits with 800ms throttle
- Collapsible reasoning blocks via <details> htmlPrefix
- Per-tool emoji reactions (brain, eyes, tool-specific, max 6)
- Heartbeat room conversation routing (heartbeatTargetChatId)
- Custom heartbeat prompt with first-person voice
- Per-room conversation isolation (per-chat mode)
- !pause, !resume, !status, !new, !timeout, !turns commands
- Audio/image/file upload handlers with E2EE media
- SDK 0.1.11 (approval recovery), CLI 0.18.2

Tested against Synapse homeserver with E2EE enabled for 2+ weeks,
handles key backup/restore and device verification.
2026-03-14 21:27:32 -04:00
Cameron
5bed4e78cd fix(recovery): deny orphaned approvals sequentially for parallel tool calls (#580)
Co-authored-by: Letta Code <noreply@letta.com>
2026-03-12 18:37:27 -07:00
Cameron
13426396ac refactor: extract DisplayPipeline from processMessage stream loop (#550)
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
2026-03-11 16:22:26 -07:00
Jason Carreira
e38f5a4db7 fix(session): auto-clear stuck conversation on invalid tool call ID mismatch (#555)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 15:18:02 -07:00
Cameron
69cd7e5225 fix: recover default-conversation approval deadlocks without conversation reset (#541)
Co-authored-by: Letta Code <noreply@letta.com>
2026-03-09 18:15:52 -07:00
Cameron
cb563d617b fix: detect and recover from stuck 409 PENDING_APPROVAL errors (#478) 2026-03-03 15:37:36 -08:00
Cameron
6fa8c98924 feat: /cancel slash command to abort active agent runs (#422) 2026-02-26 17:14:07 -08:00
Cameron
fb820ee89a fix: deduplicate tool_call_ids in orphaned approval recovery (#414) 2026-02-26 15:07:33 -08:00
Cameron
d283f837ac refactor: migrate runtime console.log calls to structured logger (#397) 2026-02-26 10:16:38 -08:00
Cameron
9975568831 feat(core): proactive approval detection via SDK bootstrapState() (#383) 2026-02-24 11:58:44 -08:00
Cameron
1cad6e6508 feat(core): structured logging with pino (#368) 2026-02-23 16:35:23 -08:00
Cameron
7028f042af fix: display-reasoning stream fixes and Signal compatibility (#355) 2026-02-23 15:00:17 -08:00
Cameron
9c0228d414 fix: recover from active runs stuck on approval (#249)
* fix: recover from active runs stuck on approval

recoverOrphanedConversationApproval() only resolved approvals from
terminated (failed/cancelled) or abandoned (completed/requires_approval)
runs. Active runs with status=running and stop_reason=requires_approval
were skipped, even though they block the entire conversation.

This happens when the CLI's HITL approval flow leaves a pending approval
that no client will resolve (e.g. agent triggered via ADE, user walked
away). Every subsequent message fails with success=false.

Now also handles running+requires_approval: rejects the approval and
cancels the stuck run so lettabot can proceed.

Fixes the recovery path for all three call sites: pre-send check,
CONFLICT catch, and error-result retry.

Written by Cameron ◯ Letta Code

"The best error message is the one that never shows up." -- Thomas Fuchs

* fix: use cancelRuns return value for accurate diagnostics

Check boolean return from cancelRuns() instead of always logging
success. Details string now shows "(cancel failed)" when it didn't
work.

Written by Cameron ◯ Letta Code

"Trust, but verify." -- Ronald Reagan

* fix: detect terminal errors and retry with recovery in bot.ts

Monkey patch until SDK issue letta-code-sdk#31 is resolved.

1. Detect terminal errors (success=false OR error field), not just
   empty-success. Both trigger orphan recovery + retry.

2. Blind retry on terminal error when no orphaned approvals found --
   client-side approval failures may leave no detectable record.

3. sendToAgent() throws on terminal error instead of returning empty
   string. Background callers get actionable errors.

Written by Cameron ◯ Letta Code

"A good patch is one you can remove." -- unknown

* test: add tests for approval recovery including stuck runs

7 tests covering recoverOrphanedConversationApproval():
- Empty conversation
- No unresolved approvals
- Recovery from failed run
- Recovery from stuck running+requires_approval (with cancel)
- Already-resolved approvals skipped
- Healthy running run not touched
- Cancel failure reported accurately

Written by Cameron ◯ Letta Code

"Code without tests is broken by design." -- Jacob Kaplan-Moss

* fix: gate retry on sentAnyMessage to prevent duplicate delivery

Codex review caught that finalizeMessage() clears the response buffer
on type changes, so hasResponse could be false even when output was
already sent. This caused duplicate retries with potential side effects.

Now checks !sentAnyMessage (authoritative delivery flag) in addition
to !hasResponse before retrying.

Written by Cameron ◯ Letta Code

"Idempotency: the art of doing nothing twice." -- unknown
2026-02-09 21:16:52 -08:00
letta-code
7e82374865 fix: remove model field from lettabot config, add lettabot model command
The model field in lettabot.yaml was redundant and misleading -- the model
is configured on the Letta agent server-side, and lettabot shouldn't
override it. Users were confused seeing a model in their startup log that
didn't match the actual model being used.

Changes:
- Remove `model` from `LettaBotConfig.agent` (made optional for backward compat)
- Remove `model` from `BotConfig` interface and `bot.ts` createAgent() calls
- Remove `model` from `main.ts` config construction and startup log
- Stop saving `model` to lettabot.yaml during onboarding
- Stop mapping `agent.model` to `MODEL` env var in config/io.ts
- Add `getAgentModel()` and `updateAgentModel()` to letta-api.ts
- Add new `src/commands/model.ts` with three subcommands:
  - `lettabot model` - interactive model selector
  - `lettabot model show` - show current agent model
  - `lettabot model set <handle>` - set model directly
- Wire up model command in cli.ts with help text
- Update docs/configuration.md, lettabot.example.yaml, SKILL.md

Model selection during `lettabot onboard` is preserved for new agent
creation -- the selected model is passed to createAgent() but is NOT
saved to the config file.

Fixes #169

Co-authored-by: Cameron <cpfiffer@users.noreply.github.com>
2026-02-06 20:52:26 +00:00
Cameron
0d32e05906 fix: orphaned approval recovery, empty-result retry, deploy stability (#183)
Fixes #180. See #194 for full analysis.
2026-02-06 10:42:41 -08:00
Cameron
3ff33fee87 fix: client-side defensive recovery for orphaned approval_request_messages (#182)
When a conversation has an orphaned approval_request_message from a
cancelled/failed run, every subsequent message fails with 409 CONFLICT.
The existing attemptRecovery() can't find these because it checks
agent.pending_approval (null) and scans runs with stop_reason=requires_approval
(but the orphaned run is cancelled/failed).

Adds recoverOrphanedConversationApproval() which directly inspects conversation
messages, finds unresolved approval requests, verifies the originating run is
terminated, and sends denials to clear the stuck state. Both processMessage()
and sendToAgent() now catch CONFLICT errors and retry once after recovery.

Fixes #180

Written by Cameron ◯ Letta Code

"It is not the strongest of the species that survives, nor the most intelligent,
but the one most responsive to change." - Charles Darwin
2026-02-05 17:46:44 -08:00
Cameron
3b7150013c fix: approval detection missing include param + /reset command + startup check (#175)
getPendingApprovals() was calling agents.retrieve() without the
include: ['agent.pending_approval'] parameter, so the Letta API never
returned the pending_approval field. This caused stuck server-side tool
approvals (requires_approval=true) to go undetected, leaving the agent
permanently stuck with empty responses.

Also adds:
- Proactive ensureNoToolApprovals() on startup to disable requires_approval
- /reset slash command for deployed instances (clears conversation, keeps memory)
- Robust parsing for ToolCallDelta and deprecated tool_call field formats

Written by Cameron ◯ Letta Code

"The only way to do great work is to love what you do." - Steve Jobs
2026-02-05 17:38:48 -08:00
Jason Carreira
4c860c748d Fix approval recovery and watchdog sendToAgent (#157)
Co-authored-by: Jason Carreira <jason@visotrust.com>
2026-02-05 08:09:51 -08:00
Cameron
d6113cab66 fix: graceful transcription fallback when ffmpeg unavailable (#155)
* fix: graceful transcription fallback when ffmpeg unavailable

When voice transcription fails (e.g., ffmpeg not installed), the agent
now receives informative error messages instead of silent failures.

Changes:
- transcribeAudio() returns TranscriptionResult with success/error/audioPath
- Tiered fallback: try format rename first, then ffmpeg, then fail gracefully
- Check ffmpeg availability once and cache result
- All channel adapters updated to show transcription errors to agent
- Agent can explain to user why transcription failed

Before:
  Agent sees: "[Voice message received]"
  Agent: "I received your voice message but there's no content..."

After:
  Agent sees: "[Voice message - transcription failed: Cannot transcribe .aac format. Install ffmpeg for audio conversion, or send in a supported format (mp3, ogg, wav, m4a). Audio saved to: /path/to/file.aac]"
  Agent: "I couldn't transcribe your voice message because ffmpeg isn't installed. You could type your message instead."

Fixes voice transcription on systems without ffmpeg.

Written by Cameron ◯ Letta Code

"Fail gracefully, inform clearly." - Error handling wisdom

* fix: handle undefined transcription errors better

* fix: correct API param for tool approval + workaround letta-client type bug
2026-02-04 19:31:50 -08:00
Cameron
fe233b2f8f feat: add e2e tests with Letta Cloud (#149)
E2E testing infrastructure that tests the full message flow against a real Letta Cloud agent.

Changes:
- Add MockChannelAdapter for simulating inbound/outbound messages
- Add e2e/bot.e2e.test.ts with 4 e2e tests:
  - Simple message/response
  - /status command
  - /help command
  - Conversation context retention
- Add 'mock' to ChannelId type
- Update CI workflow with separate e2e job (uses secrets)
- Add npm run test:e2e script

E2E tests require:
- LETTA_API_KEY (already in repo secrets)
- LETTA_E2E_AGENT_ID (needs to be added)

E2E tests are skipped locally without these env vars.

Written by Cameron ◯ Letta Code

"Trust, but verify." - Ronald Reagan (on e2e testing)
2026-02-04 17:51:23 -08:00
Cameron
75f65049bc feat: Railway deployment support with one-click deploy (#106)
* feat: add Railway deployment support with agent auto-discovery

- Add railway.toml for build/deploy config with health checks
- Skip config file requirement when RAILWAY_ENVIRONMENT detected
- Auto-discover existing agent by name on container deploys
- Add findAgentByName() API function for agent lookup
- Add setAgentId() method to LettaBot class
- Add comprehensive Railway deployment docs

One-click deploy flow:
1. Set LETTA_API_KEY + channel tokens
2. LettaBot finds existing agent by AGENT_NAME (default: "LettaBot")
3. If not found, creates on first message
4. Subsequent deploys auto-reconnect to same agent

Written by Cameron ◯ Letta Code

"The best way to predict the future is to deploy it." - Railway, probably

* fix: specify Node 22 for Railway deployment

* fix: fail fast if LETTA_API_KEY is missing

* fix: don't await Telegram bot.start() - it never resolves

* fix: extract message from send_message tool call

* Revert "fix: extract message from send_message tool call"

This reverts commit 370306e49de3728434352d2df1b78c744e888833.

* fix: clear LETTA_AGENT_ID env var when agent doesn't exist

* docs: add Railway deploy button to README and docs

* fix: .nvmrc newline and correct MODEL default in docs
2026-02-03 14:46:37 -08:00
Cameron
13d84245c5 feat: add X-Letta-Source header for telemetry (#73)
Add "X-Letta-Source: lettabot" header to Letta API client
for usage tracking/telemetry.

Closes #72

Written by Cameron ◯ Letta Code

"If you can't measure it, you can't improve it." - Peter Drucker
2026-02-02 16:19:17 -08:00
Sarah Wooders
22770e6e88 Initial commit - LettaBot multi-channel AI assistant
Co-authored-by: Cameron Pfiffer <cameron@pfiffer.org>
Co-authored-by: Caren Thomas <carenthomas@gmail.com>
Co-authored-by: Charles Packer <packercharles@gmail.com>
Co-authored-by: Sarah Wooders <sarahwooders@gmail.com>
2026-01-28 18:02:51 -08:00