Merge pull request #35 from letta-ai/fix/message-bubbles-and-tool-approval
Fix message bubbles and tool approval
This commit is contained in:
24
package-lock.json
generated
24
package-lock.json
generated
@@ -46,9 +46,9 @@
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/@alcalzone/ansi-tokenize": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.3.tgz",
|
||||
"integrity": "sha512-jsElTJ0sQ4wHRz+C45tfect76BwbTbgkgKByOzpCN9xG61N5V6u/glvg1CsNJhq2xJIFpKHSwG3D2wPPuEYOrQ==",
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.4.tgz",
|
||||
"integrity": "sha512-HTgrrTgZ9Jgeo6Z3oqbQ7lifOVvRR14vaDuBGPPUxk9Thm+vObaO4QfYYYWw4Zo5CWQDBEfsinFA6Gre+AqwNQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -1265,9 +1265,9 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@letta-ai/letta-code": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/@letta-ai/letta-code/-/letta-code-0.13.11.tgz",
|
||||
"integrity": "sha512-L1zQ+Pvn2FNzxNdgCXR5pQxS1Yhnbc+Mm/VyfcwrxCQ4QlIewv1q5MSexP0qZHTHC4ZnOjdbaDPMJWMXBMPeBw==",
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@letta-ai/letta-code/-/letta-code-0.14.1.tgz",
|
||||
"integrity": "sha512-4XQQxqDUlFNo7uKBilyIJ4KKHC8QrFoeMfZXmr9LgtNMtXYqB+I5AYuCnG9BBueeZgO1hlDN2ekJZELyJUqrPQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -2265,9 +2265,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cli-truncate/node_modules/string-width": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz",
|
||||
"integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==",
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz",
|
||||
"integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -3560,9 +3560,9 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/ink/node_modules/string-width": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz",
|
||||
"integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==",
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz",
|
||||
"integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -166,15 +166,15 @@ export class LettaBot {
|
||||
// Create or resume session
|
||||
let session: Session;
|
||||
// Base options for all sessions (model only included for new agents)
|
||||
// Note: canUseTool workaround for SDK v0.0.3 bug - can be removed after letta-ai/letta-code-sdk#10 is released
|
||||
const baseOptions = {
|
||||
permissionMode: 'bypassPermissions' as const,
|
||||
allowedTools: this.config.allowedTools,
|
||||
cwd: this.config.workingDir,
|
||||
systemPrompt: SYSTEM_PROMPT,
|
||||
canUseTool: () => ({ allow: true }),
|
||||
};
|
||||
|
||||
console.log('[Bot] Session options:', JSON.stringify(baseOptions, null, 2));
|
||||
|
||||
try {
|
||||
if (this.store.agentId) {
|
||||
process.env.LETTA_AGENT_ID = this.store.agentId;
|
||||
@@ -188,35 +188,6 @@ export class LettaBot {
|
||||
// Only pass model when creating a new agent
|
||||
session = createSession({ ...baseOptions, model: this.config.model, memory: loadMemoryBlocks(this.config.agentName) });
|
||||
}
|
||||
console.log(`[Bot] Session object:`, Object.keys(session));
|
||||
console.log(`[Bot] Session initialized:`, (session as any).initialized);
|
||||
console.log(`[Bot] Session _agentId:`, (session as any)._agentId);
|
||||
console.log(`[Bot] Session options.permissionMode:`, (session as any).options?.permissionMode);
|
||||
|
||||
// Hook into transport errors and stdout
|
||||
const transport = (session as any).transport;
|
||||
if (transport?.process) {
|
||||
console.log('[Bot] Transport process PID:', transport.process.pid);
|
||||
transport.process.stdout?.on('data', (data: Buffer) => {
|
||||
console.log('[Bot] CLI stdout:', data.toString().slice(0, 500));
|
||||
});
|
||||
transport.process.stderr?.on('data', (data: Buffer) => {
|
||||
console.error('[Bot] CLI stderr:', data.toString());
|
||||
});
|
||||
transport.process.on('exit', (code: number) => {
|
||||
console.log('[Bot] CLI process exited with code:', code);
|
||||
});
|
||||
transport.process.on('error', (err: Error) => {
|
||||
console.error('[Bot] CLI process error:', err);
|
||||
});
|
||||
} else {
|
||||
console.log('[Bot] No transport process found');
|
||||
}
|
||||
|
||||
// Initialize session explicitly (so we can log timing/failures)
|
||||
console.log('[Bot] About to initialize session...');
|
||||
console.log('[Bot] LETTA_API_KEY in env:', process.env.LETTA_API_KEY ? `${process.env.LETTA_API_KEY.slice(0, 30)}...` : 'NOT SET');
|
||||
console.log('[Bot] LETTA_CLI_PATH:', process.env.LETTA_CLI_PATH || 'not set (will use default)');
|
||||
|
||||
const initTimeoutMs = 30000; // Increased to 30s
|
||||
const withTimeout = async <T>(promise: Promise<T>, label: string): Promise<T> => {
|
||||
@@ -233,22 +204,15 @@ export class LettaBot {
|
||||
}
|
||||
};
|
||||
|
||||
console.log('[Bot] Initializing session...');
|
||||
const initInfo = await withTimeout(session.initialize(), 'Session initialize');
|
||||
console.log('[Bot] Session initialized:', initInfo);
|
||||
console.log('[Bot] Session initialized, agent:', initInfo.agentId);
|
||||
|
||||
// Send message to agent with metadata envelope
|
||||
const formattedMessage = formatMessageEnvelope(msg);
|
||||
console.log('[Bot] Formatted message:', formattedMessage.slice(0, 200));
|
||||
console.log('[Bot] Target server:', process.env.LETTA_BASE_URL || 'https://api.letta.com (default)');
|
||||
console.log('[Bot] API key:', process.env.LETTA_API_KEY ? `${process.env.LETTA_API_KEY.slice(0, 20)}...` : '(not set)');
|
||||
console.log('[Bot] Agent ID:', this.store.agentId || '(new agent)');
|
||||
console.log('[Bot] Sending message to session...');
|
||||
try {
|
||||
await withTimeout(session.send(formattedMessage), 'Session send');
|
||||
console.log('[Bot] Message sent successfully, starting stream...');
|
||||
} catch (sendError) {
|
||||
console.error('[Bot] Error in session.send():', sendError);
|
||||
console.error('[Bot] Error sending message:', sendError);
|
||||
throw sendError;
|
||||
}
|
||||
|
||||
@@ -256,6 +220,28 @@ export class LettaBot {
|
||||
let response = '';
|
||||
let lastUpdate = Date.now();
|
||||
let messageId: string | null = null;
|
||||
let lastMsgType: string | null = null;
|
||||
let sentAnyMessage = false;
|
||||
|
||||
// Helper to finalize and send current accumulated response
|
||||
const finalizeMessage = async () => {
|
||||
if (response.trim()) {
|
||||
try {
|
||||
if (messageId) {
|
||||
await adapter.editMessage(msg.chatId, messageId, response);
|
||||
} else {
|
||||
await adapter.sendMessage({ chatId: msg.chatId, text: response, threadId: msg.threadId });
|
||||
}
|
||||
sentAnyMessage = true;
|
||||
} catch {
|
||||
// Ignore send errors
|
||||
}
|
||||
}
|
||||
// Reset for next message bubble
|
||||
response = '';
|
||||
messageId = null;
|
||||
lastUpdate = Date.now();
|
||||
};
|
||||
|
||||
// Keep typing indicator alive
|
||||
const typingInterval = setInterval(() => {
|
||||
@@ -264,6 +250,13 @@ export class LettaBot {
|
||||
|
||||
try {
|
||||
for await (const streamMsg of session.stream()) {
|
||||
// When message type changes, finalize the current message
|
||||
// This ensures different message types appear as separate bubbles
|
||||
if (lastMsgType && lastMsgType !== streamMsg.type && response.trim()) {
|
||||
await finalizeMessage();
|
||||
}
|
||||
lastMsgType = streamMsg.type;
|
||||
|
||||
if (streamMsg.type === 'assistant') {
|
||||
response += streamMsg.content;
|
||||
|
||||
@@ -308,44 +301,37 @@ export class LettaBot {
|
||||
clearInterval(typingInterval);
|
||||
}
|
||||
|
||||
console.log(`[Bot] Stream complete. Response length: ${response.length}`);
|
||||
console.log(`[Bot] Response preview: ${response.slice(0, 100)}...`);
|
||||
|
||||
// Send final response
|
||||
if (response) {
|
||||
console.log(`[Bot] Sending final response (messageId=${messageId})`);
|
||||
if (response.trim()) {
|
||||
try {
|
||||
if (messageId) {
|
||||
await adapter.editMessage(msg.chatId, messageId, response);
|
||||
console.log('[Bot] Edited existing message');
|
||||
} else {
|
||||
await adapter.sendMessage({ chatId: msg.chatId, text: response, threadId: msg.threadId });
|
||||
console.log('[Bot] Sent new message');
|
||||
}
|
||||
sentAnyMessage = true;
|
||||
} catch (sendError) {
|
||||
console.error('[Bot] Error sending final message:', sendError);
|
||||
// If we already sent a streamed message, don't duplicate — the user already saw it.
|
||||
console.error('[Bot] Error sending response:', sendError);
|
||||
if (!messageId) {
|
||||
await adapter.sendMessage({ chatId: msg.chatId, text: response, threadId: msg.threadId });
|
||||
sentAnyMessage = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('[Bot] No response from agent, sending placeholder');
|
||||
}
|
||||
|
||||
// Only show "no response" if we never sent anything
|
||||
if (!sentAnyMessage) {
|
||||
await adapter.sendMessage({ chatId: msg.chatId, text: '(No response from agent)', threadId: msg.threadId });
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Bot] Error processing message:', error);
|
||||
if (error instanceof Error) {
|
||||
console.error('[Bot] Error stack:', error.stack);
|
||||
}
|
||||
await adapter.sendMessage({
|
||||
chatId: msg.chatId,
|
||||
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
||||
threadId: msg.threadId,
|
||||
});
|
||||
} finally {
|
||||
console.log('[Bot] Closing session');
|
||||
session!?.close();
|
||||
}
|
||||
}
|
||||
@@ -365,11 +351,13 @@ export class LettaBot {
|
||||
_context?: TriggerContext
|
||||
): Promise<string> {
|
||||
// Base options (model only for new agents)
|
||||
// Note: canUseTool workaround for SDK v0.0.3 bug - can be removed after letta-ai/letta-code-sdk#10 is released
|
||||
const baseOptions = {
|
||||
permissionMode: 'bypassPermissions' as const,
|
||||
allowedTools: this.config.allowedTools,
|
||||
cwd: this.config.workingDir,
|
||||
systemPrompt: SYSTEM_PROMPT,
|
||||
canUseTool: () => ({ allow: true }),
|
||||
};
|
||||
|
||||
let session: Session;
|
||||
|
||||
Reference in New Issue
Block a user