Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ Emdash currently supports 24 CLI providers, and we are adding new ones regularly
| [Kilocode](https://kilo.ai/docs/cli) | ✅ Supported | <code>npm install -g @kilocode/cli</code> |
| [Kimi](https://www.kimi.com/code/docs/en/kimi-cli/guides/getting-started.html) | ✅ Supported | <code>uv tool install kimi-cli</code> |
| [Kiro (AWS)](https://kiro.dev/docs/cli/) | ✅ Supported | <code>curl -fsSL https://cli.kiro.dev/install &#124; bash</code> |
| [Letta](https://docs.letta.com/letta-code/cli) | ✅ Supported | <code>npm install -g @letta-ai/letta-code</code> |
| [Mistral Vibe](https://github.com/mistralai/mistral-vibe) | ✅ Supported | <code>curl -LsSf https://mistral.ai/vibe/install.sh &#124; bash</code> |
| [OpenCode](https://opencode.ai/docs/cli/) | ✅ Supported | <code>npm install -g opencode-ai</code> |
| [Pi](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent) | ✅ Supported | <code>npm install -g @mariozechner/pi-coding-agent</code> |
Expand Down
4 changes: 2 additions & 2 deletions agents/integrations/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions src/assets/images/letta.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/main/core/agent-hooks/classifiers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -44,6 +45,7 @@ const classifierFactories: Partial<Record<AgentProviderId, () => ProviderClassif
kilocode: createKilocodeClassifier,
kimi: createKimiClassifier,
kiro: createKiroClassifier,
letta: createLettaClassifier,
mistral: createMistralClassifier,
opencode: createOpenCodeClassifier,
pi: createPiClassifier,
Expand Down
55 changes: 55 additions & 0 deletions src/main/core/agent-hooks/classifiers/letta.ts
Original file line number Diff line number Diff line change
@@ -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|Connected to|Login successful/i.test(text)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The auth_success check includes "Connected to" as an alternative, which is far broader than the other patterns in the block. Any Letta output that contains the phrase "Connected to" (e.g., "Connected to workspace", "Connected to remote agent", ordinary status lines) will fire an auth_success notification even during a normal session. The other patterns — "Successfully authenticated" and "Login successful" — are specific enough; "Connected to" should be made more precise or removed.

Suggested change
// Auth success (e.g. /connect flow)
if (/Successfully authenticated|Connected to|Login successful/i.test(text)) {
// Auth success (e.g. /connect flow)
if (/Successfully authenticated|Successfully connected|Login successful/i.test(text)) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main/core/agent-hooks/classifiers/letta.ts
Line: 30-31

Comment:
The `auth_success` check includes `"Connected to"` as an alternative, which is far broader than the other patterns in the block. Any Letta output that contains the phrase "Connected to" (e.g., "Connected to workspace", "Connected to remote agent", ordinary status lines) will fire an `auth_success` notification even during a normal session. The other patterns — `"Successfully authenticated"` and `"Login successful"` — are specific enough; `"Connected to"` should be made more precise or removed.

```suggestion
    // Auth success (e.g. /connect flow)
    if (/Successfully authenticated|Successfully connected|Login successful/i.test(text)) {
```

How can I resolve this? If you propose a fix, please make it concise.

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;
});
}
2 changes: 2 additions & 0 deletions src/main/core/conversations/impl/agent-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/lib/providers/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -46,6 +47,7 @@ const ICONS: Record<string, string> = {
'kimi.png': kimiIcon,
'kilocode.png': kilocodeIcon,
'kiro.png': kiroIcon,
'letta.svg': lettaIcon,
'atlassian.png': atlassianIcon,
'cline.png': clineIcon,
'continue.png': continueIcon,
Expand Down
8 changes: 8 additions & 0 deletions src/renderer/utils/agentConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -51,6 +52,13 @@ export const agentConfig: Record<AgentProviderId, AgentInfo> = {
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' },
Expand Down
28 changes: 28 additions & 0 deletions src/shared/agent-provider-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const AGENT_PROVIDER_IDS = [
'mistral',
'junie',
'pi',
'letta',
'autohand',
] as const;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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',
Expand Down
Loading