fix: add guard for default conversation id (#1245)

This commit is contained in:
cthomas
2026-03-03 14:42:35 -08:00
committed by GitHub
parent 13063371c0
commit 28ba2e7e1f
5 changed files with 62 additions and 20 deletions

View File

@@ -6,7 +6,7 @@ import { APIError } from "@letta-ai/letta-client/core/error";
import type { AgentState } from "@letta-ai/letta-client/resources/agents/agents"; import type { AgentState } from "@letta-ai/letta-client/resources/agents/agents";
import type { Message } from "@letta-ai/letta-client/resources/agents/messages"; import type { Message } from "@letta-ai/letta-client/resources/agents/messages";
import type { ApprovalRequest } from "../cli/helpers/stream"; import type { ApprovalRequest } from "../cli/helpers/stream";
import { debugWarn } from "../utils/debug"; import { debugLog, debugWarn } from "../utils/debug";
// Backfill should feel like "the last turn(s)", not "the last N raw messages". // Backfill should feel like "the last turn(s)", not "the last N raw messages".
// Tool-heavy turns can generate many tool_call/tool_return messages that would // Tool-heavy turns can generate many tool_call/tool_return messages that would
@@ -344,16 +344,21 @@ export async function getResumeData(
// Use conversations API for explicit conversations, // Use conversations API for explicit conversations,
// use agents API for "default" or no conversationId (agent's primary message history) // use agents API for "default" or no conversationId (agent's primary message history)
const useConversationsApi = conversationId && conversationId !== "default"; const useConversationsApi =
conversationId &&
conversationId !== "default" &&
!conversationId.startsWith("agent-");
if (process.env.DEBUG) { if (conversationId?.startsWith("agent-")) {
console.log( debugWarn(
`[DEBUG] getResumeData: conversationId=${conversationId}, useConversationsApi=${useConversationsApi}, agentId=${agent.id}`, "check-approval",
`getResumeData called with agent ID as conversationId: ${conversationId}\n${new Error().stack}`,
); );
} }
if (useConversationsApi) { if (useConversationsApi) {
// Get conversation to access in_context_message_ids (source of truth) // Get conversation to access in_context_message_ids (source of truth)
debugLog("conversations", `retrieve(${conversationId}) [getResumeData]`);
const conversation = await client.conversations.retrieve(conversationId); const conversation = await client.conversations.retrieve(conversationId);
inContextMessageIds = conversation.in_context_message_ids; inContextMessageIds = conversation.in_context_message_ids;

View File

@@ -3280,6 +3280,10 @@ export default function App({
try { try {
const client = await getClient(); const client = await getClient();
debugLog(
"conversations",
`retrieve(${conversationId}) [syncConversationModel]`,
);
const conversation = const conversation =
await client.conversations.retrieve(conversationId); await client.conversations.retrieve(conversationId);
if (cancelled) return; if (cancelled) return;
@@ -6154,7 +6158,9 @@ export default function App({
// Build success message // Build success message
const agentLabel = agent.name || targetAgentId; const agentLabel = agent.name || targetAgentId;
const isSpecificConv = const isSpecificConv =
opts?.conversationId && opts.conversationId !== "default"; opts?.conversationId &&
opts.conversationId !== "default" &&
!opts?.conversationId.startsWith("agent-");
const successOutput = isSpecificConv const successOutput = isSpecificConv
? [ ? [
`Switched to **${agentLabel}**`, `Switched to **${agentLabel}**`,
@@ -8695,7 +8701,7 @@ export default function App({
// Build export parameters (include conversation_id if in specific conversation) // Build export parameters (include conversation_id if in specific conversation)
const exportParams: { conversation_id?: string } = {}; const exportParams: { conversation_id?: string } = {};
if (conversationId !== "default") { if (conversationId !== "default" && conversationId !== agentId) {
exportParams.conversation_id = conversationId; exportParams.conversation_id = conversationId;
} }
@@ -12914,15 +12920,16 @@ If using apply_patch, use this exact relative patch path: ${applyPatchRelativePa
: `letta --agent ${agentId}`} : `letta --agent ${agentId}`}
</Text> </Text>
{/* Only show conversation hint if not on default (default is resumed automatically) */} {/* Only show conversation hint if not on default (default is resumed automatically) */}
{conversationId !== "default" && ( {conversationId !== "default" &&
<> conversationId !== agentId && (
<Box height={1} /> <>
<Text dimColor>Resume this conversation with:</Text> <Box height={1} />
<Text color={colors.link.url}> <Text dimColor>Resume this conversation with:</Text>
{`letta --conv ${conversationId}`} <Text color={colors.link.url}>
</Text> {`letta --conv ${conversationId}`}
</> </Text>
)} </>
)}
</Box> </Box>
); );
})()} })()}

View File

@@ -177,7 +177,9 @@ export const AgentInfoBar = memo(function AgentInfoBar({
{/* Phantom alien row + Conversation ID */} {/* Phantom alien row + Conversation ID */}
<Box> <Box>
<Text>{alienLines[3]}</Text> <Text>{alienLines[3]}</Text>
{conversationId && conversationId !== "default" ? ( {conversationId &&
conversationId !== "default" &&
!conversationId.startsWith("agent-") ? (
<Box width={rightWidth} flexShrink={1}> <Box width={rightWidth} flexShrink={1}>
<Text dimColor wrap="truncate-end"> <Text dimColor wrap="truncate-end">
{truncateText(conversationId, rightWidth)} {truncateText(conversationId, rightWidth)}

View File

@@ -114,6 +114,7 @@ import type {
StreamEvent, StreamEvent,
SystemInitMessage, SystemInitMessage,
} from "./types/protocol"; } from "./types/protocol";
import { debugLog } from "./utils/debug";
import { import {
markMilestone, markMilestone,
measureSinceMilestone, measureSinceMilestone,
@@ -526,7 +527,10 @@ export async function handleHeadlessCommand(
// Validate shared mutual-exclusion rules for startup flags. // Validate shared mutual-exclusion rules for startup flags.
try { try {
validateFlagConflicts({ validateFlagConflicts({
guard: specifiedConversationId && specifiedConversationId !== "default", guard:
specifiedConversationId &&
specifiedConversationId !== "default" &&
!specifiedConversationId.startsWith("agent-"),
checks: [ checks: [
{ {
when: specifiedAgentId, when: specifiedAgentId,
@@ -730,8 +734,16 @@ export async function handleHeadlessCommand(
// Priority 0: --conversation derives agent from conversation ID. // Priority 0: --conversation derives agent from conversation ID.
// "default" is a virtual agent-scoped conversation (not a retrievable conv-*). // "default" is a virtual agent-scoped conversation (not a retrievable conv-*).
// It requires --agent and should not hit conversations.retrieve(). // It requires --agent and should not hit conversations.retrieve().
if (specifiedConversationId && specifiedConversationId !== "default") { if (
specifiedConversationId &&
specifiedConversationId !== "default" &&
!specifiedConversationId.startsWith("agent-")
) {
try { try {
debugLog(
"conversations",
`retrieve(${specifiedConversationId}) [headless conv→agent lookup]`,
);
const conversation = await client.conversations.retrieve( const conversation = await client.conversations.retrieve(
specifiedConversationId, specifiedConversationId,
); );
@@ -1007,6 +1019,10 @@ export async function handleHeadlessCommand(
} else { } else {
// User specified an explicit conversation to resume - validate it exists // User specified an explicit conversation to resume - validate it exists
try { try {
debugLog(
"conversations",
`retrieve(${specifiedConversationId}) [headless --conv validate]`,
);
await client.conversations.retrieve(specifiedConversationId); await client.conversations.retrieve(specifiedConversationId);
conversationId = specifiedConversationId; conversationId = specifiedConversationId;
} catch { } catch {
@@ -1030,6 +1046,10 @@ export async function handleHeadlessCommand(
} else { } else {
// Verify the conversation still exists // Verify the conversation still exists
try { try {
debugLog(
"conversations",
`retrieve(${lastSession.conversationId}) [headless lastSession resume]`,
);
await client.conversations.retrieve(lastSession.conversationId); await client.conversations.retrieve(lastSession.conversationId);
conversationId = lastSession.conversationId; conversationId = lastSession.conversationId;
} catch { } catch {

View File

@@ -47,6 +47,7 @@ import { settingsManager } from "./settings-manager";
import { startStartupAutoUpdateCheck } from "./startup-auto-update"; import { startStartupAutoUpdateCheck } from "./startup-auto-update";
import { telemetry } from "./telemetry"; import { telemetry } from "./telemetry";
import { loadTools } from "./tools/manager"; import { loadTools } from "./tools/manager";
import { debugLog } from "./utils/debug";
import { markMilestone } from "./utils/timing"; import { markMilestone } from "./utils/timing";
// Stable empty array constants to prevent new references on every render // Stable empty array constants to prevent new references on every render
@@ -629,7 +630,10 @@ async function main(): Promise<void> {
// Validate shared mutual-exclusion rules for startup flags. // Validate shared mutual-exclusion rules for startup flags.
try { try {
validateFlagConflicts({ validateFlagConflicts({
guard: specifiedConversationId && specifiedConversationId !== "default", guard:
specifiedConversationId &&
specifiedConversationId !== "default" &&
!specifiedConversationId.startsWith("agent-"),
checks: [ checks: [
{ {
when: specifiedAgentId, when: specifiedAgentId,
@@ -1193,6 +1197,10 @@ async function main(): Promise<void> {
// For explicit conversations, derive agent from conversation // For explicit conversations, derive agent from conversation
try { try {
debugLog(
"conversations",
`retrieve(${specifiedConversationId}) [TUI conv→agent lookup]`,
);
const conversation = await client.conversations.retrieve( const conversation = await client.conversations.retrieve(
specifiedConversationId, specifiedConversationId,
); );