diff --git a/package-lock.json b/package-lock.json index 964ca39..8f5d5e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" diff --git a/src/cli.ts b/src/cli.ts index e83e58a..474e46d 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -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 List pending pairing requests pairing approve 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 \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; diff --git a/src/core/bot.ts b/src/core/bot.ts index f128d5c..ea60ca5 100644 --- a/src/core/bot.ts +++ b/src/core/bot.ts @@ -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;