diff --git a/README.md b/README.md index 211c206e3..8fc84f990 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ Emdash currently supports 24 CLI providers, and we are adding new ones regularly | [Kilocode](https://kilo.ai/docs/cli) | ✅ Supported | npm install -g @kilocode/cli | | [Kimi](https://www.kimi.com/code/docs/en/kimi-cli/guides/getting-started.html) | ✅ Supported | uv tool install kimi-cli | | [Kiro (AWS)](https://kiro.dev/docs/cli/) | ✅ Supported | curl -fsSL https://cli.kiro.dev/install | bash | +| [Letta](https://docs.letta.com/letta-code/cli) | ✅ Supported | npm install -g @letta-ai/letta-code | | [Mistral Vibe](https://github.com/mistralai/mistral-vibe) | ✅ Supported | curl -LsSf https://mistral.ai/vibe/install.sh | bash | | [OpenCode](https://opencode.ai/docs/cli/) | ✅ Supported | npm install -g opencode-ai | | [Pi](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent) | ✅ Supported | npm install -g @mariozechner/pi-coding-agent | diff --git a/agents/integrations/providers.md b/agents/integrations/providers.md index 0a709bd95..b7c1bb87f 100644 --- a/agents/integrations/providers.md +++ b/agents/integrations/providers.md @@ -6,9 +6,9 @@ - `src/main/core/dependencies/dependency-manager.ts` - `src/main/core/pty/` -## Current Providers (25) +## Current Providers (26) -codex, claude, devin, qwen, droid, gemini, cursor, copilot, amp, opencode, hermes, charm, auggie, goose, kimi, kilocode, kiro, rovo, cline, continue, codebuff, mistral, junie, pi, autohand +codex, claude, devin, qwen, droid, gemini, cursor, copilot, amp, opencode, hermes, charm, auggie, goose, kimi, kilocode, kiro, rovo, cline, continue, codebuff, mistral, junie, pi, autohand, letta ## Provider Metadata Includes diff --git a/src/assets/images/letta.svg b/src/assets/images/letta.svg new file mode 100644 index 000000000..568a54914 --- /dev/null +++ b/src/assets/images/letta.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/core/agent-hooks/classifiers/index.ts b/src/main/core/agent-hooks/classifiers/index.ts index da2a2553a..371542d15 100644 --- a/src/main/core/agent-hooks/classifiers/index.ts +++ b/src/main/core/agent-hooks/classifiers/index.ts @@ -18,6 +18,7 @@ import { createJunieClassifier } from './junie'; import { createKilocodeClassifier } from './kilocode'; import { createKimiClassifier } from './kimi'; import { createKiroClassifier } from './kiro'; +import { createLettaClassifier } from './letta'; import { createMistralClassifier } from './mistral'; import { createOpenCodeClassifier } from './opencode'; import { createPiClassifier } from './pi'; @@ -44,6 +45,7 @@ const classifierFactories: Partial ProviderClassif kilocode: createKilocodeClassifier, kimi: createKimiClassifier, kiro: createKiroClassifier, + letta: createLettaClassifier, mistral: createMistralClassifier, opencode: createOpenCodeClassifier, pi: createPiClassifier, diff --git a/src/main/core/agent-hooks/classifiers/letta.ts b/src/main/core/agent-hooks/classifiers/letta.ts new file mode 100644 index 000000000..016460a58 --- /dev/null +++ b/src/main/core/agent-hooks/classifiers/letta.ts @@ -0,0 +1,55 @@ +import { createProviderClassifier, type ClassificationResult } from './base'; + +export function createLettaClassifier() { + return createProviderClassifier((text: string): ClassificationResult => { + const tail = text.slice(-500); + + // Permission/approval prompts + if (/approve|reject|permission|allow|confirm/i.test(tail)) { + return { + type: 'notification', + notificationType: 'permission_prompt', + }; + } + + // Idle/ready prompts (slash-command hint shows in Letta's input footer) + if (/Press\s+Tab|\/help|\/connect|\/model|\/agents/i.test(tail)) { + return { + type: 'notification', + notificationType: 'idle_prompt', + }; + } + + if (/letta\s*>|^>\s*$/im.test(tail)) { + return { + type: 'notification', + notificationType: 'idle_prompt', + }; + } + + // Auth success (e.g. /connect flow) + if (/Successfully authenticated|Successfully connected|Login successful/i.test(text)) { + return { + type: 'notification', + notificationType: 'auth_success', + }; + } + + // Questions/elicitation + if (/What.*\?|How.*\?|Which.*\?|Please (provide|specify|clarify)/i.test(tail)) { + return { + type: 'notification', + notificationType: 'elicitation_dialog', + }; + } + + // Error detection + if (/error:|fatal:|exception|failed/i.test(text)) { + return { + type: 'error', + }; + } + + return undefined; + }); +} diff --git a/src/main/core/conversations/impl/agent-command.ts b/src/main/core/conversations/impl/agent-command.ts index f7eb4f652..386e4d64b 100644 --- a/src/main/core/conversations/impl/agent-command.ts +++ b/src/main/core/conversations/impl/agent-command.ts @@ -116,6 +116,8 @@ export function buildAgentCommand({ } } else if (providerConfig?.sessionIdFlag) { args.push(...parseArgField(providerConfig.sessionIdFlag), sessionId); + } else if (!isResuming && providerDef?.newConversationFlag) { + args.push(providerDef.newConversationFlag); } if (autoApprove && providerConfig?.autoApproveFlag) { diff --git a/src/renderer/lib/providers/meta.ts b/src/renderer/lib/providers/meta.ts index 28d7d065a..c021a1f64 100644 --- a/src/renderer/lib/providers/meta.ts +++ b/src/renderer/lib/providers/meta.ts @@ -18,6 +18,7 @@ import junieIcon from '@/assets/images/junie-color.png'; import kilocodeIcon from '@/assets/images/kilocode.png'; import kimiIcon from '@/assets/images/kimi.png'; import kiroIcon from '@/assets/images/kiro.png'; +import lettaIcon from '@/assets/images/letta.svg?raw'; import mistralIcon from '@/assets/images/mistral.png'; import openaiIcon from '@/assets/images/openai.svg?raw'; import opencodeIcon from '@/assets/images/opencode.png'; @@ -46,6 +47,7 @@ const ICONS: Record = { 'kimi.png': kimiIcon, 'kilocode.png': kilocodeIcon, 'kiro.png': kiroIcon, + 'letta.svg': lettaIcon, 'atlassian.png': atlassianIcon, 'cline.png': clineIcon, 'continue.png': continueIcon, diff --git a/src/renderer/utils/agentConfig.ts b/src/renderer/utils/agentConfig.ts index 1bee3f05b..b7b925f41 100644 --- a/src/renderer/utils/agentConfig.ts +++ b/src/renderer/utils/agentConfig.ts @@ -19,6 +19,7 @@ import junieLogo from '../../assets/images/junie-color.png'; import kilocodeLogo from '../../assets/images/kilocode.png'; import kimiLogo from '../../assets/images/kimi.png'; import kiroLogo from '../../assets/images/kiro.png'; +import lettaLogoSvg from '../../assets/images/letta.svg?raw'; import mistralLogo from '../../assets/images/mistral.png'; import openaiLogoSvg from '../../assets/images/openai.svg?raw'; import opencodeLogo from '../../assets/images/opencode.png'; @@ -51,6 +52,13 @@ export const agentConfig: Record = { goose: { name: 'Goose', logo: gooseLogo, alt: 'Goose CLI' }, kimi: { name: 'Kimi', logo: kimiLogo, alt: 'Kimi CLI' }, kilocode: { name: 'Kilocode', logo: kilocodeLogo, alt: 'Kilocode CLI' }, + letta: { + name: 'Letta', + logo: lettaLogoSvg, + alt: 'Letta Code CLI', + isSvg: true, + invertInDark: true, + }, kiro: { name: 'Kiro', logo: kiroLogo, alt: 'Kiro CLI' }, cline: { name: 'Cline', logo: clineLogo, alt: 'Cline CLI' }, continue: { name: 'Continue', logo: continueLogo, alt: 'Continue CLI' }, diff --git a/src/shared/agent-provider-registry.ts b/src/shared/agent-provider-registry.ts index 477e05dbd..ce4c81a64 100644 --- a/src/shared/agent-provider-registry.ts +++ b/src/shared/agent-provider-registry.ts @@ -23,6 +23,7 @@ export const AGENT_PROVIDER_IDS = [ 'mistral', 'junie', 'pi', + 'letta', 'autohand', ] as const; @@ -56,6 +57,12 @@ export type AgentProviderDefinition = { * e.g. '--session-id' for Claude Code. */ sessionIdFlag?: string; + /** + * CLI flag for providers whose default invocation auto-resumes the last + * conversation (e.g. `letta --new`). Emitted only when starting a fresh + * session and the provider has no sessionIdFlag to address by ID. + */ + newConversationFlag?: string; defaultArgs?: string[]; planActivateCommand?: string; autoStartCommand?: string; @@ -466,6 +473,27 @@ export const AGENT_PROVIDERS: AgentProviderDefinition[] = [ alt: 'Pi CLI', terminalOnly: true, }, + { + id: 'letta', + name: 'Letta', + description: + 'Memory-first coding agent CLI with persistent agents that learn across sessions and portable memory across models.', + docUrl: 'https://docs.letta.com/letta-code/cli', + installCommand: 'npm install -g @letta-ai/letta-code', + commands: ['letta'], + versionArgs: ['--version'], + cli: 'letta', + autoApproveFlag: '--yolo', + initialPromptFlag: '', + // Bare `letta` auto-resumes the cwd's last conversation; `--new` is + // required to start a fresh one when emdash spins up a new chat. + newConversationFlag: '--new', + useKeystrokeInjection: true, + icon: 'letta.svg', + alt: 'Letta Code CLI', + invertInDark: true, + terminalOnly: true, + }, { id: 'autohand', name: 'Autohand Code',