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
This commit is contained in:
Cameron
2026-02-03 21:59:08 -08:00
committed by GitHub
parent 41c6e88977
commit df0343d0be
3 changed files with 76 additions and 16 deletions

30
package-lock.json generated
View File

@@ -1211,9 +1211,9 @@
}
},
"node_modules/@isaacs/brace-expansion": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz",
"integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==",
"license": "MIT",
"dependencies": {
"@isaacs/balanced-match": "^4.0.1"
@@ -1277,13 +1277,13 @@
"license": "Apache-2.0"
},
"node_modules/@letta-ai/letta-code": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/@letta-ai/letta-code/-/letta-code-0.14.1.tgz",
"integrity": "sha512-4XQQxqDUlFNo7uKBilyIJ4KKHC8QrFoeMfZXmr9LgtNMtXYqB+I5AYuCnG9BBueeZgO1hlDN2ekJZELyJUqrPQ==",
"version": "0.14.8",
"resolved": "https://registry.npmjs.org/@letta-ai/letta-code/-/letta-code-0.14.8.tgz",
"integrity": "sha512-sS5jsAcA1hLIuQXzlKGveA1IjFw6TTcjK/oZmtVq3suB2HUdML9RdNpqwMnDRz6NS4tT4nyWvbu732uyJEq7ig==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@letta-ai/letta-client": "^1.7.6",
"@letta-ai/letta-client": "^1.7.7",
"glob": "^13.0.0",
"ink-link": "^5.0.0",
"open": "^10.2.0",
@@ -1306,12 +1306,12 @@
}
},
"node_modules/@letta-ai/letta-code/node_modules/glob": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz",
"integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==",
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz",
"integrity": "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==",
"license": "BlueOak-1.0.0",
"dependencies": {
"minimatch": "^10.1.1",
"minimatch": "^10.1.2",
"minipass": "^7.1.2",
"path-scurry": "^2.0.0"
},
@@ -1332,12 +1332,12 @@
}
},
"node_modules/@letta-ai/letta-code/node_modules/minimatch": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
"integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz",
"integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/brace-expansion": "^5.0.0"
"@isaacs/brace-expansion": "^5.0.1"
},
"engines": {
"node": "20 || >=22"

View File

@@ -184,6 +184,7 @@ Commands:
logout Logout from Letta Platform (revoke OAuth tokens)
skills Configure which skills are enabled
skills status Show skills status
reset-conversation Clear conversation ID (fixes corrupted conversations)
destroy Delete all local data and start fresh
pairing list <ch> List pending pairing requests
pairing approve <ch> <code> Approve a pairing code
@@ -343,6 +344,57 @@ async function main() {
p.outro('✨ Done! Run `npx lettabot server` to create a fresh agent.');
break;
}
case 'reset-conversation': {
const { existsSync, readFileSync, writeFileSync } = await import('node:fs');
const { join } = await import('node:path');
const p = await import('@clack/prompts');
const dataDir = getDataDir();
const agentJsonPath = join(dataDir, 'lettabot-agent.json');
p.intro('Reset Conversation');
if (!existsSync(agentJsonPath)) {
p.log.error('No agent store found. Run the server first to create an agent.');
break;
}
const store = JSON.parse(readFileSync(agentJsonPath, 'utf-8'));
const oldConversationId = store.conversationId;
if (!oldConversationId) {
p.log.info('No conversation ID stored. Nothing to reset.');
break;
}
p.log.warn(`Current conversation: ${oldConversationId}`);
p.log.message('');
p.log.message('This will clear the conversation ID, causing the bot to create');
p.log.message('a new conversation on the next message. Use this if you see:');
p.log.message(' • "stop_reason: error" with empty responses');
p.log.message(' • Messages not reaching the agent');
p.log.message(' • Agent returning empty results');
p.log.message('');
p.log.message('The agent and its memory will be preserved.');
const confirmed = await p.confirm({
message: 'Reset conversation?',
initialValue: true,
});
if (!confirmed || p.isCancel(confirmed)) {
p.cancel('Cancelled');
break;
}
store.conversationId = null;
writeFileSync(agentJsonPath, JSON.stringify(store, null, 2));
p.log.success('Conversation ID cleared');
p.outro('Restart the server - a new conversation will be created on the next message.');
break;
}
case 'logout': {
const { revokeToken } = await import('./auth/oauth.js');
@@ -382,7 +434,7 @@ async function main() {
case undefined:
console.log('Usage: lettabot <command>\n');
console.log('Commands: onboard, server, configure, skills, destroy, help\n');
console.log('Commands: onboard, server, configure, skills, reset-conversation, destroy, help\n');
console.log('Run "lettabot help" for more information.');
break;

View File

@@ -373,6 +373,14 @@ export class LettaBot {
}
if (streamMsg.type === 'result') {
// Check for corrupted conversation (empty result usually means error)
const resultMsg = streamMsg as { result?: string; success?: boolean };
if (resultMsg.success && resultMsg.result === '' && !response.trim()) {
console.error('[Bot] Warning: Agent returned empty result with no response.');
console.error('[Bot] This often indicates a corrupted conversation.');
console.error('[Bot] Try running: lettabot reset-conversation');
}
// Save agent ID and conversation ID
if (session.agentId && session.agentId !== this.store.agentId) {
const isNewAgent = !this.store.agentId;