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 { Message } from "@letta-ai/letta-client/resources/agents/messages";
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".
// 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 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) {
console.log(
`[DEBUG] getResumeData: conversationId=${conversationId}, useConversationsApi=${useConversationsApi}, agentId=${agent.id}`,
if (conversationId?.startsWith("agent-")) {
debugWarn(
"check-approval",
`getResumeData called with agent ID as conversationId: ${conversationId}\n${new Error().stack}`,
);
}
if (useConversationsApi) {
// Get conversation to access in_context_message_ids (source of truth)
debugLog("conversations", `retrieve(${conversationId}) [getResumeData]`);
const conversation = await client.conversations.retrieve(conversationId);
inContextMessageIds = conversation.in_context_message_ids;

View File

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

View File

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

View File

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

View File

@@ -47,6 +47,7 @@ import { settingsManager } from "./settings-manager";
import { startStartupAutoUpdateCheck } from "./startup-auto-update";
import { telemetry } from "./telemetry";
import { loadTools } from "./tools/manager";
import { debugLog } from "./utils/debug";
import { markMilestone } from "./utils/timing";
// 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.
try {
validateFlagConflicts({
guard: specifiedConversationId && specifiedConversationId !== "default",
guard:
specifiedConversationId &&
specifiedConversationId !== "default" &&
!specifiedConversationId.startsWith("agent-"),
checks: [
{
when: specifiedAgentId,
@@ -1193,6 +1197,10 @@ async function main(): Promise<void> {
// For explicit conversations, derive agent from conversation
try {
debugLog(
"conversations",
`retrieve(${specifiedConversationId}) [TUI conv→agent lookup]`,
);
const conversation = await client.conversations.retrieve(
specifiedConversationId,
);