Commit Graph

176 Commits

Author SHA1 Message Date
Cameron
b1d69965b5 feat: add <no-reply/> silent marker for agent opt-out (#196)
* feat: add {{NO_REPLY}} silent marker for agent opt-out

Allow the agent to respond with {{NO_REPLY}} to suppress message
delivery for messages that don't warrant a reply. The marker is
checked in three places: the streaming edit guard (prefix match to
prevent partial sends), finalizeMessage(), and the post-stream
response handler.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: use <no-reply/> XML marker instead of {{NO_REPLY}}

Switch to XML-style self-closing tag for consistency with the XML
envelope format used elsewhere, and because LLMs produce well-formed
XML tags more reliably than template syntax.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add no-reply hint to group chat envelopes

Agents created outside lettabot (via ADE, Letta Cloud) won't have the
system prompt telling them about <no-reply/>. Adding the hint to group
chat envelopes makes the opt-out mechanism self-documenting.

Written by Cameron ◯ Letta Code

"Silence is one of the great arts of conversation." -- Marcus Tullius Cicero

---------

Co-authored-by: Gabriele Sarti <gabriele.sarti996@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 11:08:56 -08:00
Ari Webb
04f58e72c8 feat: add ergonomic channel management CLI (#188)
* feat: add ergonomic channel management CLI

Add `lettabot channels` command for easier channel management:
- `lettabot channels` - Interactive menu
- `lettabot channels list` - Show status of all channels
- `lettabot channels add <channel>` - Add with focused setup
- `lettabot channels remove <channel>` - Remove/disable
- `lettabot channels enable/disable <channel>` - Quick toggle

This makes it much easier to add a single channel without going
through the full onboard wizard. For example, adding Discord
after already having Telegram configured now only requires
the Discord-specific prompts.

Also fixes test for /reset command (was added but test not updated).

🐙 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* refactor: remove enable/disable commands from channels CLI

Simplify the channels CLI to just add/remove. The enable/disable
commands were redundant - users can use `add` to reconfigure
and `remove` to disable.

🐙 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>

* clean up

---------

Co-authored-by: Letta <noreply@letta.com>
2026-02-06 10:58:09 -08:00
Gabriele Sarti
b1e1b5693c feat: make polling interval configurable via lettabot.yaml (#181)
Add `pollIntervalSec` to the Google integration config so the email
polling interval can be set in lettabot.yaml instead of requiring
the POLLING_INTERVAL_MS environment variable.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 10:50:22 -08:00
Gabriele Sarti
8cd48d9f54 fix: abort agent when stuck in tool-call loop (#185)
Add a configurable maxToolCalls safeguard (default: 100) that aborts the
session when the agent enters an infinite tool-calling loop. The stream
watchdog didn't catch this because the stream was active (sending
tool_call events), just not productive.

Configurable via lettabot.yaml:
  features:
    maxToolCalls: 100

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 10:43:37 -08: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
5dea82acc5 fix: pin baileys to 6.7.21 and fix stale command count test (#193)
The ^6.7.21 caret range resolves to 6.17.16 on fresh npm install, which
ships incompatible TypeScript types (no call signatures on default export).
Pins exact version and adds explicit type annotation on getMessage key param.

Also updates commands.test.ts to expect 5 commands (reset was added but
test still expected 4).

Fixes #192

Written by Cameron ◯ Letta Code

"The caret giveth, and the caret taketh away."
2026-02-06 10:30:04 -08:00
Cameron
2373dbb3b1 fix: add npm overrides for keyv resolution (#162)
* fix: telegram ESM compatibility and improved diagnostics

- Replace telegram-markdown-v2 with telegramify-markdown (ESM compatible)
- Add raw text fallback when Telegram formatting fails, with error notice
- Improve empty response diagnostics: log agent ID, show conversation ID
- Add reset-conversation command hint to user messages
- Add telegram-format.test.ts with 7 tests

Fixes Railway deployment ERR_REQUIRE_ESM error with remark package.

Written by Cameron and Letta Code

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

* fix: add npm overrides for keyv resolution

Users were still hitting ERR_MODULE_NOT_FOUND for keyv even after
PR #154 added it as a direct dependency. This happens because npm's
hoisting doesn't always resolve peer deps of optional deps properly.

npm overrides force the package manager to use our root keyv version
for all nested references, which is the idiomatic solution for
transitive peer dependency issues.

Also adds troubleshooting entry to README.

Written by Cameron ◯ Letta Code

"The best error message is the one that never shows up."
- Thomas Fuchs
2026-02-06 10:25:30 -08:00
dependabot[bot]
49db36f52f chore(deps): bump @letta-ai/letta-client from 1.7.7 to 1.7.8 (#186)
Bumps [@letta-ai/letta-client](https://github.com/letta-ai/letta-node) from 1.7.7 to 1.7.8.
- [Release notes](https://github.com/letta-ai/letta-node/releases)
- [Changelog](https://github.com/letta-ai/letta-node/blob/main/CHANGELOG.md)
- [Commits](https://github.com/letta-ai/letta-node/compare/v1.7.7...v1.7.8)

---
updated-dependencies:
- dependency-name: "@letta-ai/letta-client"
  dependency-version: 1.7.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 10:25:27 -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
Cameron
0bed2cc166 fix: replace deprecated discord.js 'ready' event with 'clientReady' (#178)
The 'ready' event is deprecated in discord.js v14 and will break in v15.
Also switch from on() to once() since this event fires only once per login.

Reported-by: Aeo

Written by Cameron ◯ Letta Code

"The quietest fix is the one that prevents the loudest crash." -- a wise linter
2026-02-05 16:19:47 -08:00
Cameron
002fa48b2a docs: add releasing guide (#177)
Documents the automated release workflow:
- How to tag and push releases
- Pre-release detection (alpha/beta/rc)
- Versioning strategy (semver)
- Release checklist
- Links to npm publish tracking issue (#174)

Written by Cameron ◯ Letta Code

"Ship it." - GitHub
2026-02-05 15:13:19 -08:00
Cameron
610f2a64eb fix: remove broken letta-code notification step from release workflow (#176)
The `if: ${{ secrets.LETTA_API_KEY != '' }}` expression caused a
workflow parse error (secrets can't be used in `if` conditions).
Also `custom_prompt` may not be a valid input for letta-code-action.

Stripped the step for now -- will re-add once letta-code-action
supports release events.

Written by Cameron ◯ Letta Code

"Simplicity is the ultimate sophistication."
- Leonardo da Vinci
2026-02-05 15:07:40 -08:00
Cameron
9bd0134f72 feat: add automated GitHub Release workflow (#173)
Triggers on version tags (v*). Workflow:
1. Builds and runs tests (gate)
2. Generates release notes from merged PRs since last tag
3. Creates GitHub Release (with pre-release detection for alpha/beta/rc)
4. Optionally pings letta-code agent to write a friendly summary

Usage:
  git tag v0.1.0
  git push origin v0.1.0

Written by Cameron ◯ Letta Code

"Release early, release often."
- Eric S. Raymond
2026-02-05 14:58:01 -08:00
Cameron
4bb1e584cf fix: WhatsApp self-chat detection + debug logging (#171)
Fixes silent message drops for selfChat users by fixing an
inconsistency between two self-chat detection mechanisms:

1. `isSelfChatMessage()` in utils.ts correctly detects LID-based
   self-chat (common on newer WhatsApp versions)
2. `isExtractedSelfChat` in extract.ts only checked `from === selfE164`,
   missing LID-based self-chat entirely

When these disagreed, the fromMe filter would pass but the
selfChatMode filter would drop the message silently.

Also adds DEBUG_WHATSAPP=1 environment variable for verbose logging
at all message filter points - helps diagnose similar issues.

Written by Cameron ◯ Letta Code

"Debugging is twice as hard as writing the code in the first place."
- Brian Kernighan
2026-02-05 13:58:17 -08:00
Cameron
7db7f35804 feat: standardize message envelopes around <system-reminder> XML tags (#172)
Replace bracket-based message envelopes with XML system-reminder tags
matching Letta Code CLI conventions. The agent already recognizes these
tags, so this standardizes how lettabot sends metadata.

Changes:
- Rewrite formatMessageEnvelope() to use <system-reminder> tags
- Add Session Context section for first message in new chat sessions
- Add Chat Context section (group info, mentions, reply context)
- Move format hints into structured metadata fields
- Export SYSTEM_REMINDER constants and SessionContextOptions
- Update all 36 tests to validate new XML format + add session tests

Closes #170

Written by Cameron ◯ Letta Code

> "The best code is the code that speaks the same language everywhere." - Unknown
2026-02-05 13:54:44 -08:00
Cameron
c85c4a3272 fix: telegram ESM compatibility and improved diagnostics (#161)
- Replace telegram-markdown-v2 with telegramify-markdown (ESM compatible)
- Add raw text fallback when Telegram formatting fails, with error notice
- Improve empty response diagnostics: log agent ID, show conversation ID
- Add reset-conversation command hint to user messages
- Add telegram-format.test.ts with 7 tests

Fixes Railway deployment ERR_REQUIRE_ESM error with remark package.

Written by Cameron and Letta Code

"The best error message is the one that never shows up." - Thomas Fuchs
2026-02-05 10:31:53 -08:00
Cameron
257da79e94 fix: allow selfhosted mode without LETTA_API_KEY (#160)
Users were confused why selfhosted mode still required an API key.
The validation check now properly skips the API key requirement when
server.mode is 'selfhosted'.

Also adds comprehensive selfhosted-setup.md guide covering:
- Letta Docker server setup
- Network configuration (Docker, remote servers)
- Troubleshooting (connection refused, stuck agent, tool approvals)
- Running as a service (systemd, launchd)
- Hardware requirements

Thanks to oculairthebear for the community guide that inspired this.

Written by Cameron and Letta Code

"I've learned that people will forget what you said, people will forget
what you did, but people will never forget how you made them feel."
- Maya Angelou
2026-02-05 10:27:33 -08:00
Cameron
407cd2f18d fix: improve empty response error message (no reset suggestion) (#159)
Changed from unhelpful "(No response from agent)" to more informative
message asking user to try again. Added agent/conversation IDs to logs
for debugging.

Avoids suggesting conversation reset - focuses on retrying.

Written by Cameron ◯ Letta Code
2026-02-05 09:43:58 -08:00
Cameron
1ff0aede9d fix: check for pending approvals before rejecting due to max attempts (#158)
The recovery logic was checking the attempt counter BEFORE checking for
pending approvals. If the counter was >= 2 (from previous failures),
ALL future messages would fail immediately without ever checking if
there were actually pending approvals (which would reset the counter).

Now:
1. Check for pending approvals first
2. If none, reset counter and continue (this was blocked before)
3. Only then check if we've exceeded max attempts

This fixes the case where previous API errors incremented the counter,
but there are no longer any actual pending approvals.

Written by Cameron ◯ Letta Code
2026-02-05 09:30:10 -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
b4058f17ce fix: add keyv as direct dependency (#154)
keyv is a transitive dependency of Baileys via @cacheable/utils,
but since Baileys is in optionalDependencies, some npm versions
don't properly install its transitive dependencies on fresh installs.

Adding keyv as a direct dependency ensures it's always installed.

Fixes ERR_MODULE_NOT_FOUND: Cannot find package 'keyv' on fresh installs.

Written by Cameron ◯ Letta Code

"The best time to fix a dependency bug is before someone reports it.
The second best time is immediately after." - Ancient npm proverb
2026-02-04 18:43:10 -08:00
Cameron
63c6d60c05 fix: reset recovery counter + add skills loader tests (#153)
* fix: reset recovery counter on successful response

When the agent successfully sends a message, reset the recoveryAttempts
counter. This ensures the counter only reflects consecutive failures,
not total failures over time.

"Success is not final, failure is not fatal." - Winston Churchill

Written by Cameron ◯ Letta Code

* test: add skills loader tests

Add 10 tests for the skills loader module:
- getAgentSkillsDir() path generation
- FEATURE_SKILLS configuration
- Skill installation behavior (directory creation, copying, no-overwrite)

"Untested code is broken code." - Anonymous

Written by Cameron ◯ Letta Code
2026-02-04 18:41:32 -08:00
Cameron
8c4a472480 feat: add LETTABOT_CONFIG env var for config path (#152)
* docs: add TESTING.md guide

Comprehensive testing documentation covering:
- Unit test setup and patterns
- E2E test setup with Letta Cloud
- MockChannelAdapter usage
- CI/CD workflow
- Best practices

Written by Cameron ◯ Letta Code

* feat: add LETTABOT_CONFIG env var for config path

Addresses Discord feedback from fpl9000 who was confused about where
to put the config file after a global npm install.

Changes:
- Add LETTABOT_CONFIG env var that overrides the config search order
- Update error messages to show the env var option
- Document in docs/configuration.md

Now users doing global installs can either:
- Create ~/.lettabot/config.yaml, or
- Set LETTABOT_CONFIG=/path/to/config.yaml

Written by Cameron ◯ Letta Code

"Configuration should be explicit, not magic." - The Twelve-Factor App
2026-02-04 18:36:10 -08:00
Cameron
9be59847f3 docs: add TESTING.md guide (#151)
Comprehensive testing documentation covering:
- Unit test setup and patterns
- E2E test setup with Letta Cloud
- MockChannelAdapter usage
- CI/CD workflow
- Best practices

Written by Cameron ◯ Letta Code
2026-02-04 18:00:26 -08:00
Cameron
3d1f536c93 fix: MockChannelAdapter handles commands like real channels (#150)
The e2e test for /help failed because MockChannelAdapter was passing
commands to the agent instead of handling them locally. Real channels
(Telegram, Signal, etc.) intercept /help and return HELP_TEXT directly.

Now MockChannelAdapter does the same, making tests consistent.

Written by Cameron ◯ Letta Code
2026-02-04 17:54:28 -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
1113631252 feat(skills): install to agent-scoped location instead of .skills/ (#148)
Skills now install to ~/.letta/agents/{agentId}/skills/ after agent
creation, aligning with Letta Code CLI behavior. This removes the
duplicate installation that was happening at both startup and after
agent creation.

Changes:
- Add SkillsConfig type and pass through BotConfig
- Update installSkillsToAgent() to actually install skills
- Remove installSkillsToWorkingDir() call from main.ts startup
- Closes #108 (reimplemented from PR #114 due to conflicts)

"The best way to predict the future is to invent it." - Alan Kay

Written by Cameron ◯ Letta Code
2026-02-04 17:25:53 -08:00
Cameron
030a2b2bc5 feat: add CI test workflow and commands tests (#147)
Testing infrastructure improvements:

1. Add GitHub Actions workflow (.github/workflows/test.yml)
   - Runs on push/PR to main
   - Installs deps, builds, runs tests
   - Blocks merging broken code

2. Add tests for commands.ts (src/core/commands.test.ts)
   - Tests parseCommand() with valid/invalid inputs
   - Tests case insensitivity
   - Tests COMMANDS array and HELP_TEXT

Now at 231 tests across 17 test files.

Written by Cameron ◯ Letta Code

"Test early, test often." - Software proverb
2026-02-04 17:13:18 -08:00
Cameron
c8d55c8e84 feat: add Signal group chat support with mention gating (#146)
Add group message filtering so bot only responds when mentioned:

- Add native Signal mentions support (mentions array in SSE)
- Add quote/reply detection
- Add regex pattern matching (configurable via mentionPatterns)
- Add E.164 phone number fallback
- Add per-group config (groups.*.requireMention)
- Default: requireMention=true for safety (opt-in to respond to all)

Config example:
```yaml
channels:
  signal:
    mentionPatterns: ["@bot", "@lettabot"]
    groups:
      "*":
        requireMention: true
      "group:abc123":
        requireMention: false
```

Closes #137

Written by Cameron ◯ Letta Code

"In groups, listen first. Speak only when spoken to." - Bot wisdom
2026-02-04 17:11:23 -08:00
Cameron
21957f5c6e feat: add slash command support to all channels (#145)
Adds /status, /heartbeat, /help, and /start commands to:
- Signal (was missing all commands)
- Slack (was missing all commands)
- WhatsApp (was missing all commands)
- Discord (was missing /help and /start)

Telegram already had full support.

Changes:
- Create src/core/commands.ts with shared HELP_TEXT and parseCommand()
- Add onCommand property to Signal, Slack, WhatsApp adapters
- Add command detection before onMessage in each adapter
- /help and /start are handled locally, /status and /heartbeat
  delegate to onCommand callback

Fixes #91

Written by Cameron ◯ Letta Code

"Consistency is the last refuge of the unimaginative." - Oscar Wilde
(But sometimes it's just good UX)
2026-02-04 16:56:44 -08:00
Cameron
ac6dcd4f02 fix: improve stuck approval detection with better diagnostics (#144)
When stream receives no data at all (likely stuck approval):
- Log detailed diagnostics including conversation ID
- Suggest user retry their message (CLI may auto-recover)
- Point to reset-conversation as last resort
- Do NOT auto-reset conversation (preserves context)

When stream receives data but no assistant message:
- Log message type counts for debugging
- Different message to user

Also fixes TypeScript error: 'system' -> 'init' stream message type

Related: #125, #127, #132

Written by Cameron ◯ Letta Code

"Diagnose first, prescribe second." - Debugging wisdom
2026-02-04 16:28:51 -08:00
github-actions[bot]
c8f4f173f1 fix: add type assertion for 'system' stream message type (#141)
The SDK's StreamMessageType definition doesn't include 'system', but
the runtime emits these messages. Added type assertion to allow the
comparison without TypeScript error TS2367.

Fixes #140

Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com>
Co-authored-by: Cameron <cpfiffer@users.noreply.github.com>
2026-02-04 16:06:50 -08:00
Cameron
541bcf496e Revert "fix: auto-reset conversation when stream receives no data (#142)" (#143)
This reverts commit 4258e92c50.
2026-02-04 15:42:54 -08:00
Cameron
4258e92c50 fix: auto-reset conversation when stream receives no data (#142)
When a conversation has a stuck tool approval from a previous session,
the stream receives NO data at all (not even init). This leaves users
stuck with "(No response from agent)" and no clear path to recovery.

Changes:
- Track if stream received ANY data (not just assistant messages)
- If stream times out with zero data, assume stuck approval state
- Auto-reset conversation and notify user to try again
- Add message type counts to "no response" logs for debugging
- Fix 'system' -> 'init' type comparison (was causing TS error)

This addresses the issue reported by Signo on Discord where responses
were going to ADE instead of the channel due to stuck approvals.

Related issues: #125, #127, #132

Written by Cameron ◯ Letta Code

"When the stream runs dry, dig a new well." - Infrastructure proverb
2026-02-04 15:27:25 -08:00
github-actions[bot]
e4219d0615 fix: use getDataDir() for CronService storage path (#139)
Fix path mismatch between CLI and CronService that caused cron jobs
created via CLI to never be picked up by the running service.

- CLI uses getDataDir() for cron-jobs.json
- CronService was overriding with workingDir path
- Now both use getDataDir() (defaults to cwd or Railway volume)

Fixes #135

Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com>
Co-authored-by: Cameron <cpfiffer@users.noreply.github.com>
2026-02-04 15:10:13 -08:00
Cameron
3e48036db4 feat: add detailed stream message logging (#138)
- Log every stream message type received (tool_call, tool_result, etc.)
- Add message type counts summary at end of stream
- Add DEBUG_STREAM=1 for verbose per-message logging with JSON preview
- Add reasoning and system message type logging
- Include more detail in tool_result logs (error status, content length)

Helps diagnose why watchdog times out when tools appear to be running
in ADE but no tool_call/tool_result messages are received by lettabot.

Written by Cameron ◯ Letta Code

"You can't fix what you can't see." - Debugging wisdom
2026-02-04 13:10:35 -08:00
Cameron
b31a67d024 fix: increase stream idle timeout default from 30s to 120s (#136)
Tools execute client-side without emitting stream messages. Multiple
tool executions (10-20s each) plus API processing gaps can exceed
30s of "idle" time even though the agent is actively working.

Increase default to 120s to prevent false timeouts. Users can still
override via LETTA_STREAM_IDLE_TIMEOUT_MS env var.

Written by Cameron ◯ Letta Code

"Patience is a virtue, especially when waiting for tools." - Ancient proverb
2026-02-04 12:53:09 -08:00
Cameron
550ea6cf2e feat: add inbound reaction handling (#134)
* Add inbound reaction handling

* Add inbound reaction types

* Add reaction envelope test

---------

Co-authored-by: Jason Carreira <jason@visotrust.com>
2026-02-04 12:08:49 -08:00
Cameron
0c79b147f4 fix: add diagnostic logging for tool approval flow (#132) (#133)
Adds logging to help diagnose the issue where tool approvals are
being requested despite bypassPermissions mode:

1. Log session options when created (permissionMode, allowedTools count)
2. Add fallback canUseTool callback that logs warnings if called
   - This should NOT be called when permissionMode=bypassPermissions
   - If logs appear, it indicates the mode isn't being respected
3. Log stream result details (success, hasResponse, resultLen, error)
4. Add context when "(No response from agent)" is sent
   - Suggests checking if ADE is open (session conflict)

If users see "Tool approval requested" warnings in their logs,
it means the bypassPermissions mode isn't working correctly at
the SDK/CLI level.

Closes #132

Written by Cameron ◯ Letta Code

"You can't fix what you can't see." - Debugging proverb
2026-02-04 11:22:48 -08:00
Gabriele Sarti
0aef80157a feat: add lettabot-channels CLI for channel discovery (#121)
Allow the agent to discover channel IDs across Discord and Slack so it
can send messages to channels it hasn't received messages from (e.g.
"write something in #announcements"). Updates the system prompt so the
agent knows the command exists.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:02:35 -08:00
Gabriele Sarti
92c81eb294 fix(telegram): reorder voice handler before generic message handler (#120)
The `message:voice` handler was registered after the generic `message`
handler, which meant grammY matched voice messages to the broader
handler first. The guard clause returned early but didn't forward to
the voice handler, silently dropping voice messages.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:01:22 -08:00
Cameron
c80c7ae7d5 feat: chunked transcription for large audio files (#129)
Whisper API has a 25MB limit. For larger audio files:
1. Split into 10-minute chunks using ffmpeg
2. Transcribe each chunk separately
3. Combine transcriptions into single text

Example output:
```
[Transcription] File too large (32.5MB), splitting into chunks
[Transcription] Split into 4 chunks
[Transcription] Transcribing chunk 1/4 (5120KB)
[Transcription] Transcribing chunk 2/4 (5120KB)
...
[Transcription] Combined 4 chunks into 4521 chars
```

Written by Cameron ◯ Letta Code

"Divide and conquer, then concatenate." - Parallel processing proverb
2026-02-03 23:32:16 -08:00
Cameron
4843ac3d3a fix: convert AAC audio with ffmpeg for transcription (#128)
OpenAI Whisper rejects raw AAC files even when renamed to .m4a - it
checks actual file format, not just extension. Signal voice messages
are often AAC.

Now uses ffmpeg to convert unsupported formats (aac, amr, caf, 3gp)
to MP3 before sending to Whisper API.

Requires ffmpeg installed on system.

Written by Cameron ◯ Letta Code

"When in doubt, transcode it out." - Audio engineering wisdom
2026-02-03 23:27:50 -08:00
Cameron
ac6ec9505d fix: prevent 409 conflicts between heartbeats and user messages (#123)
Heartbeats and user messages were racing to use the agent, causing
409 CONFLICT errors ("Another request is currently being processed").

This adds a processing mutex to `sendToAgent()` (used by heartbeats):
- Waits for any in-progress message processing to complete
- Marks itself as processing to prevent queue from starting
- Releases lock and triggers queue processing when done

This ensures heartbeats and user messages are serialized.

Written by Cameron ◯ Letta Code

"Concurrency is hard. Mutexes are your friend." - Every debugger ever
2026-02-03 22:27:30 -08:00
Cameron
df0343d0be feat: add reset-conversation command and better error detection (#122)
When conversations become corrupted on Letta Cloud, users see empty
responses with no useful error message. This adds:

1. Warning message when empty result detected:
   - Logs: "Agent returned empty result with no response"
   - Suggests running `lettabot reset-conversation`

2. New CLI command `lettabot reset-conversation`:
   - Clears the conversationId from lettabot-agent.json
   - Preserves agent and memory
   - Next message creates fresh conversation

Symptoms of corrupted conversation:
- stop_reason: "error" with empty result
- Messages not appearing in agent history
- duration_api_ms: 0 (no API call made)

Written by Cameron ◯ Letta Code

"When in doubt, start fresh." - Ancient debugging wisdom
2026-02-03 21:59:08 -08:00
github-actions[bot]
41c6e88977 Upgrade Letta Code SDK to 0.0.5 (#117)
Breaking API changes in SDK 0.0.5 require:
- Use createAgent() for new agents with custom systemPrompt and memory
- Remove systemPrompt from createSession()/resumeSession() options

Fixes #116

Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com>
Co-authored-by: Cameron <cpfiffer@users.noreply.github.com>
2026-02-03 18:54:56 -08:00
Parth Modi
d1d758739d feat(whatsapp): add lettabot-message CLI support for text and files (#89)
Merged WhatsApp CLI support with HTTP API server.

Features:
- HTTP API server for CLI-to-bot communication across Docker boundaries
- WhatsApp text + file sending via `lettabot-message send --file photo.jpg`
- Unified multipart endpoint at /api/v1/messages
- Security: timing-safe auth, localhost binding, same-origin CORS
- Bad MAC error handling for WhatsApp encryption renegotiation

Written by Cameron ◯ Letta Code
2026-02-03 17:21:27 -08:00
Cameron
27402d86d8 feat: add stream idle watchdog and session init improvements (#105)
Merged stream idle watchdog and session init improvements.

Features:
- Stream idle watchdog - aborts stuck sessions after timeout
- Periodic waiting logs every 10s when stream is idle
- Persist conversationId immediately after session.initialize()
- Configurable via LETTA_STREAM_IDLE_TIMEOUT_MS

Closes #87, closes #88

Written by Cameron ◯ Letta Code
2026-02-03 17:01:19 -08:00