@letta-ai/letta-code 0.21.9 → 0.21.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/letta.js CHANGED
@@ -3269,7 +3269,7 @@ var package_default;
3269
3269
  var init_package = __esm(() => {
3270
3270
  package_default = {
3271
3271
  name: "@letta-ai/letta-code",
3272
- version: "0.21.9",
3272
+ version: "0.21.11",
3273
3273
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
3274
3274
  type: "module",
3275
3275
  bin: {
@@ -3535,6 +3535,7 @@ var init_secrets = __esm(async () => {
3535
3535
  // src/settings-manager.ts
3536
3536
  var exports_settings_manager = {};
3537
3537
  __export(exports_settings_manager, {
3538
+ shouldPersistSessionState: () => shouldPersistSessionState,
3538
3539
  settingsManager: () => settingsManager
3539
3540
  });
3540
3541
  import { randomUUID } from "node:crypto";
@@ -3543,6 +3544,9 @@ import { join as join2, resolve } from "node:path";
3543
3544
  function isSubagentProcess() {
3544
3545
  return process.env.LETTA_CODE_AGENT_ROLE === "subagent";
3545
3546
  }
3547
+ function shouldPersistSessionState() {
3548
+ return process.env.LETTA_CODE_AGENT_ROLE !== "subagent" && process.env.LETTA_DISABLE_SESSION_PERSIST !== "1";
3549
+ }
3546
3550
  function normalizeBaseUrl(baseUrl) {
3547
3551
  let normalized = baseUrl.replace(/^https?:\/\//, "");
3548
3552
  normalized = normalized.replace(/\/$/, "");
@@ -5484,7 +5488,6 @@ var init_memory_check_reminder = () => {};
5484
5488
  var memory_filesystem_default = `---
5485
5489
  label: memory_filesystem
5486
5490
  description: Filesystem view of memory blocks (system + user)
5487
- limit: 8000
5488
5491
  ---
5489
5492
 
5490
5493
  /memory/
@@ -6577,7 +6580,7 @@ When applying memory in responses, integrate it naturally — like a colleague w
6577
6580
  var init_system_prompt_blocks = () => {};
6578
6581
 
6579
6582
  // src/agent/prompts/system_prompt_memfs.md
6580
- var system_prompt_memfs_default = "# Memory\n\nYour memory is stored in a git repository at `$MEMORY_DIR` (absolute path provided by Letta Code shell tools; usually `~/.letta/agents/$AGENT_ID/memory/`). This provides full version control, sync with the server, and worktrees for parallel edits. All memory files are markdown with YAML frontmatter (`description`, `limit`, optional `metadata`). The `description` field enables progressive disclosure like skills, you see descriptions in your prompt and load full contents on demand; `limit` caps file size to keep system memory lean.\n\n## Memory layout\n\n**System memory** (`memory/system/`): Every `.md` file here is pinned directly into your system prompt — you see it at all times. This is your most valuable real estate: reserve it for durable knowledge that helps across sessions (user identity, persona, project architecture, conventions, gotchas). Do NOT store transient items here like specific commits, current work items, or session-specific notes — those dilute the signal.\n\n**Progressive memory**: Files outside `system/` are stored but not pinned in-context. Access them with standard file tools when you need deeper reference material good for large notes, historical records, transient work tracking, or data that doesn't need to be always-visible.\n\n**Recall** (conversation history): Your full message history is searchable even after messages leave your context window. Use the recall subagent to retrieve past discussions, decisions, and context from earlier sessions.\n\n## How files map to your prompt\n\n1. Each `.md` file in `memory/system/` is pinned to your system prompt with tags <system/context/{name}.md></system/context/{name}.md>\n2. The `memory_filesystem` block renders the current tree view of all available memory files\n3. The system prompt is only recompiled on compactions or message resets — your local edits take effect on the next recompilation\n\n## Syncing\n\nChanges you commit and push sync to the Letta server within seconds, and server-side changes sync back automatically.\n\n```bash\ncd \"$MEMORY_DIR\"\n\n# See what changed\ngit status\n\n# Commit and push your changes\ngit add .\ngit commit -m \"<type>: <what changed>\" # e.g. \"fix: update user prefs\", \"refactor: reorganize persona blocks\"\ngit push\n\n# Get latest from server\ngit pull\n```\nThe system will remind you when your memory has uncommitted changes. Sync when convenient.\n\n## History\n```bash\ngit -C \"$MEMORY_DIR\" log --oneline\n```\n";
6583
+ var system_prompt_memfs_default = "# Memory\n\nYour memory is projected onto the local memory filesystem (MemFS) at `$MEMORY_DIR` (usually `~/.letta/agents/$AGENT_ID/memory/`), including your memory blocks (in-context in the system prompt) and external memory. This projection makes it easy for you to modify your own context with filesystem operations which also include git tracking. Local changes are only propagated to your actual state on a successful push to remote, and the system prompt is only recompiled on compactions or new conversations (so may be stale). \n\n## Memory structure\nYou are responsible to maintaining a clear memory structure. All memory files are markdown with YAML frontmatter (`description`, optional `metadata`).\n\n**In-context memory** (`system/`): Memory files in `system/` correspond to memory blocks, which are pinned directly into your system prompt — visible at all times. This is your most valuable real estate: reserve it for durable knowledge that helps across sessions (user identity, persona, project architecture, conventions, gotchas). Do NOT store transient items here like specific commits, current work items, or session-specific notes — those dilute the signal.\n\n**External memory**: Files outside `system/` follow progressive disclosure — an index of files and descriptions is kept in the system prompt, but full contents must be retrieved on demand (e.g. by reading the file). Skills are a special type of external memory stored in the `skills/` folder. Use `[[path]]` to index files from memory blocks, or create discovery paths between related context (e.g. `[[reference/project/architecture.md]]` or `[[skills/using-slack/SKILL.md]]`).\n\n**Recall** (conversation history): Your full message history is searchable even after messages leave your context window. Use the recall subagent to retrieve past discussions, decisions, and context from earlier sessions.\n\n## Syncing\n\nChanges you commit and push sync to the Letta server within seconds, and server-side changes sync back automatically.\n\n```bash\ncd \"$MEMORY_DIR\"\n\n# See what changed\ngit status\n\n# Commit and push your changes\ngit add .\ngit commit --author=\"$AGENT_NAME <$AGENT_ID@letta.com>\" -m \"<type>: <what changed>\" # e.g. \"fix: update user prefs\", \"refactor: reorganize persona blocks\"\ngit push\n\n# Get latest from server\ngit pull\n```\nThe system will remind you when your memory has uncommitted changes. Sync when convenient.\n\n## History\n```bash\ngit -C \"$MEMORY_DIR\" log --oneline\n```\n";
6581
6584
  var init_system_prompt_memfs = () => {};
6582
6585
 
6583
6586
  // src/utils/error.ts
@@ -6708,12 +6711,6 @@ async function loadMemoryBlocksFromMdx() {
6708
6711
  if (frontmatter.description) {
6709
6712
  block.description = frontmatter.description;
6710
6713
  }
6711
- if (frontmatter.limit) {
6712
- const limit2 = parseInt(frontmatter.limit, 10);
6713
- if (!Number.isNaN(limit2) && limit2 > 0) {
6714
- block.limit = limit2;
6715
- }
6716
- }
6717
6714
  if (READ_ONLY_BLOCK_LABELS.includes(label)) {
6718
6715
  block.read_only = true;
6719
6716
  }
@@ -6840,7 +6837,7 @@ var history_analyzer_default = `---
6840
6837
  name: history-analyzer
6841
6838
  description: Analyze Claude Code or Codex conversation history and directly update agent memory files with insights
6842
6839
  tools: Read, Write, Bash, Glob, Grep
6843
- skills: migrating-from-codex-and-claude-code
6840
+ skills:
6844
6841
  model: auto
6845
6842
  memoryBlocks: none
6846
6843
  mode: stateless
@@ -6858,19 +6855,64 @@ Your memory files form the parent agent's identity and knowledge. Follow these p
6858
6855
  - **Generalize, don't memorize**: Distill patterns from repeated observations. "Always use uv, never pip (corrected 10+ times)" is valuable; a single offhand mention is not. Look for signal through repetition.
6859
6856
  - **System/ is the core program**: Only durable, generalizable knowledge belongs in \`system/\`. Distilled preferences, behavioral rules, project gotchas, conventions enforced through corrections. Evidence trails, raw session summaries, and verbose context go outside \`system/\`.
6860
6857
  - **Progressive disclosure**: Frontmatter descriptions should let the agent decide whether to load a file without reading it. Summaries and principles in \`system/\`; detail and evidence outside it, linked with \`[[path]]\`.
6861
- - **Identity continuity**: Treat this history as the agent's own past — not someone else's sessions. Findings should read as learned knowledge, not "analysis of external data."
6858
+ - **Identity continuity**: This history IS the agent's past. These are memories of working with this user you're reconstructing lived experience, not analyzing external data. Write findings as learned knowledge ("I've seen Sarah correct this 10+ times"), not research summaries ("The user appears to prefer...").
6862
6859
  - **Preserve and connect**: If a memory file already has good content, extend it — don't replace it. Use \`[[path]]\` links to connect new findings to existing memory.
6860
+ - **Promote findings into canonical memory**: Don't leave durable insights trapped in generic ingestion files if they can be promoted into focused memory like \`system/human/identity.md\`, \`system/human/prefs/workflow.md\`, or \`system/<project>/gotchas.md\`.
6863
6861
 
6864
6862
  ## Goal
6865
6863
 
6866
- Distill actionable knowledge from conversation history into well-organized memory. Focus on:
6864
+ Distill actionable knowledge from conversation history into well-organized memory. You MUST produce findings in all three categories below — missing any category is a failure.
6865
+
6866
+ This is not a request for a thin recap. Your output should be detailed enough that the parent agent can use it in future sessions without rereading the history chunk.
6867
+
6868
+ ### Required Output Categories
6869
+
6870
+ You MUST extract and document all three:
6871
+
6872
+ **1. User Personality & Identity** (REQUIRED)
6873
+ - How would you describe them as a person? (e.g., "pragmatic builder who values shipping over perfection")
6874
+ - What drives them? What are their goals? (e.g., "building tools that reduce friction for developers")
6875
+ - Communication style beyond just "direct" — do they joke? Use sarcasm? Have catchphrases?
6876
+ - Quirks, linguistic patterns, unique attributes
6877
+ - Pattern-match to common personas if applicable (e.g., "scrappy startup engineer", "meticulous architect")
6878
+
6879
+ **2. Hard Rules & Preferences** (REQUIRED)
6880
+ - Coding preferences with enforcement evidence (e.g., "Use uv — corrected 10+ times")
6881
+ - Workflow patterns (testing habits, commit style, tool choices)
6882
+ - What frustrates them and why
6883
+ - Explicit "always/never" statements
6884
+
6885
+ **3. Project Context** (REQUIRED)
6886
+ - Codebase structures, conventions, patterns
6887
+ - Gotchas discovered through debugging
6888
+ - Which files are safe to edit vs deprecated
6889
+ - Environment quirks
6890
+
6891
+ If you cannot extract meaningful findings for ANY category, explicitly state why (e.g., "Insufficient data for personality analysis — only 5 prompts, all about a single bug fix").
6892
+
6893
+ ### Quality Bar
6894
+
6895
+ When sufficient data exists, aim to extract at least:
6896
+ - **5+ durable findings** for user personality / identity
6897
+ - **8+ durable findings** for hard rules / preferences
6898
+ - **8+ durable findings** for project context
6899
+
6900
+ If you produce materially fewer findings in a category, explain why the chunk truly lacked signal.
6901
+
6902
+ Avoid low-value summaries like:
6903
+ - "User is direct"
6904
+ - "Project uses TypeScript"
6905
+ - "Uses conventional commits"
6906
+
6907
+ These are insufficient unless paired with concrete operational detail, enforcement patterns, or repo-specific implications.
6908
+
6909
+ ### What NOT to Store
6910
+ One-off events, session-by-session summaries, anything that can be retrieved from conversation history on demand.
6867
6911
 
6868
- - **Preferences enforced through corrections**: What the user repeatedly corrects their AI assistant about — these are gold. They reveal what the user actually cares about vs. what's merely documented.
6869
- - **Project gotchas**: Footguns, fragile areas, and non-obvious constraints discovered through debugging sessions and errors.
6870
- - **Working patterns**: How the user works — debugging style, testing habits, tools they reach for, communication style.
6871
- - **Conventions actually used**: Not just what's in a README, but what's enforced through practice.
6912
+ ### What TO Preserve
6913
+ Focus on understanding **why** the user reacted the way they did — what mistake or behavior triggered the correction? The pattern matters more than the quote. For example, don't just record "user said stop adding stuff" — record that the agent was over-engineering by adding abstractions when a simple flag change was needed. Quotes can serve as supporting evidence, but the real value is the behavioral pattern and what to do differently.
6872
6914
 
6873
- **What NOT to store**: Raw quotes, one-off events, session-by-session summaries, anything that can be retrieved from conversation history on demand.
6915
+ Keep specific correction counts ("corrected 10+ times"), specific file paths, and specific gotchas with context. Specificity is identity; vague summaries are forgettable.
6874
6916
 
6875
6917
  ## Workflow
6876
6918
 
@@ -6888,12 +6930,29 @@ git worktree add "$WORKTREE_DIR/$BRANCH_NAME" -b "$BRANCH_NAME"
6888
6930
  If worktree creation fails (locked index), retry up to 3 times with backoff (sleep 2, 5, 10). Never delete \`.git/index.lock\` manually. All edits go in \`$WORKTREE_DIR/$BRANCH_NAME/\`.
6889
6931
 
6890
6932
  ### 2. Read existing memory
6891
-
6892
- Read all files in your worktree's \`system/\` directory. Understand what's already there so you can extend it, not duplicate it.
6933
+ Read the memory files in your worktree, to understand what already exists in the memory filesystem.
6893
6934
 
6894
6935
  ### 3. Read and analyze history
6895
6936
 
6896
- Use the \`migrating-from-codex-and-claude-code\` skill for data access patterns. Filter to your assigned chunk.
6937
+ Your prompt will specify a pre-split JSONL chunk file and its source format. Use these patterns to read it:
6938
+
6939
+ **Claude Code** (\`~/.claude/\`):
6940
+ - \`history.jsonl\` — each line: \`.display\` (prompt text), \`.timestamp\` (unix ms), \`.project\` (working dir), \`.sessionId\`
6941
+ - Session files at \`~/.claude/projects/<encoded-path>/<session-uuid>.jsonl\` (path encoding: \`/\` → \`-\`)
6942
+ - User messages: \`jq 'select(.type == "user") | .message.content'\`
6943
+ - Assistant text: \`jq 'select(.type == "assistant") | .message.content[] | select(.type == "text") | .text'\`
6944
+ - Tool calls: \`jq 'select(.type == "assistant") | .message.content[] | select(.type == "tool_use") | {name, input}'\`
6945
+ - Summaries: \`jq 'select(.type == "summary") | .summary'\`
6946
+
6947
+ **OpenAI Codex** (\`~/.codex/\`):
6948
+ - \`history.jsonl\` — each line: \`.text\` (prompt text), \`.ts\` (unix seconds) — no project path
6949
+ - Session files at \`~/.codex/sessions/<year>/<month>/<day>/rollout-*.jsonl\`
6950
+ - Session metadata (first line): \`jq 'select(.type == "session_meta") | .payload.cwd'\` (to get project dir)
6951
+ - User messages: \`jq 'select(.type == "event_msg" and .payload.type == "user_message") | .payload.message'\`
6952
+ - Assistant text: \`jq 'select(.type == "response_item" and .payload.type == "message") | .payload.content[] | select(.type == "output_text") | .text'\`
6953
+ - Tool calls: \`jq 'select(.type == "response_item" and .payload.type == "function_call") | {name: .payload.name, args: .payload.arguments}'\`
6954
+
6955
+ **Key format difference**: Claude uses \`.timestamp\` (milliseconds) and \`.display\`; Codex uses \`.ts\` (seconds) and \`.text\`.
6897
6956
 
6898
6957
  Look for **repeated patterns**, not isolated events:
6899
6958
  - Count correction frequency — 10 corrections on the same topic >> 1 mention
@@ -6901,38 +6960,70 @@ Look for **repeated patterns**, not isolated events:
6901
6960
  - Implicit preferences revealed by what commands they run, what patterns they follow
6902
6961
  - Frustration signals — "no", "undo", rapid corrections, /clear, model switches
6903
6962
 
6963
+ **For personality analysis**, look beyond the reaction to what caused it:
6964
+ - What agent behaviors triggered corrections? (over-engineering, wrong tool, verbose explanations, etc.)
6965
+ - What agent behaviors got positive responses? (fast fixes, running tests unprompted, etc.)
6966
+ - How do they phrase requests? (imperative, collaborative, questioning?)
6967
+ - What topics excite them vs bore them?
6968
+ - What's their tolerance for explanation vs "just fix it"?
6969
+ - How do they handle mistakes — their own and the agent's?
6970
+
6904
6971
  ### 4. Update memory files
6905
6972
 
6906
6973
  **Content placement:**
6907
6974
  - \`system/\`: Generalized rules, distilled preferences, project gotchas, identity. Keep files lean — bullets, short lines, scannable.
6908
6975
  - Outside \`system/\`: Evidence, detailed history, verbose context. Link from system/ with \`[[path]]\`.
6909
6976
 
6977
+ **Preferred canonical paths:**
6978
+ - \`system/human/identity.md\`
6979
+ - \`system/human/prefs/communication.md\`
6980
+ - \`system/human/prefs/workflow.md\`
6981
+ - \`system/human/prefs/coding.md\`
6982
+ - \`system/<project>/conventions.md\`
6983
+ - \`system/<project>/gotchas.md\`
6984
+
6985
+ If the current memory uses a more compressed layout, extend it carefully, but prefer splitting into these focused files when there is enough material to justify the move.
6986
+
6910
6987
  **File structure:**
6911
6988
  - Use the project's **real name** as directory prefix (e.g. \`my-app/conventions.md\`), not generic \`project/\`
6912
6989
  - One concept per file, nested with \`/\` paths
6913
6990
  - Every file needs a meaningful \`description\` in frontmatter
6914
6991
  - Write for the agent's future self — clean, actionable, no clutter
6915
6992
 
6993
+ Each durable finding should include at least one of:
6994
+ - correction frequency or intensity
6995
+ - concrete commands that worked or failed
6996
+ - concrete file or directory paths
6997
+ - date range or source reference for future lookup
6998
+ - why the rule matters in practice
6999
+
6916
7000
  You can also cite the files if you want to note where something came from (e.g. \`(from: ~/.claude/history.jsonl)\`).
6917
7001
 
6918
7002
  ### 5. Commit
6919
7003
 
7004
+ Before writing the commit, resolve the actual ID values:
7005
+ \`\`\`bash
7006
+ echo "AGENT_ID=$LETTA_AGENT_ID"
7007
+ echo "PARENT_AGENT_ID=$LETTA_PARENT_AGENT_ID"
7008
+ \`\`\`
7009
+
7010
+ Use the printed values (e.g., \`agent-abc123...\`) in the trailers. If a variable is empty or unset, omit that trailer. Never write a literal variable name like \`$LETTA_AGENT_ID\` or \`$AGENT_ID\` in the commit message.
7011
+
6920
7012
  \`\`\`bash
6921
7013
  cd $WORKTREE_DIR/$BRANCH_NAME
6922
7014
  git add -A
6923
- git commit -m "<type>(history-analyzer): [summary]
7015
+ git commit --author="History Analyzer <<ACTUAL_AGENT_ID>@letta.com>" -m "<type>(history-analyzer): <summary>
6924
7016
 
6925
7017
  Source: [file path] ([N] prompts, [DATE RANGE])
6926
- Key updates:
6927
- - [file]: [what was added/changed]
7018
+
7019
+ Updates:
7020
+ - <what changed and why>
6928
7021
 
6929
7022
  Generated-By: Letta Code
6930
7023
  Agent-ID: <ACTUAL_AGENT_ID>
6931
7024
  Parent-Agent-ID: <ACTUAL_PARENT_AGENT_ID>"
6932
7025
  \`\`\`
6933
7026
 
6934
- Resolve \`ACTUAL_AGENT_ID\` and \`ACTUAL_PARENT_AGENT_ID\` by running \`echo $LETTA_AGENT_ID\` and \`echo $LETTA_PARENT_AGENT_ID\` first. Never write literal variable names in the commit message. Omit trailers if the variable is empty.
6935
-
6936
7027
  **Commit types**: \`chore\` (routine ingestion), \`feat\` (new memory topics), \`refactor\` (reorganizing by domain).
6937
7028
 
6938
7029
  ## Rules
@@ -6940,7 +7031,8 @@ Resolve \`ACTUAL_AGENT_ID\` and \`ACTUAL_PARENT_AGENT_ID\` by running \`echo $LE
6940
7031
  - Work in your worktree — do NOT edit the memory dir directly
6941
7032
  - Do NOT merge into main — the parent agent handles merging
6942
7033
  - Preserve existing content — extend or refine, don't replace
6943
- - Quality over quantity fewer distilled insights beat many raw observations
7034
+ - Preserve specificityspecific quotes, correction counts, and file paths are more valuable than vague summaries. Don't compress away the details that give the parent agent its character and grounding.
7035
+ - **REQUIRED**: You MUST produce findings for all three output categories (Personality, Rules, Project). If any category lacks data, explicitly state why.
6944
7036
  `;
6945
7037
  var init_history_analyzer = () => {};
6946
7038
 
@@ -53756,7 +53848,7 @@ If a directory has more than 1,000 entries, only the first 1,000 will be shown.`
53756
53848
  var init_LS = () => {};
53757
53849
 
53758
53850
  // src/tools/descriptions/Memory.md
53759
- var Memory_default = '# Memory\nA convinience tool for memories stored in the memory directory (`$MEMORY_DIR`) that automatically commits and pushes changes. \n\nFiles stored inside of `system/` eventually become part of the agent\'s system prompt, so are always in the context window and do not need to be re-read. Other files only have metadata in the system prompt, so may need to be explicitly read to be updated. \n\nSupported operations on memory files: \n- `str_replace`\n- `insert`\n- `delete` (files, or directories recursively)\n- `rename` (path rename only)\n- `update_description`\n- `create`\nMore general operations can be performanced through directory modifying the files. \n\nPath formats accepted:\n- relative memory file paths (e.g. `system/contacts.md`, `reference/project/team.md`)\n- absolute paths only when they are inside `$MEMORY_DIR`\n\nNote: absolute paths outside `$MEMORY_DIR` are rejected.\n\nExamples:\n\n```python\n# Replace text in a memory file \nmemory(command="str_replace", reason="Update theme preference", path="system/human/preferences.md", old_string="theme: dark", new_string="theme: light")\n\n# Insert text at line 5\nmemory(command="insert", reason="Add note about meeting", path="reference/history/meeting-notes.md", insert_line=5, insert_text="New note here")\n\n# Delete a memory file \nmemory(command="delete", reason="Remove stale notes", path="reference/history/old_notes.md")\n\n# Rename a memory file \nmemory(command="rename", reason="Promote temp notes", old_path="reference/history/temp.md", new_path="reference/history/permanent.md")\n\n# Update a block description\nmemory(command="update_description", reason="Clarify coding prefs block", path="system/human/prefs/coding.md", description="Dr. Wooders\' coding preferences.")\n\n# Create a block with starting text\nmemory(command="create", reason="Track coding preferences", path="system/human/prefs/coding.md", description="The user\'s coding preferences.", file_text="The user seems to add type hints to all of their Python code.")\n\n# Create an empty block\nmemory(command="create", reason="Create coding preferences block", path="reference/history/coding_preferences.md", description="The user\'s coding preferences.")\n```\n';
53851
+ var Memory_default = "# Memory\nA convinience tool for memories stored in the memory directory (`$MEMORY_DIR`) that automatically commits and pushes changes. \n\nFiles stored inside of `system/` eventually become part of the agent's system prompt, so are always in the context window and do not need to be re-read. Other files only have metadata in the system prompt, so may need to be explicitly read to be updated. \n\nSupported operations on memory files: \n- `str_replace`\n- `insert`\n- `delete` (files, or directories recursively)\n- `rename` (path rename only)\n- `update_description`\n- `create`\nMore general operations can be performanced through directory modifying the files. \n\nPath formats accepted:\n- relative memory file paths (e.g. `system/contacts.md`, `reference/project/team.md`)\n- absolute paths only when they are inside `$MEMORY_DIR`\n\nNote: absolute paths outside `$MEMORY_DIR` are rejected.\n\nWhen creating or deleting files, check for `[[path]]` references in other memory files that may need to be added or updated. Keeping references consistent ensures future discoverability.\n\nExamples:\n\n```python\n# Replace text in a memory file \nmemory(command=\"str_replace\", reason=\"Update theme preference\", path=\"system/human/preferences.md\", old_string=\"theme: dark\", new_string=\"theme: light\")\n\n# Insert text at line 5\nmemory(command=\"insert\", reason=\"Add note about meeting\", path=\"reference/history/meeting-notes.md\", insert_line=5, insert_text=\"New note here\")\n\n# Delete a memory file \nmemory(command=\"delete\", reason=\"Remove stale notes\", path=\"reference/history/old_notes.md\")\n\n# Rename a memory file \nmemory(command=\"rename\", reason=\"Promote temp notes\", old_path=\"reference/history/temp.md\", new_path=\"reference/history/permanent.md\")\n\n# Update a block description\nmemory(command=\"update_description\", reason=\"Clarify coding prefs block\", path=\"system/human/prefs/coding.md\", description=\"Dr. Wooders' coding preferences.\")\n\n# Create a block with starting text\nmemory(command=\"create\", reason=\"Track coding preferences\", path=\"system/human/prefs/coding.md\", description=\"The user's coding preferences.\", file_text=\"The user seems to add type hints to all of their Python code.\")\n\n# Create an empty block\nmemory(command=\"create\", reason=\"Create coding preferences block\", path=\"reference/history/coding_preferences.md\", description=\"The user's coding preferences.\")\n```\n";
53760
53852
  var init_Memory = () => {};
53761
53853
 
53762
53854
  // src/tools/descriptions/MemoryApplyPatch.md
@@ -65799,6 +65891,9 @@ function parseResultFromStdout(stdout, agentId) {
65799
65891
  };
65800
65892
  }
65801
65893
  }
65894
+ function resolveSubagentWorkingDirectory(env3 = process.env, fallbackCwd = process.cwd()) {
65895
+ return env3.USER_CWD || fallbackCwd;
65896
+ }
65802
65897
  function resolveSubagentLauncher(cliArgs, options = {}) {
65803
65898
  const env3 = options.env ?? process.env;
65804
65899
  const argv = options.argv ?? process.argv;
@@ -65913,8 +66008,9 @@ async function executeSubagent(type, config, model, userPrompt, baseURL, subagen
65913
66008
  const settings = await settingsManager.getSettingsWithSecureTokens();
65914
66009
  const inheritedApiKey = process.env.LETTA_API_KEY || settings.env?.LETTA_API_KEY;
65915
66010
  const inheritedBaseUrl = process.env.LETTA_BASE_URL || settings.env?.LETTA_BASE_URL;
66011
+ const subagentWorkingDirectory = resolveSubagentWorkingDirectory();
65916
66012
  const proc2 = spawn4(launcher.command, launcher.args, {
65917
- cwd: process.cwd(),
66013
+ cwd: subagentWorkingDirectory,
65918
66014
  env: {
65919
66015
  ...process.env,
65920
66016
  ...inheritedApiKey && { LETTA_API_KEY: inheritedApiKey },
@@ -73306,18 +73402,29 @@ var init_turn_recovery_policy = __esm(() => {
73306
73402
  });
73307
73403
 
73308
73404
  // src/agent/approval-recovery.ts
73309
- async function fetchRunErrorDetail(runId) {
73405
+ async function fetchRunErrorInfo(runId) {
73310
73406
  if (!runId)
73311
73407
  return null;
73312
73408
  try {
73313
73409
  const client = await getClient();
73314
73410
  const run = await client.runs.retrieve(runId);
73315
73411
  const metaError = run.metadata?.error;
73316
- return metaError?.detail ?? metaError?.message ?? metaError?.error?.detail ?? metaError?.error?.message ?? null;
73412
+ const nestedError = metaError?.error;
73413
+ const errorInfo = {
73414
+ error_type: metaError?.error_type ?? metaError?.type ?? nestedError?.error_type ?? nestedError?.type,
73415
+ message: metaError?.message ?? nestedError?.message,
73416
+ detail: metaError?.detail ?? nestedError?.detail,
73417
+ run_id: metaError?.run_id ?? nestedError?.run_id ?? runId
73418
+ };
73419
+ return errorInfo.error_type || errorInfo.message || errorInfo.detail ? errorInfo : null;
73317
73420
  } catch {
73318
73421
  return null;
73319
73422
  }
73320
73423
  }
73424
+ async function fetchRunErrorDetail(runId) {
73425
+ const errorInfo = await fetchRunErrorInfo(runId);
73426
+ return errorInfo?.detail ?? errorInfo?.message ?? null;
73427
+ }
73321
73428
  var init_approval_recovery = __esm(async () => {
73322
73429
  init_turn_recovery_policy();
73323
73430
  await init_client2();
@@ -75761,6 +75868,7 @@ class StreamProcessor {
75761
75868
  const errorDetail = chunkWithError.error.detail || "";
75762
75869
  errorInfo = {
75763
75870
  message: errorDetail ? `${errorText}: ${errorDetail}` : errorText,
75871
+ detail: errorDetail || undefined,
75764
75872
  run_id: this.lastRunId || undefined
75765
75873
  };
75766
75874
  }
@@ -77090,13 +77198,53 @@ function requestApprovalOverWS(runtime, socket, requestId, controlRequest) {
77090
77198
  if (socket.readyState !== WebSocket.OPEN) {
77091
77199
  return Promise.reject(new Error("WebSocket not open"));
77092
77200
  }
77201
+ const abortSignal = runtime.activeAbortController?.signal ?? null;
77202
+ const isInterrupted = () => runtime.cancelRequested || abortSignal?.aborted === true;
77203
+ if (isInterrupted()) {
77204
+ return Promise.reject(new Error("Cancelled by user"));
77205
+ }
77093
77206
  return new Promise((resolve23, reject) => {
77207
+ let settled = false;
77208
+ const cleanupAbortListener = () => {
77209
+ abortSignal?.removeEventListener("abort", handleAbort);
77210
+ };
77211
+ const wrappedResolve = (response) => {
77212
+ if (settled) {
77213
+ return;
77214
+ }
77215
+ settled = true;
77216
+ cleanupAbortListener();
77217
+ resolve23(response);
77218
+ };
77219
+ const wrappedReject = (error) => {
77220
+ if (settled) {
77221
+ return;
77222
+ }
77223
+ settled = true;
77224
+ cleanupAbortListener();
77225
+ reject(error);
77226
+ };
77227
+ const handleAbort = () => {
77228
+ runtime.pendingApprovalResolvers.delete(requestId);
77229
+ runtime.listener.approvalRuntimeKeyByRequestId.delete(requestId);
77230
+ wrappedReject(new Error("Cancelled by user"));
77231
+ };
77232
+ abortSignal?.addEventListener("abort", handleAbort, { once: true });
77233
+ if (isInterrupted()) {
77234
+ handleAbort();
77235
+ return;
77236
+ }
77094
77237
  runtime.pendingApprovalResolvers.set(requestId, {
77095
- resolve: resolve23,
77096
- reject,
77238
+ resolve: wrappedResolve,
77239
+ reject: wrappedReject,
77097
77240
  controlRequest
77098
77241
  });
77099
77242
  runtime.listener.approvalRuntimeKeyByRequestId.set(requestId, runtime.key);
77243
+ if (isInterrupted()) {
77244
+ handleAbort();
77245
+ return;
77246
+ }
77247
+ runtime.lastStopReason = "requires_approval";
77100
77248
  setLoopStatus(runtime, "WAITING_ON_APPROVAL");
77101
77249
  emitLoopStatusIfOpen(runtime.listener, {
77102
77250
  agent_id: runtime.agentId,
@@ -77392,12 +77540,18 @@ function populateInterruptQueue(runtime, input) {
77392
77540
  return false;
77393
77541
  }
77394
77542
  function consumeInterruptQueue(runtime, agentId, conversationId) {
77543
+ const ctx = runtime.pendingInterruptedContext;
77544
+ const matchingContext = !!ctx && ctx.agentId === agentId && ctx.conversationId === conversationId && ctx.continuationEpoch === runtime.continuationEpoch;
77395
77545
  if (!runtime.pendingInterruptedResults || runtime.pendingInterruptedResults.length === 0) {
77546
+ if (matchingContext) {
77547
+ runtime.pendingInterruptedResults = null;
77548
+ runtime.pendingInterruptedContext = null;
77549
+ runtime.pendingInterruptedToolCallIds = null;
77550
+ }
77396
77551
  return null;
77397
77552
  }
77398
- const ctx = runtime.pendingInterruptedContext;
77399
77553
  let result = null;
77400
- if (ctx && ctx.agentId === agentId && ctx.conversationId === conversationId && ctx.continuationEpoch === runtime.continuationEpoch) {
77554
+ if (matchingContext) {
77401
77555
  result = {
77402
77556
  approvalMessage: {
77403
77557
  type: "approval",
@@ -77565,6 +77719,175 @@ var init_permissionMode = __esm(() => {
77565
77719
  init_remote_settings();
77566
77720
  });
77567
77721
 
77722
+ // src/websocket/listener/recoverable-notices.ts
77723
+ function getRecoverableStatusNoticeVisibility(kind) {
77724
+ switch (kind) {
77725
+ case "stale_approval_conflict_recovery":
77726
+ return "debug_only";
77727
+ default:
77728
+ return "transcript";
77729
+ }
77730
+ }
77731
+ function getRecoverableRetryNoticeVisibility(kind, attempt) {
77732
+ switch (kind) {
77733
+ case "transient_provider_retry":
77734
+ return attempt === 1 ? "debug_only" : "transcript";
77735
+ default:
77736
+ return "transcript";
77737
+ }
77738
+ }
77739
+ function isDesktopDebugPanelMirrorEnabled() {
77740
+ return process.env.LETTA_DESKTOP_DEBUG_PANEL === "1";
77741
+ }
77742
+ function mirrorRecoverableNoticeToDesktopDebugPanel(message) {
77743
+ if (!isDesktopDebugPanelMirrorEnabled()) {
77744
+ return;
77745
+ }
77746
+ try {
77747
+ process.stderr.write(`${DESKTOP_DEBUG_PANEL_INFO_PREFIX} ${message}
77748
+ `);
77749
+ } catch {}
77750
+ }
77751
+ function toStructuredApiError(errorInfo) {
77752
+ if (!errorInfo?.error_type || !errorInfo.run_id) {
77753
+ return;
77754
+ }
77755
+ return {
77756
+ message_type: "error_message",
77757
+ message: errorInfo.message || errorInfo.detail || "An error occurred",
77758
+ error_type: errorInfo.error_type,
77759
+ run_id: errorInfo.run_id,
77760
+ ...errorInfo.detail ? { detail: errorInfo.detail } : {}
77761
+ };
77762
+ }
77763
+ function getStructuredApiErrorFromError(error) {
77764
+ if (!(error instanceof Error)) {
77765
+ return;
77766
+ }
77767
+ const errorWithStructuredInfo = error;
77768
+ return errorWithStructuredInfo.apiError ?? toStructuredApiError(errorWithStructuredInfo.runErrorInfo);
77769
+ }
77770
+ function buildStructuredFormatInput(apiError) {
77771
+ return {
77772
+ error: {
77773
+ error: {
77774
+ type: apiError.error_type,
77775
+ message: apiError.message,
77776
+ ...apiError.detail ? { detail: apiError.detail } : {}
77777
+ },
77778
+ run_id: apiError.run_id
77779
+ }
77780
+ };
77781
+ }
77782
+ function isAbortLikeError(error) {
77783
+ if (error instanceof APIUserAbortError) {
77784
+ return true;
77785
+ }
77786
+ if (!(error instanceof Error)) {
77787
+ return false;
77788
+ }
77789
+ const errorWithCode = error;
77790
+ return error.name === "AbortError" || error.message === "The operation was aborted" || errorWithCode.code === "ABORT_ERR";
77791
+ }
77792
+ function isTerminatedProcessNoise(message) {
77793
+ return message.trim().toLowerCase() === "terminated";
77794
+ }
77795
+ function isProxyTransportError(detail, error, message) {
77796
+ if (error instanceof APIError2 && error.status >= 500 && detail.toLowerCase().includes("trying to proxy")) {
77797
+ return true;
77798
+ }
77799
+ return detail.toLowerCase().includes("error occurred while trying to proxy") || message.toLowerCase().includes("error occurred while trying to proxy");
77800
+ }
77801
+ function getLoopErrorNoticeDecision(params) {
77802
+ const apiError = params.apiError ?? toStructuredApiError(params.errorInfo) ?? toStructuredApiError(params.runErrorInfo) ?? getStructuredApiErrorFromError(params.error);
77803
+ const detail = apiError?.detail ?? params.errorInfo?.detail ?? params.runErrorInfo?.detail ?? extractConflictDetail(params.error) ?? "";
77804
+ if (params.cancelRequested || params.abortSignal?.aborted || isAbortLikeError(params.error) || isTerminatedProcessNoise(params.message)) {
77805
+ return {
77806
+ visibility: "debug_only",
77807
+ message: params.message
77808
+ };
77809
+ }
77810
+ const cloudflareMessage = checkCloudflareEdgeError2(detail) ?? checkCloudflareEdgeError2(params.message);
77811
+ if (cloudflareMessage) {
77812
+ return {
77813
+ visibility: "transcript",
77814
+ message: cloudflareMessage,
77815
+ apiError
77816
+ };
77817
+ }
77818
+ if (isProxyTransportError(detail, params.error, params.message)) {
77819
+ return {
77820
+ visibility: "transcript",
77821
+ message: "Connection to Letta service failed. Please retry.",
77822
+ apiError
77823
+ };
77824
+ }
77825
+ const formattedMessage = formatErrorDetails2(apiError ? buildStructuredFormatInput(apiError) : params.error ?? params.message, params.agentId ?? undefined, params.conversationId ?? undefined);
77826
+ return {
77827
+ visibility: "transcript",
77828
+ message: formattedMessage,
77829
+ apiError
77830
+ };
77831
+ }
77832
+ function emitLoopErrorNotice(socket, runtime, params) {
77833
+ const decision = getLoopErrorNoticeDecision(params);
77834
+ if (decision.visibility === "debug_only") {
77835
+ debugLog("recovery", `Debug-only loop error (${params.stopReason}): ${params.message}`);
77836
+ mirrorRecoverableNoticeToDesktopDebugPanel(params.message);
77837
+ return;
77838
+ }
77839
+ emitLoopErrorDelta(socket, runtime, {
77840
+ message: decision.message,
77841
+ stopReason: params.stopReason,
77842
+ isTerminal: params.isTerminal,
77843
+ runId: params.runId,
77844
+ agentId: params.agentId,
77845
+ conversationId: params.conversationId,
77846
+ apiError: decision.apiError
77847
+ });
77848
+ }
77849
+ function emitRecoverableStatusNotice(socket, runtime, params) {
77850
+ const visibility = getRecoverableStatusNoticeVisibility(params.kind);
77851
+ if (visibility === "debug_only") {
77852
+ debugLog("recovery", `Debug-only lifecycle notice (${params.kind}): ${params.message}`);
77853
+ mirrorRecoverableNoticeToDesktopDebugPanel(params.message);
77854
+ return;
77855
+ }
77856
+ emitStatusDelta(socket, runtime, {
77857
+ message: params.message,
77858
+ level: params.level,
77859
+ runId: params.runId,
77860
+ agentId: params.agentId,
77861
+ conversationId: params.conversationId
77862
+ });
77863
+ }
77864
+ function emitRecoverableRetryNotice(socket, runtime, params) {
77865
+ const visibility = getRecoverableRetryNoticeVisibility(params.kind, params.attempt);
77866
+ if (visibility === "debug_only") {
77867
+ debugLog("recovery", `Debug-only retry notice (${params.kind}, attempt ${params.attempt}/${params.maxAttempts}): ${params.message}`);
77868
+ mirrorRecoverableNoticeToDesktopDebugPanel(params.message);
77869
+ return;
77870
+ }
77871
+ emitRetryDelta(socket, runtime, {
77872
+ message: params.message,
77873
+ reason: params.reason,
77874
+ attempt: params.attempt,
77875
+ maxAttempts: params.maxAttempts,
77876
+ delayMs: params.delayMs,
77877
+ runId: params.runId,
77878
+ agentId: params.agentId,
77879
+ conversationId: params.conversationId
77880
+ });
77881
+ }
77882
+ var DESKTOP_DEBUG_PANEL_INFO_PREFIX = "[LETTA_DESKTOP_DEBUG_PANEL_INFO]";
77883
+ var init_recoverable_notices = __esm(async () => {
77884
+ init_error();
77885
+ init_turn_recovery_policy();
77886
+ init_errorFormatter();
77887
+ init_debug();
77888
+ await init_protocol_outbound();
77889
+ });
77890
+
77568
77891
  // node_modules/diff/libesm/diff/base.js
77569
77892
  class Diff {
77570
77893
  diff(oldStr, newStr, options = {}) {
@@ -79144,13 +79467,15 @@ async function drainRecoveryStreamWithEmission(recoveryStream, socket, runtime,
79144
79467
  }
79145
79468
  }
79146
79469
  if (errorInfo) {
79147
- emitLoopErrorDelta(socket, runtime, {
79470
+ emitLoopErrorNotice(socket, runtime, {
79148
79471
  message: errorInfo.message || "Stream error",
79149
79472
  stopReason: errorInfo.error_type || "error",
79150
79473
  isTerminal: false,
79151
79474
  runId: runtime.activeRunId || errorInfo.run_id,
79152
79475
  agentId: params.agentId ?? undefined,
79153
- conversationId: params.conversationId
79476
+ conversationId: params.conversationId,
79477
+ errorInfo,
79478
+ abortSignal: params.abortSignal
79154
79479
  });
79155
79480
  }
79156
79481
  if (shouldOutput) {
@@ -79201,7 +79526,7 @@ function finalizeHandledRecoveryTurn(runtime, socket, params) {
79201
79526
  const runId = runtime.activeRunId;
79202
79527
  clearActiveRunState(runtime);
79203
79528
  emitRuntimeStateUpdates(runtime, scope);
79204
- emitLoopErrorDelta(socket, runtime, {
79529
+ emitLoopErrorNotice(socket, runtime, {
79205
79530
  message: `Recovery continuation ended unexpectedly: ${terminalStopReason}`,
79206
79531
  stopReason: terminalStopReason,
79207
79532
  isTerminal: true,
@@ -79538,7 +79863,8 @@ var init_recovery = __esm(async () => {
79538
79863
  init_approval_suggestions(),
79539
79864
  init_interrupts(),
79540
79865
  init_protocol_outbound(),
79541
- init_queue()
79866
+ init_queue(),
79867
+ init_recoverable_notices()
79542
79868
  ]);
79543
79869
  });
79544
79870
 
@@ -79646,9 +79972,6 @@ async function resolveStaleApprovals(runtime, socket, abortSignal, deps = {}) {
79646
79972
  ];
79647
79973
  let pendingNeedsUserInput = [...needsUserInput];
79648
79974
  if (pendingNeedsUserInput.length > 0) {
79649
- runtime.lastStopReason = "requires_approval";
79650
- setLoopStatus(runtime, "WAITING_ON_APPROVAL", scope);
79651
- emitRuntimeStateUpdates(runtime, scope);
79652
79975
  while (pendingNeedsUserInput.length > 0) {
79653
79976
  const ac = pendingNeedsUserInput.shift();
79654
79977
  if (!ac) {
@@ -79842,8 +80165,8 @@ async function sendMessageStreamWithRetry(conversationId, messages, opts, socket
79842
80165
  throw new Error("Cancelled by user");
79843
80166
  }
79844
80167
  }
79845
- const detail = await fetchRunErrorDetail(runtime.activeRunId);
79846
- throw new Error(detail || `Pre-stream approval conflict after ${preStreamRecoveryAttempts} recovery attempts`);
80168
+ const runErrorInfo = await fetchRunErrorInfo(runtime.activeRunId);
80169
+ throw Object.assign(new Error(runErrorInfo?.detail || runErrorInfo?.message || `Pre-stream approval conflict after ${preStreamRecoveryAttempts} recovery attempts`), { runErrorInfo });
79847
80170
  }
79848
80171
  if (action === "retry_transient") {
79849
80172
  runtime.isRecoveringApprovals = true;
@@ -79976,8 +80299,8 @@ async function sendApprovalContinuationWithRetry(conversationId, messages, opts,
79976
80299
  }
79977
80300
  continue;
79978
80301
  }
79979
- const detail = await fetchRunErrorDetail(runtime.activeRunId);
79980
- throw new Error(detail || `Approval continuation conflict after ${preStreamRecoveryAttempts} recovery attempts`);
80302
+ const runErrorInfo = await fetchRunErrorInfo(runtime.activeRunId);
80303
+ throw Object.assign(new Error(runErrorInfo?.detail || runErrorInfo?.message || `Approval continuation conflict after ${preStreamRecoveryAttempts} recovery attempts`), { runErrorInfo });
79981
80304
  }
79982
80305
  if (action === "retry_transient") {
79983
80306
  runtime.isRecoveringApprovals = true;
@@ -80103,7 +80426,7 @@ async function handleApprovalStop(params) {
80103
80426
  agent_id: agentId,
80104
80427
  conversation_id: conversationId
80105
80428
  });
80106
- emitLoopErrorDelta(socket, runtime, {
80429
+ emitLoopErrorNotice(socket, runtime, {
80107
80430
  message: "requires_approval stop returned no approvals",
80108
80431
  stopReason: "error",
80109
80432
  isTerminal: true,
@@ -80177,11 +80500,6 @@ async function handleApprovalStop(params) {
80177
80500
  if (shouldInterrupt()) {
80178
80501
  return interruptTermination();
80179
80502
  }
80180
- runtime.lastStopReason = "requires_approval";
80181
- setLoopStatus(runtime, "WAITING_ON_APPROVAL", {
80182
- agent_id: agentId,
80183
- conversation_id: conversationId
80184
- });
80185
80503
  while (pendingNeedsUserInput.length > 0) {
80186
80504
  const ac = pendingNeedsUserInput.shift();
80187
80505
  if (!ac) {
@@ -80411,6 +80729,7 @@ var init_turn_approval = __esm(async () => {
80411
80729
  init_interrupts(),
80412
80730
  init_protocol_outbound(),
80413
80731
  init_queue(),
80732
+ init_recoverable_notices(),
80414
80733
  init_recovery(),
80415
80734
  init_send()
80416
80735
  ]);
@@ -80742,13 +81061,16 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
80742
81061
  }
80743
81062
  if (errorInfo) {
80744
81063
  latestErrorText = errorInfo.message || latestErrorText;
80745
- emitLoopErrorDelta(socket, runtime, {
81064
+ emitLoopErrorNotice(socket, runtime, {
80746
81065
  message: errorInfo.message || "Stream error",
80747
81066
  stopReason: errorInfo.error_type || "error",
80748
81067
  isTerminal: false,
80749
81068
  runId: runId || errorInfo.run_id,
80750
81069
  agentId,
80751
- conversationId
81070
+ conversationId,
81071
+ errorInfo,
81072
+ cancelRequested: runtime.cancelRequested,
81073
+ abortSignal: turnAbortSignal
80752
81074
  });
80753
81075
  }
80754
81076
  if (shouldOutput) {
@@ -80808,7 +81130,8 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
80808
81130
  }
80809
81131
  if (stopReason !== "requires_approval") {
80810
81132
  const lastRunId = runId || msgRunIds[msgRunIds.length - 1] || null;
80811
- const errorDetail = latestErrorText || (lastRunId ? await fetchRunErrorDetail(lastRunId) : null);
81133
+ const runErrorInfo = lastRunId ? await fetchRunErrorInfo(lastRunId) : null;
81134
+ const errorDetail = latestErrorText || runErrorInfo?.detail || runErrorInfo?.message || null;
80812
81135
  if (shouldAttemptPostStopApprovalRecovery({
80813
81136
  stopReason,
80814
81137
  runIdsSeen: msgRunIds.length,
@@ -80817,7 +81140,8 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
80817
81140
  latestErrorText
80818
81141
  })) {
80819
81142
  postStopApprovalRecoveryRetries += 1;
80820
- emitStatusDelta(socket, runtime, {
81143
+ emitRecoverableStatusNotice(socket, runtime, {
81144
+ kind: "stale_approval_conflict_recovery",
80821
81145
  message: "Recovering from stale approval conflict after interrupted/reconnected turn",
80822
81146
  level: "warning",
80823
81147
  runId: lastRunId || undefined,
@@ -80913,7 +81237,8 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
80913
81237
  detail: errorDetail
80914
81238
  });
80915
81239
  const retryMessage = getRetryStatusMessage(errorDetail) || `LLM API error encountered, retrying (attempt ${attempt}/${LLM_API_ERROR_MAX_RETRIES})...`;
80916
- emitRetryDelta(socket, runtime, {
81240
+ emitRecoverableRetryNotice(socket, runtime, {
81241
+ kind: "transient_provider_retry",
80917
81242
  message: retryMessage,
80918
81243
  reason: "llm_api_error",
80919
81244
  attempt,
@@ -80968,13 +81293,16 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
80968
81293
  conversation_id: conversationId
80969
81294
  });
80970
81295
  const errorMessage = errorDetail || `Unexpected stop reason: ${stopReason}`;
80971
- emitLoopErrorDelta(socket, runtime, {
81296
+ emitLoopErrorNotice(socket, runtime, {
80972
81297
  message: errorMessage,
80973
81298
  stopReason: effectiveStopReason,
80974
81299
  isTerminal: true,
80975
81300
  runId,
80976
81301
  agentId,
80977
- conversationId
81302
+ conversationId,
81303
+ runErrorInfo: runErrorInfo ?? undefined,
81304
+ cancelRequested: runtime.cancelRequested,
81305
+ abortSignal: turnAbortSignal
80978
81306
  });
80979
81307
  break;
80980
81308
  }
@@ -81058,12 +81386,15 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
81058
81386
  conversation_id: conversationId
81059
81387
  });
81060
81388
  const errorMessage = error instanceof Error ? error.message : String(error);
81061
- emitLoopErrorDelta(socket, runtime, {
81389
+ emitLoopErrorNotice(socket, runtime, {
81062
81390
  message: errorMessage,
81063
81391
  stopReason: "error",
81064
81392
  isTerminal: true,
81065
81393
  agentId: agentId || undefined,
81066
- conversationId
81394
+ conversationId,
81395
+ error,
81396
+ cancelRequested: runtime.cancelRequested,
81397
+ abortSignal: turnAbortSignal
81067
81398
  });
81068
81399
  if (isDebugEnabled()) {
81069
81400
  console.error("[Listen] Error handling message:", error);
@@ -81113,6 +81444,7 @@ var init_turn = __esm(async () => {
81113
81444
  init_toolset(),
81114
81445
  init_interrupts(),
81115
81446
  init_protocol_outbound(),
81447
+ init_recoverable_notices(),
81116
81448
  init_recovery(),
81117
81449
  init_send(),
81118
81450
  init_turn_approval()
@@ -81436,7 +81768,7 @@ function buildLoopStatus(runtime, params) {
81436
81768
  const conversationRuntime = getConversationRuntime(listener, scopedAgentId, scopedConversationId);
81437
81769
  const interruptedCacheActive = hasInterruptedCacheForScope(listener, scope);
81438
81770
  const recovered = getRecoveredApprovalStateForScope(listener, scope);
81439
- const status = interruptedCacheActive ? !conversationRuntime?.isProcessing ? "WAITING_ON_INPUT" : conversationRuntime?.loopStatus ?? "WAITING_ON_INPUT" : recovered && recovered.pendingRequestIds.size > 0 && conversationRuntime?.loopStatus === "WAITING_ON_INPUT" ? "WAITING_ON_APPROVAL" : conversationRuntime?.loopStatus ?? "WAITING_ON_INPUT";
81771
+ const status = interruptedCacheActive ? !conversationRuntime?.isProcessing ? "WAITING_ON_INPUT" : conversationRuntime?.loopStatus === "WAITING_ON_APPROVAL" ? "WAITING_ON_INPUT" : conversationRuntime?.loopStatus ?? "WAITING_ON_INPUT" : recovered && recovered.pendingRequestIds.size > 0 && conversationRuntime?.loopStatus === "WAITING_ON_INPUT" ? "WAITING_ON_APPROVAL" : conversationRuntime?.loopStatus ?? "WAITING_ON_INPUT";
81440
81772
  return {
81441
81773
  status,
81442
81774
  active_run_ids: interruptedCacheActive && !conversationRuntime?.isProcessing ? [] : conversationRuntime?.activeRunId ? [conversationRuntime.activeRunId] : []
@@ -81598,9 +81930,6 @@ function resolveSubagentScopeForSnapshot(runtime, scope) {
81598
81930
  function buildSubagentSnapshot(runtime, scope) {
81599
81931
  const runtimeScope = resolveSubagentScopeForSnapshot(runtime, scope);
81600
81932
  return getSubagents().filter((a) => {
81601
- if (a.status !== "pending" && a.status !== "running") {
81602
- return false;
81603
- }
81604
81933
  if (a.silent && a.isBackground !== true) {
81605
81934
  return false;
81606
81935
  }
@@ -81672,7 +82001,8 @@ function emitLoopErrorDelta(socket, runtime, params) {
81672
82001
  ...createLifecycleMessageBase("loop_error", params.runId),
81673
82002
  message: params.message,
81674
82003
  stop_reason: params.stopReason,
81675
- is_terminal: params.isTerminal
82004
+ is_terminal: params.isTerminal,
82005
+ ...params.apiError ? { api_error: params.apiError } : {}
81676
82006
  }, {
81677
82007
  agent_id: params.agentId,
81678
82008
  conversation_id: params.conversationId
@@ -82915,12 +83245,13 @@ function handleModeChange(msg, socket, runtime, scope) {
82915
83245
  }
82916
83246
  } catch (error) {
82917
83247
  trackListenerError("listener_mode_change_failed", error, "listener_mode_change");
82918
- emitLoopErrorDelta(socket, runtime, {
83248
+ emitLoopErrorNotice(socket, runtime, {
82919
83249
  message: error instanceof Error ? error.message : "Mode change failed",
82920
83250
  stopReason: "error",
82921
83251
  isTerminal: false,
82922
83252
  agentId: scope?.agent_id,
82923
- conversationId: scope?.conversation_id
83253
+ conversationId: scope?.conversation_id,
83254
+ error
82924
83255
  });
82925
83256
  if (isDebugEnabled()) {
82926
83257
  console.error("[Listen] Mode change failed:", error);
@@ -83670,6 +84001,7 @@ async function handleAbortMessageInput(listener, params, deps = {}) {
83670
84001
  }
83671
84002
  const interruptedRunId = scopedRuntime.activeRunId;
83672
84003
  scopedRuntime.cancelRequested = true;
84004
+ const pendingRequestsSnapshot = hasPendingApprovals ? resolvedDeps.getPendingControlRequests(listener, scope) : [];
83673
84005
  if (scopedRuntime.activeExecutingToolCallIds.length > 0 && (!scopedRuntime.pendingInterruptedResults || scopedRuntime.pendingInterruptedResults.length === 0)) {
83674
84006
  scopedRuntime.pendingInterruptedResults = scopedRuntime.activeExecutingToolCallIds.map((toolCallId) => ({
83675
84007
  type: "tool",
@@ -83715,9 +84047,8 @@ async function handleAbortMessageInput(listener, params, deps = {}) {
83715
84047
  agentId: scope.agent_id,
83716
84048
  conversationId: scope.conversation_id
83717
84049
  });
83718
- } else if (hasPendingApprovals) {
83719
- const pendingRequests = resolvedDeps.getPendingControlRequests(listener, scope);
83720
- scopedRuntime.pendingInterruptedResults = pendingRequests.map((req) => ({
84050
+ } else if (hasPendingApprovals && (!scopedRuntime.pendingInterruptedResults || scopedRuntime.pendingInterruptedResults.length === 0) && pendingRequestsSnapshot.length > 0) {
84051
+ scopedRuntime.pendingInterruptedResults = pendingRequestsSnapshot.map((req) => ({
83721
84052
  type: "approval",
83722
84053
  tool_call_id: req.request.tool_call_id,
83723
84054
  approve: false,
@@ -83728,6 +84059,7 @@ async function handleAbortMessageInput(listener, params, deps = {}) {
83728
84059
  conversationId: scope.conversation_id,
83729
84060
  continuationEpoch: scopedRuntime.continuationEpoch
83730
84061
  };
84062
+ scopedRuntime.pendingInterruptedToolCallIds = null;
83731
84063
  resolvedDeps.emitInterruptedStatusDelta(params.socket, scopedRuntime, {
83732
84064
  runId: interruptedRunId,
83733
84065
  agentId: scope.agent_id,
@@ -83772,12 +84104,13 @@ async function handleCwdChange(msg, socket, runtime) {
83772
84104
  conversation_id: conversationId
83773
84105
  });
83774
84106
  } catch (error) {
83775
- emitLoopErrorDelta(socket, runtime, {
84107
+ emitLoopErrorNotice(socket, runtime, {
83776
84108
  message: error instanceof Error ? error.message : "Working directory change failed",
83777
84109
  stopReason: "error",
83778
84110
  isTerminal: false,
83779
84111
  agentId,
83780
- conversationId
84112
+ conversationId,
84113
+ error
83781
84114
  });
83782
84115
  }
83783
84116
  }
@@ -83996,7 +84329,7 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
83996
84329
  return;
83997
84330
  }
83998
84331
  if (parsed.type === "__invalid_input") {
83999
- emitLoopErrorDelta(socket, runtime, {
84332
+ emitLoopErrorNotice(socket, runtime, {
84000
84333
  message: parsed.reason,
84001
84334
  stopReason: "error",
84002
84335
  isTerminal: false,
@@ -84039,7 +84372,7 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
84039
84372
  }
84040
84373
  const inputPayload = parsed.payload;
84041
84374
  if (inputPayload.kind !== "create_message") {
84042
- emitLoopErrorDelta(socket, runtime, {
84375
+ emitLoopErrorNotice(socket, runtime, {
84043
84376
  message: `Unsupported input payload kind: ${String(inputPayload.kind)}`,
84044
84377
  stopReason: "error",
84045
84378
  isTerminal: false,
@@ -84056,7 +84389,7 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
84056
84389
  };
84057
84390
  const hasApprovalPayload = incoming.messages.some((payload) => ("type" in payload) && payload.type === "approval");
84058
84391
  if (hasApprovalPayload) {
84059
- emitLoopErrorDelta(socket, runtime, {
84392
+ emitLoopErrorNotice(socket, runtime, {
84060
84393
  message: "Protocol violation: approval payloads are not allowed in input.kind=create_message. Use input.kind=approval_response.",
84061
84394
  stopReason: "error",
84062
84395
  isTerminal: false,
@@ -84613,12 +84946,13 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
84613
84946
  if (!parsedScope) {
84614
84947
  return;
84615
84948
  }
84616
- emitLoopErrorDelta(socket, runtime, {
84949
+ emitLoopErrorNotice(socket, runtime, {
84617
84950
  message: error instanceof Error ? error.message : "Failed to process listener message",
84618
84951
  stopReason: "error",
84619
84952
  isTerminal: false,
84620
84953
  agentId: parsedScope.agent_id,
84621
- conversationId: parsedScope.conversation_id
84954
+ conversationId: parsedScope.conversation_id,
84955
+ error
84622
84956
  });
84623
84957
  }
84624
84958
  });
@@ -84916,6 +85250,7 @@ var init_client4 = __esm(async () => {
84916
85250
  init_protocol_inbound(),
84917
85251
  init_protocol_outbound(),
84918
85252
  init_queue(),
85253
+ init_recoverable_notices(),
84919
85254
  init_recovery(),
84920
85255
  init_send(),
84921
85256
  init_turn(),
@@ -85336,7 +85671,7 @@ var init_skills2 = __esm(() => {
85336
85671
  var exports_fs = {};
85337
85672
  __export(exports_fs, {
85338
85673
  writeJsonFile: () => writeJsonFile,
85339
- writeFile: () => writeFile6,
85674
+ writeFile: () => writeFile7,
85340
85675
  readJsonFile: () => readJsonFile,
85341
85676
  readFile: () => readFile9,
85342
85677
  mkdir: () => mkdir6,
@@ -85352,7 +85687,7 @@ import { dirname as dirname13 } from "node:path";
85352
85687
  async function readFile9(path23) {
85353
85688
  return fsReadFileSync2(path23, { encoding: "utf-8" });
85354
85689
  }
85355
- async function writeFile6(path23, content) {
85690
+ async function writeFile7(path23, content) {
85356
85691
  const dir = dirname13(path23);
85357
85692
  if (!existsSync25(dir)) {
85358
85693
  mkdirSync17(dir, { recursive: true });
@@ -85373,7 +85708,7 @@ async function writeJsonFile(path23, data, options) {
85373
85708
  const indent = options?.indent ?? 2;
85374
85709
  const content = `${JSON.stringify(data, null, indent)}
85375
85710
  `;
85376
- await writeFile6(path23, content);
85711
+ await writeFile7(path23, content);
85377
85712
  }
85378
85713
  var init_fs2 = () => {};
85379
85714
 
@@ -85398,7 +85733,7 @@ async function queryTerminalBackground(timeoutMs = 100) {
85398
85733
  }
85399
85734
  const wasRaw = process.stdin.isRaw;
85400
85735
  const wasFlowing = process.stdin.readableFlowing;
85401
- return new Promise((resolve25) => {
85736
+ return new Promise((resolve26) => {
85402
85737
  let response = "";
85403
85738
  let resolved = false;
85404
85739
  const cleanup = () => {
@@ -85415,7 +85750,7 @@ async function queryTerminalBackground(timeoutMs = 100) {
85415
85750
  };
85416
85751
  const timeout = setTimeout(() => {
85417
85752
  cleanup();
85418
- resolve25(null);
85753
+ resolve26(null);
85419
85754
  }, timeoutMs);
85420
85755
  const onData = (data) => {
85421
85756
  response += data.toString();
@@ -85425,7 +85760,7 @@ async function queryTerminalBackground(timeoutMs = 100) {
85425
85760
  if (match3) {
85426
85761
  clearTimeout(timeout);
85427
85762
  cleanup();
85428
- resolve25({
85763
+ resolve26({
85429
85764
  r: parseHexComponent(match3[1] ?? "0"),
85430
85765
  g: parseHexComponent(match3[2] ?? "0"),
85431
85766
  b: parseHexComponent(match3[3] ?? "0")
@@ -85440,7 +85775,7 @@ async function queryTerminalBackground(timeoutMs = 100) {
85440
85775
  } catch {
85441
85776
  clearTimeout(timeout);
85442
85777
  cleanup();
85443
- resolve25(null);
85778
+ resolve26(null);
85444
85779
  }
85445
85780
  });
85446
85781
  }
@@ -86649,10 +86984,10 @@ __export(exports_setup, {
86649
86984
  runSetup: () => runSetup
86650
86985
  });
86651
86986
  async function runSetup() {
86652
- return new Promise((resolve26) => {
86987
+ return new Promise((resolve27) => {
86653
86988
  const { waitUntilExit } = render_default(import_react32.default.createElement(SetupUI, {
86654
86989
  onComplete: () => {
86655
- resolve26();
86990
+ resolve27();
86656
86991
  }
86657
86992
  }));
86658
86993
  waitUntilExit().catch((error) => {
@@ -87127,6 +87462,20 @@ function validateRegistryHandleOrThrow2(handle) {
87127
87462
  }
87128
87463
  }
87129
87464
 
87465
+ // src/runtime-context.ts
87466
+ import { AsyncLocalStorage } from "node:async_hooks";
87467
+ function getCurrentWorkingDirectory() {
87468
+ const workingDirectory = runtimeContextStorage.getStore()?.workingDirectory;
87469
+ if (typeof workingDirectory === "string" && workingDirectory.length > 0) {
87470
+ return workingDirectory;
87471
+ }
87472
+ return process.env.USER_CWD || process.cwd();
87473
+ }
87474
+ var runtimeContextStorage;
87475
+ var init_runtime_context = __esm(() => {
87476
+ runtimeContextStorage = new AsyncLocalStorage;
87477
+ });
87478
+
87130
87479
  // src/agent/github-utils.ts
87131
87480
  var exports_github_utils = {};
87132
87481
  __export(exports_github_utils, {
@@ -87167,11 +87516,11 @@ __export(exports_import, {
87167
87516
  extractSkillsFromAf: () => extractSkillsFromAf
87168
87517
  });
87169
87518
  import { createReadStream } from "node:fs";
87170
- import { chmod, mkdir as mkdir7, readFile as readFile10, writeFile as writeFile7 } from "node:fs/promises";
87171
- import { dirname as dirname14, resolve as resolve26 } from "node:path";
87519
+ import { chmod, mkdir as mkdir7, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
87520
+ import { dirname as dirname14, resolve as resolve27 } from "node:path";
87172
87521
  async function importAgentFromFile(options) {
87173
87522
  const client = await getClient();
87174
- const resolvedPath = resolve26(options.filePath);
87523
+ const resolvedPath = resolve27(options.filePath);
87175
87524
  const file = createReadStream(resolvedPath);
87176
87525
  const importResponse = await client.agents.importFile({
87177
87526
  file,
@@ -87206,7 +87555,7 @@ async function extractSkillsFromAf(afPath, destDir) {
87206
87555
  return [];
87207
87556
  }
87208
87557
  for (const skill2 of afData.skills) {
87209
- const skillDir = resolve26(destDir, skill2.name);
87558
+ const skillDir = resolve27(destDir, skill2.name);
87210
87559
  await mkdir7(skillDir, { recursive: true });
87211
87560
  if (skill2.files) {
87212
87561
  await writeSkillFiles(skillDir, skill2.files);
@@ -87226,9 +87575,9 @@ async function writeSkillFiles(skillDir, files) {
87226
87575
  }
87227
87576
  }
87228
87577
  async function writeSkillFile(skillDir, filePath, content) {
87229
- const fullPath = resolve26(skillDir, filePath);
87578
+ const fullPath = resolve27(skillDir, filePath);
87230
87579
  await mkdir7(dirname14(fullPath), { recursive: true });
87231
- await writeFile7(fullPath, content, "utf-8");
87580
+ await writeFile8(fullPath, content, "utf-8");
87232
87581
  const isScript = filePath.startsWith("scripts/") || content.trimStart().startsWith("#!");
87233
87582
  if (isScript) {
87234
87583
  try {
@@ -87281,7 +87630,7 @@ function parseRegistryHandle(handle) {
87281
87630
  async function importAgentFromRegistry(options) {
87282
87631
  const { tmpdir: tmpdir4 } = await import("node:os");
87283
87632
  const { join: join35 } = await import("node:path");
87284
- const { writeFile: writeFile8, unlink: unlink3 } = await import("node:fs/promises");
87633
+ const { writeFile: writeFile9, unlink: unlink3 } = await import("node:fs/promises");
87285
87634
  const { author, name } = parseRegistryHandle(options.handle);
87286
87635
  const rawUrl = `https://raw.githubusercontent.com/${AGENT_REGISTRY_OWNER}/${AGENT_REGISTRY_REPO}/refs/heads/${AGENT_REGISTRY_BRANCH}/agents/@${author}/${name}/${name}.af`;
87287
87636
  const response = await fetch(rawUrl);
@@ -87293,7 +87642,7 @@ async function importAgentFromRegistry(options) {
87293
87642
  }
87294
87643
  const afContent = await response.text();
87295
87644
  const tempPath = join35(tmpdir4(), `letta-import-${author}-${name}-${Date.now()}.af`);
87296
- await writeFile8(tempPath, afContent, "utf-8");
87645
+ await writeFile9(tempPath, afContent, "utf-8");
87297
87646
  try {
87298
87647
  const result = await importAgentFromFile({
87299
87648
  filePath: tempPath,
@@ -87524,7 +87873,7 @@ async function prepareHeadlessToolExecutionContext(params) {
87524
87873
  agentId: params.agentId,
87525
87874
  conversationId: params.conversationId,
87526
87875
  overrideModel: params.overrideModel,
87527
- workingDirectory: process.env.USER_CWD || process.cwd(),
87876
+ workingDirectory: getCurrentWorkingDirectory(),
87528
87877
  exclude: ["AskUserQuestion"]
87529
87878
  });
87530
87879
  return {
@@ -87532,6 +87881,20 @@ async function prepareHeadlessToolExecutionContext(params) {
87532
87881
  availableTools: preparedToolContext.preparedToolContext.clientTools.map((tool) => tool.name)
87533
87882
  };
87534
87883
  }
87884
+ async function flushAndExit(code) {
87885
+ const flushWritable = (stream2) => new Promise((resolve28) => {
87886
+ if (stream2.destroyed || stream2.writableEnded) {
87887
+ resolve28();
87888
+ return;
87889
+ }
87890
+ stream2.write("", () => resolve28());
87891
+ });
87892
+ await Promise.allSettled([
87893
+ flushWritable(process.stdout),
87894
+ flushWritable(process.stderr)
87895
+ ]);
87896
+ process.exit(code);
87897
+ }
87535
87898
  async function handleHeadlessCommand(parsedArgs, model, skillsDirectoryOverride, skillSourcesOverride, systemInfoReminderEnabledOverride) {
87536
87899
  const { values, positionals } = parsedArgs;
87537
87900
  telemetry.setSurface("headless");
@@ -87602,6 +87965,7 @@ In headless mode, use:
87602
87965
  let forceNewConversation = values.new ?? false;
87603
87966
  const fromAgentId = values["from-agent"];
87604
87967
  let agent = null;
87968
+ let autoEnableMemfsForFreshAgent = false;
87605
87969
  let specifiedAgentId = values.agent;
87606
87970
  const specifiedAgentName = values.name;
87607
87971
  let specifiedConversationId = values.conversation;
@@ -87884,7 +88248,7 @@ In headless mode, use:
87884
88248
  }
87885
88249
  if (!agent && forceNew) {
87886
88250
  const updateArgs = getModelUpdateArgs2(model);
87887
- const { isLettaCloud: isLettaCloud2, enableMemfsIfCloud: enableMemfsIfCloud2 } = await Promise.resolve().then(() => (init_memoryFilesystem(), exports_memoryFilesystem));
88251
+ const { isLettaCloud: isLettaCloud2 } = await Promise.resolve().then(() => (init_memoryFilesystem(), exports_memoryFilesystem));
87888
88252
  const willAutoEnableMemfs = shouldAutoEnableMemfsForNewAgent && await isLettaCloud2();
87889
88253
  const effectiveMemoryMode = requestedMemoryPromptMode ?? (willAutoEnableMemfs ? "memfs" : undefined);
87890
88254
  const createOptions = {
@@ -87904,13 +88268,11 @@ In headless mode, use:
87904
88268
  };
87905
88269
  const result = await createAgent(createOptions);
87906
88270
  agent = result.agent;
87907
- if (willAutoEnableMemfs) {
87908
- await enableMemfsIfCloud2(agent.id);
87909
- }
88271
+ autoEnableMemfsForFreshAgent = willAutoEnableMemfs;
87910
88272
  }
87911
88273
  if (!agent) {
87912
88274
  await settingsManager.loadLocalProjectSettings();
87913
- const localAgentId = settingsManager.getLocalLastAgentId(process.cwd());
88275
+ const localAgentId = settingsManager.getLocalLastAgentId(getCurrentWorkingDirectory());
87914
88276
  if (localAgentId) {
87915
88277
  try {
87916
88278
  agent = await client.agents.retrieve(localAgentId);
@@ -87965,13 +88327,14 @@ In headless mode, use:
87965
88327
  let conversationId;
87966
88328
  let effectiveReflectionSettings;
87967
88329
  const isSubagent = process.env.LETTA_CODE_AGENT_ROLE === "subagent";
88330
+ const startupMemfsFlag = autoEnableMemfsForFreshAgent ? true : memfsFlag;
87968
88331
  let memfsBgPromise;
87969
88332
  const secretsAgentId = agent?.id;
87970
88333
  const secretsInitPromise = secretsAgentId ? init_secretsStore().then(() => exports_secretsStore).then(({ initSecretsFromServer: initSecretsFromServer2 }) => initSecretsFromServer2(secretsAgentId)) : Promise.resolve();
87971
88334
  if (memfsStartupPolicy === "skip") {
87972
88335
  try {
87973
88336
  const { applyMemfsFlags: applyMemfsFlags2 } = await Promise.resolve().then(() => (init_memoryFilesystem(), exports_memoryFilesystem));
87974
- await applyMemfsFlags2(agent.id, memfsFlag, noMemfsFlag, {
88337
+ await applyMemfsFlags2(agent.id, startupMemfsFlag, noMemfsFlag, {
87975
88338
  pullOnExistingRepo: false,
87976
88339
  agentTags: agent.tags,
87977
88340
  skipPromptUpdate: forceNew
@@ -87983,7 +88346,7 @@ In headless mode, use:
87983
88346
  }
87984
88347
  } else if (memfsStartupPolicy === "background") {
87985
88348
  const { applyMemfsFlags: applyMemfsFlags2 } = await Promise.resolve().then(() => (init_memoryFilesystem(), exports_memoryFilesystem));
87986
- memfsBgPromise = applyMemfsFlags2(agent.id, memfsFlag, noMemfsFlag, {
88349
+ memfsBgPromise = applyMemfsFlags2(agent.id, startupMemfsFlag, noMemfsFlag, {
87987
88350
  pullOnExistingRepo: true,
87988
88351
  agentTags: agent.tags,
87989
88352
  skipPromptUpdate: forceNew
@@ -87994,7 +88357,7 @@ In headless mode, use:
87994
88357
  } else {
87995
88358
  try {
87996
88359
  const { applyMemfsFlags: applyMemfsFlags2 } = await Promise.resolve().then(() => (init_memoryFilesystem(), exports_memoryFilesystem));
87997
- const memfsResult = await applyMemfsFlags2(agent.id, memfsFlag, noMemfsFlag, {
88360
+ const memfsResult = await applyMemfsFlags2(agent.id, startupMemfsFlag, noMemfsFlag, {
87998
88361
  pullOnExistingRepo: true,
87999
88362
  agentTags: agent.tags,
88000
88363
  skipPromptUpdate: forceNew
@@ -88097,7 +88460,7 @@ In headless mode, use:
88097
88460
  }
88098
88461
  markMilestone("HEADLESS_CONVERSATION_READY");
88099
88462
  setConversationId2(conversationId);
88100
- if (!isSubagent) {
88463
+ if (shouldPersistSessionState()) {
88101
88464
  await settingsManager.loadLocalProjectSettings();
88102
88465
  settingsManager.persistSession(agent.id, conversationId);
88103
88466
  }
@@ -88136,7 +88499,7 @@ In headless mode, use:
88136
88499
  conversation_id: conversationId,
88137
88500
  model: agent.llm_config?.model ?? "",
88138
88501
  tools: availableTools,
88139
- cwd: process.cwd(),
88502
+ cwd: getCurrentWorkingDirectory(),
88140
88503
  mcp_servers: [],
88141
88504
  permission_mode: "",
88142
88505
  slash_commands: [],
@@ -88297,6 +88660,7 @@ ${SYSTEM_REMINDER_CLOSE}
88297
88660
  },
88298
88661
  state: sharedReminderState,
88299
88662
  sessionContextReminderEnabled: systemInfoReminderEnabled,
88663
+ workingDirectory: getCurrentWorkingDirectory(),
88300
88664
  reflectionSettings: effectiveReflectionSettings,
88301
88665
  skillSources: resolvedSkillSources,
88302
88666
  resolvePlanModeReminder: async () => {
@@ -88470,7 +88834,7 @@ ${loadedContents.join(`
88470
88834
  } else {
88471
88835
  console.error(`Conversation is busy, waiting ${Math.round(retryDelayMs / 1000)}s and retrying...`);
88472
88836
  }
88473
- await new Promise((resolve27) => setTimeout(resolve27, retryDelayMs));
88837
+ await new Promise((resolve28) => setTimeout(resolve28, retryDelayMs));
88474
88838
  continue;
88475
88839
  }
88476
88840
  }
@@ -88519,7 +88883,7 @@ ${loadedContents.join(`
88519
88883
  const delaySeconds = Math.round(delayMs / 1000);
88520
88884
  console.error(`Transient API error before streaming (attempt ${attempt} of ${LLM_API_ERROR_MAX_RETRIES2}), retrying in ${delaySeconds}s...`);
88521
88885
  }
88522
- await new Promise((resolve27) => setTimeout(resolve27, delayMs));
88886
+ await new Promise((resolve28) => setTimeout(resolve28, delayMs));
88523
88887
  conversationBusyRetries = 0;
88524
88888
  continue;
88525
88889
  }
@@ -88758,7 +89122,7 @@ ${loadedContents.join(`
88758
89122
  const delaySeconds = Math.round(delayMs / 1000);
88759
89123
  console.error(`LLM API error encountered (attempt ${attempt} of ${LLM_API_ERROR_MAX_RETRIES2}), retrying in ${delaySeconds}s...`);
88760
89124
  }
88761
- await new Promise((resolve27) => setTimeout(resolve27, delayMs));
89125
+ await new Promise((resolve28) => setTimeout(resolve28, delayMs));
88762
89126
  refreshCurrentInputOtids();
88763
89127
  continue;
88764
89128
  }
@@ -88844,7 +89208,7 @@ ${loadedContents.join(`
88844
89208
  } else {
88845
89209
  console.error(`Empty LLM response, retrying (attempt ${attempt} of ${EMPTY_RESPONSE_MAX_RETRIES2})...`);
88846
89210
  }
88847
- await new Promise((resolve27) => setTimeout(resolve27, delayMs));
89211
+ await new Promise((resolve28) => setTimeout(resolve28, delayMs));
88848
89212
  refreshCurrentInputOtids();
88849
89213
  continue;
88850
89214
  }
@@ -88872,7 +89236,7 @@ ${loadedContents.join(`
88872
89236
  const delaySeconds = Math.round(delayMs / 1000);
88873
89237
  console.error(`LLM API error encountered (attempt ${attempt} of ${LLM_API_ERROR_MAX_RETRIES2}), retrying in ${delaySeconds}s...`);
88874
89238
  }
88875
- await new Promise((resolve27) => setTimeout(resolve27, delayMs));
89239
+ await new Promise((resolve28) => setTimeout(resolve28, delayMs));
88876
89240
  refreshCurrentInputOtids();
88877
89241
  continue;
88878
89242
  }
@@ -89001,6 +89365,7 @@ ${loadedContents.join(`
89001
89365
  }
89002
89366
  markMilestone("HEADLESS_COMPLETE");
89003
89367
  reportAllMilestones();
89368
+ await flushAndExit(0);
89004
89369
  }
89005
89370
  async function runBidirectionalMode(agent, conversationId, client, _outputFormat, includePartialMessages, availableTools, skillSources, systemInfoReminderEnabled, reflectionSettings) {
89006
89371
  const sessionId = agent.id;
@@ -89013,7 +89378,7 @@ async function runBidirectionalMode(agent, conversationId, client, _outputFormat
89013
89378
  conversation_id: conversationId,
89014
89379
  model: agent.llm_config?.model,
89015
89380
  tools: availableTools,
89016
- cwd: process.cwd(),
89381
+ cwd: getCurrentWorkingDirectory(),
89017
89382
  memfs_enabled: settingsManager.isMemfsEnabled(agent.id),
89018
89383
  skill_sources: skillSources,
89019
89384
  system_info_reminder_enabled: systemInfoReminderEnabled,
@@ -89205,9 +89570,9 @@ async function runBidirectionalMode(agent, conversationId, client, _outputFormat
89205
89570
  const syntheticUserLine = serializeQueuedMessageAsUserLine(queuedMessage);
89206
89571
  maybeNotifyBlocked(syntheticUserLine);
89207
89572
  if (lineResolver) {
89208
- const resolve27 = lineResolver;
89573
+ const resolve28 = lineResolver;
89209
89574
  lineResolver = null;
89210
- resolve27(syntheticUserLine);
89575
+ resolve28(syntheticUserLine);
89211
89576
  return;
89212
89577
  }
89213
89578
  lineQueue.push(syntheticUserLine);
@@ -89215,9 +89580,9 @@ async function runBidirectionalMode(agent, conversationId, client, _outputFormat
89215
89580
  rl.on("line", (line) => {
89216
89581
  maybeNotifyBlocked(line);
89217
89582
  if (lineResolver) {
89218
- const resolve27 = lineResolver;
89583
+ const resolve28 = lineResolver;
89219
89584
  lineResolver = null;
89220
- resolve27(line);
89585
+ resolve28(line);
89221
89586
  } else {
89222
89587
  lineQueue.push(line);
89223
89588
  }
@@ -89226,17 +89591,17 @@ async function runBidirectionalMode(agent, conversationId, client, _outputFormat
89226
89591
  setMessageQueueAdder(null);
89227
89592
  msgQueueRuntime.clear("shutdown");
89228
89593
  if (lineResolver) {
89229
- const resolve27 = lineResolver;
89594
+ const resolve28 = lineResolver;
89230
89595
  lineResolver = null;
89231
- resolve27(null);
89596
+ resolve28(null);
89232
89597
  }
89233
89598
  });
89234
89599
  async function getNextLine() {
89235
89600
  if (lineQueue.length > 0) {
89236
89601
  return lineQueue.shift() ?? null;
89237
89602
  }
89238
- return new Promise((resolve27) => {
89239
- lineResolver = resolve27;
89603
+ return new Promise((resolve28) => {
89604
+ lineResolver = resolve28;
89240
89605
  });
89241
89606
  }
89242
89607
  async function requestPermission(toolCallId, toolName, toolInput) {
@@ -89655,6 +90020,7 @@ async function runBidirectionalMode(agent, conversationId, client, _outputFormat
89655
90020
  },
89656
90021
  state: sharedReminderState,
89657
90022
  sessionContextReminderEnabled: systemInfoReminderEnabled,
90023
+ workingDirectory: getCurrentWorkingDirectory(),
89658
90024
  reflectionSettings,
89659
90025
  skillSources,
89660
90026
  resolvePlanModeReminder: async () => {
@@ -89739,7 +90105,7 @@ async function runBidirectionalMode(agent, conversationId, client, _outputFormat
89739
90105
  uuid: `retry-bidir-${randomUUID8()}`
89740
90106
  };
89741
90107
  console.log(JSON.stringify(retryMsg));
89742
- await new Promise((resolve27) => setTimeout(resolve27, delayMs));
90108
+ await new Promise((resolve28) => setTimeout(resolve28, delayMs));
89743
90109
  continue;
89744
90110
  }
89745
90111
  throw preStreamError;
@@ -89980,6 +90346,7 @@ var init_headless = __esm(async () => {
89980
90346
  init_constants();
89981
90347
  init_diffPreview();
89982
90348
  init_queueRuntime();
90349
+ init_runtime_context();
89983
90350
  init_interactivePolicy();
89984
90351
  init_debug();
89985
90352
  init_timing();
@@ -90034,10 +90401,10 @@ async function detectAndEnableKittyProtocol() {
90034
90401
  detectionComplete = true;
90035
90402
  return;
90036
90403
  }
90037
- return new Promise((resolve27) => {
90404
+ return new Promise((resolve28) => {
90038
90405
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
90039
90406
  detectionComplete = true;
90040
- resolve27();
90407
+ resolve28();
90041
90408
  return;
90042
90409
  }
90043
90410
  const originalRawMode = process.stdin.isRaw;
@@ -90070,7 +90437,7 @@ async function detectAndEnableKittyProtocol() {
90070
90437
  console.error("[kitty] protocol query unsupported; enabled anyway (best-effort)");
90071
90438
  }
90072
90439
  detectionComplete = true;
90073
- resolve27();
90440
+ resolve28();
90074
90441
  };
90075
90442
  const handleData = (data) => {
90076
90443
  if (timeoutId === undefined) {
@@ -112256,7 +112623,7 @@ var init_pasteRegistry = __esm(() => {
112256
112623
  import { execFileSync as execFileSync3 } from "node:child_process";
112257
112624
  import { existsSync as existsSync29, readFileSync as readFileSync16, statSync as statSync10, unlinkSync as unlinkSync10 } from "node:fs";
112258
112625
  import { tmpdir as tmpdir4 } from "node:os";
112259
- import { basename as basename4, extname as extname5, isAbsolute as isAbsolute18, join as join38, resolve as resolve27 } from "node:path";
112626
+ import { basename as basename4, extname as extname5, isAbsolute as isAbsolute18, join as join38, resolve as resolve28 } from "node:path";
112260
112627
  function countLines2(text) {
112261
112628
  return (text.match(/\r\n|\r|\n/g) || []).length + 1;
112262
112629
  }
@@ -112304,7 +112671,7 @@ function translatePasteForImages(paste) {
112304
112671
  } catch {}
112305
112672
  }
112306
112673
  if (!isAbsolute18(filePath))
112307
- filePath = resolve27(process.cwd(), filePath);
112674
+ filePath = resolve28(process.cwd(), filePath);
112308
112675
  const ext3 = extname5(filePath || "").toLowerCase();
112309
112676
  if (IMAGE_EXTS.has(ext3) && existsSync29(filePath) && statSync10(filePath).isFile()) {
112310
112677
  const buf = readFileSync16(filePath);
@@ -113450,9 +113817,16 @@ var init_registry = __esm(async () => {
113450
113817
  }
113451
113818
  },
113452
113819
  "/reflect": {
113453
- desc: "Launch a background reflection agent to update memory",
113820
+ desc: "Launch reflection (/reflect [transcript_file])",
113821
+ args: "[transcript_file]",
113454
113822
  order: 50,
113455
- noArgs: true,
113823
+ handler: () => {
113824
+ return "Launching reflection agent...";
113825
+ }
113826
+ },
113827
+ "/reflection": {
113828
+ desc: "Alias for /reflect",
113829
+ args: "[transcript_file]",
113456
113830
  handler: () => {
113457
113831
  return "Launching reflection agent...";
113458
113832
  }
@@ -113812,14 +114186,6 @@ Location: ${keybindingsPath}`;
113812
114186
  return "Clearing credentials...";
113813
114187
  }
113814
114188
  },
113815
- "/empanada": {
113816
- desc: "Order empanadas from Empanada Empire",
113817
- order: 44.5,
113818
- args: "[address]",
113819
- handler: () => {
113820
- return "Checking Empanada Empire...";
113821
- }
113822
- },
113823
114189
  "/ralph": {
113824
114190
  desc: 'Start Ralph Wiggum loop (/ralph [prompt] [--completion-promise "X"] [--max-iterations N])',
113825
114191
  order: 45,
@@ -114324,11 +114690,11 @@ var init_HelpDialog = __esm(async () => {
114324
114690
 
114325
114691
  // src/hooks/writer.ts
114326
114692
  import { homedir as homedir31 } from "node:os";
114327
- import { resolve as resolve28 } from "node:path";
114693
+ import { resolve as resolve29 } from "node:path";
114328
114694
  function isProjectSettingsPathCollidingWithGlobal2(workingDirectory) {
114329
114695
  const home = process.env.HOME || homedir31();
114330
- const globalSettingsPath = resolve28(home, ".letta", "settings.json");
114331
- const projectSettingsPath = resolve28(workingDirectory, ".letta", "settings.json");
114696
+ const globalSettingsPath = resolve29(home, ".letta", "settings.json");
114697
+ const projectSettingsPath = resolve29(workingDirectory, ".letta", "settings.json");
114332
114698
  return globalSettingsPath === projectSettingsPath;
114333
114699
  }
114334
114700
  function loadHooksFromLocation(location, workingDirectory = process.cwd()) {
@@ -116966,7 +117332,7 @@ var init_AgentInfoBar = __esm(async () => {
116966
117332
 
116967
117333
  // src/cli/helpers/fileSearch.ts
116968
117334
  import { readdirSync as readdirSync13, statSync as statSync11 } from "node:fs";
116969
- import { join as join41, relative as relative15, resolve as resolve29 } from "node:path";
117335
+ import { join as join41, relative as relative15, resolve as resolve30 } from "node:path";
116970
117336
  function searchDirectoryRecursive(dir, pattern, maxResults = 200, results = [], depth = 0, maxDepth = 10, lowerPattern = pattern.toLowerCase()) {
116971
117337
  if (results.length >= maxResults || depth >= maxDepth) {
116972
117338
  return results;
@@ -117009,7 +117375,7 @@ async function searchFiles(query, deep = false) {
117009
117375
  const dirPart = query.slice(0, lastSlashIndex);
117010
117376
  const pattern = query.slice(lastSlashIndex + 1);
117011
117377
  try {
117012
- const resolvedDir = resolve29(getIndexRoot(), dirPart);
117378
+ const resolvedDir = resolve30(getIndexRoot(), dirPart);
117013
117379
  try {
117014
117380
  statSync11(resolvedDir);
117015
117381
  searchDir = resolvedDir;
@@ -131808,7 +132174,7 @@ async function executeStatusLineCommand(command, payload, options) {
131808
132174
  };
131809
132175
  }
131810
132176
  function runWithLauncher(launcher, inputJson, timeout, signal, workingDirectory, startTime) {
131811
- return new Promise((resolve30, reject) => {
132177
+ return new Promise((resolve31, reject) => {
131812
132178
  const [executable, ...args] = launcher;
131813
132179
  if (!executable) {
131814
132180
  reject(new Error("Empty launcher"));
@@ -131821,7 +132187,7 @@ function runWithLauncher(launcher, inputJson, timeout, signal, workingDirectory,
131821
132187
  const safeResolve = (result) => {
131822
132188
  if (!resolved) {
131823
132189
  resolved = true;
131824
- resolve30(result);
132190
+ resolve31(result);
131825
132191
  }
131826
132192
  };
131827
132193
  let child;
@@ -132998,14 +133364,14 @@ __export(exports_export, {
132998
133364
  packageSkills: () => packageSkills
132999
133365
  });
133000
133366
  import { readdir as readdir10, readFile as readFile13 } from "node:fs/promises";
133001
- import { relative as relative16, resolve as resolve30 } from "node:path";
133367
+ import { relative as relative16, resolve as resolve31 } from "node:path";
133002
133368
  async function packageSkills(agentId, skillsDir) {
133003
133369
  const skills = [];
133004
133370
  const skillNames = new Set;
133005
133371
  const dirsToCheck = skillsDir ? [skillsDir] : [
133006
133372
  agentId && getAgentSkillsDir(agentId),
133007
- resolve30(process.cwd(), ".skills"),
133008
- resolve30(process.env.HOME || "~", ".letta", "skills")
133373
+ resolve31(process.cwd(), ".skills"),
133374
+ resolve31(process.env.HOME || "~", ".letta", "skills")
133009
133375
  ].filter((dir) => Boolean(dir));
133010
133376
  for (const baseDir of dirsToCheck) {
133011
133377
  try {
@@ -133015,8 +133381,8 @@ async function packageSkills(agentId, skillsDir) {
133015
133381
  continue;
133016
133382
  if (skillNames.has(entry.name))
133017
133383
  continue;
133018
- const skillDir = resolve30(baseDir, entry.name);
133019
- const skillMdPath = resolve30(skillDir, "SKILL.md");
133384
+ const skillDir = resolve31(baseDir, entry.name);
133385
+ const skillMdPath = resolve31(skillDir, "SKILL.md");
133020
133386
  try {
133021
133387
  await readFile13(skillMdPath, "utf-8");
133022
133388
  } catch {
@@ -133046,7 +133412,7 @@ async function readSkillFiles(skillDir) {
133046
133412
  async function walk(dir) {
133047
133413
  const entries = await readdir10(dir, { withFileTypes: true });
133048
133414
  for (const entry of entries) {
133049
- const fullPath = resolve30(dir, entry.name);
133415
+ const fullPath = resolve31(dir, entry.name);
133050
133416
  if (entry.isDirectory()) {
133051
133417
  await walk(fullPath);
133052
133418
  } else {
@@ -135530,7 +135896,7 @@ ${newState.originalPrompt}`,
135530
135896
  cancelled = true;
135531
135897
  break;
135532
135898
  }
135533
- await new Promise((resolve31) => setTimeout(resolve31, 100));
135899
+ await new Promise((resolve32) => setTimeout(resolve32, 100));
135534
135900
  }
135535
135901
  buffersRef.current.byId.delete(statusId);
135536
135902
  buffersRef.current.order = buffersRef.current.order.filter((id) => id !== statusId);
@@ -135594,7 +135960,7 @@ ${newState.originalPrompt}`,
135594
135960
  cancelled = true;
135595
135961
  break;
135596
135962
  }
135597
- await new Promise((resolve31) => setTimeout(resolve31, 100));
135963
+ await new Promise((resolve32) => setTimeout(resolve32, 100));
135598
135964
  }
135599
135965
  if (retryStatusId) {
135600
135966
  buffersRef.current.byId.delete(retryStatusId);
@@ -136352,7 +136718,7 @@ ${feedback}
136352
136718
  });
136353
136719
  buffersRef.current.order.push(statusId);
136354
136720
  refreshDerived();
136355
- await new Promise((resolve31) => setTimeout(resolve31, delayMs));
136721
+ await new Promise((resolve32) => setTimeout(resolve32, delayMs));
136356
136722
  buffersRef.current.byId.delete(statusId);
136357
136723
  buffersRef.current.order = buffersRef.current.order.filter((id) => id !== statusId);
136358
136724
  refreshDerived();
@@ -136412,7 +136778,7 @@ ${feedback}
136412
136778
  cancelled = true;
136413
136779
  break;
136414
136780
  }
136415
- await new Promise((resolve31) => setTimeout(resolve31, 100));
136781
+ await new Promise((resolve32) => setTimeout(resolve32, 100));
136416
136782
  }
136417
136783
  if (retryStatusId) {
136418
136784
  buffersRef.current.byId.delete(retryStatusId);
@@ -143626,11 +143992,11 @@ __export(exports_import2, {
143626
143992
  extractSkillsFromAf: () => extractSkillsFromAf2
143627
143993
  });
143628
143994
  import { createReadStream as createReadStream2 } from "node:fs";
143629
- import { chmod as chmod2, mkdir as mkdir8, readFile as readFile14, writeFile as writeFile8 } from "node:fs/promises";
143630
- import { dirname as dirname19, resolve as resolve31 } from "node:path";
143995
+ import { chmod as chmod2, mkdir as mkdir8, readFile as readFile14, writeFile as writeFile9 } from "node:fs/promises";
143996
+ import { dirname as dirname19, resolve as resolve32 } from "node:path";
143631
143997
  async function importAgentFromFile2(options) {
143632
143998
  const client = await getClient();
143633
- const resolvedPath = resolve31(options.filePath);
143999
+ const resolvedPath = resolve32(options.filePath);
143634
144000
  const file = createReadStream2(resolvedPath);
143635
144001
  const importResponse = await client.agents.importFile({
143636
144002
  file,
@@ -143665,7 +144031,7 @@ async function extractSkillsFromAf2(afPath, destDir) {
143665
144031
  return [];
143666
144032
  }
143667
144033
  for (const skill2 of afData.skills) {
143668
- const skillDir = resolve31(destDir, skill2.name);
144034
+ const skillDir = resolve32(destDir, skill2.name);
143669
144035
  await mkdir8(skillDir, { recursive: true });
143670
144036
  if (skill2.files) {
143671
144037
  await writeSkillFiles2(skillDir, skill2.files);
@@ -143685,9 +144051,9 @@ async function writeSkillFiles2(skillDir, files) {
143685
144051
  }
143686
144052
  }
143687
144053
  async function writeSkillFile2(skillDir, filePath, content) {
143688
- const fullPath = resolve31(skillDir, filePath);
144054
+ const fullPath = resolve32(skillDir, filePath);
143689
144055
  await mkdir8(dirname19(fullPath), { recursive: true });
143690
- await writeFile8(fullPath, content, "utf-8");
144056
+ await writeFile9(fullPath, content, "utf-8");
143691
144057
  const isScript = filePath.startsWith("scripts/") || content.trimStart().startsWith("#!");
143692
144058
  if (isScript) {
143693
144059
  try {
@@ -143740,7 +144106,7 @@ function parseRegistryHandle2(handle) {
143740
144106
  async function importAgentFromRegistry2(options) {
143741
144107
  const { tmpdir: tmpdir7 } = await import("node:os");
143742
144108
  const { join: join50 } = await import("node:path");
143743
- const { writeFile: writeFile9, unlink: unlink3 } = await import("node:fs/promises");
144109
+ const { writeFile: writeFile10, unlink: unlink3 } = await import("node:fs/promises");
143744
144110
  const { author, name } = parseRegistryHandle2(options.handle);
143745
144111
  const rawUrl = `https://raw.githubusercontent.com/${AGENT_REGISTRY_OWNER2}/${AGENT_REGISTRY_REPO2}/refs/heads/${AGENT_REGISTRY_BRANCH2}/agents/@${author}/${name}/${name}.af`;
143746
144112
  const response = await fetch(rawUrl);
@@ -143752,7 +144118,7 @@ async function importAgentFromRegistry2(options) {
143752
144118
  }
143753
144119
  const afContent = await response.text();
143754
144120
  const tempPath = join50(tmpdir7(), `letta-import-${author}-${name}-${Date.now()}.af`);
143755
- await writeFile9(tempPath, afContent, "utf-8");
144121
+ await writeFile10(tempPath, afContent, "utf-8");
143756
144122
  try {
143757
144123
  const result = await importAgentFromFile2({
143758
144124
  filePath: tempPath,
@@ -148687,12 +149053,15 @@ async function runMemfsSubcommand(argv) {
148687
149053
 
148688
149054
  // src/cli/subcommands/messages.ts
148689
149055
  await init_client2();
149056
+ import { writeFile as writeFile6 } from "node:fs/promises";
149057
+ import { resolve as resolve23 } from "node:path";
148690
149058
  import { parseArgs as parseArgs8 } from "node:util";
148691
149059
  function printUsage5() {
148692
149060
  console.log(`
148693
149061
  Usage:
148694
149062
  letta messages search --query <text> [options]
148695
149063
  letta messages list [options]
149064
+ letta messages transcript --conversation <id> [options]
148696
149065
 
148697
149066
  Search options:
148698
149067
  --query <text> Search query (required)
@@ -148714,6 +149083,16 @@ List options:
148714
149083
  --start-date <date> Client-side filter: after this date (ISO format)
148715
149084
  --end-date <date> Client-side filter: before this date (ISO format)
148716
149085
 
149086
+ Transcript options:
149087
+ --conversation <id> Conversation ID to export (required)
149088
+ --conversation-id <id> Alias for --conversation
149089
+ --agent <id> Required when conversation is "default"
149090
+ --agent-id <id> Alias for --agent
149091
+ --limit <n> Page size while fetching (default: 100)
149092
+ --max-pages <n> Max pagination pages to fetch (default: 200)
149093
+ --out <path> Write transcript text to file
149094
+ --output <path> Alias for --out
149095
+
148717
149096
  Notes:
148718
149097
  - Output is JSON only.
148719
149098
  - Uses CLI auth; override with LETTA_API_KEY/LETTA_BASE_URL if needed.
@@ -148755,7 +149134,12 @@ var MESSAGES_OPTIONS = {
148755
149134
  "agent-id": { type: "string" },
148756
149135
  after: { type: "string" },
148757
149136
  before: { type: "string" },
148758
- order: { type: "string" }
149137
+ order: { type: "string" },
149138
+ conversation: { type: "string" },
149139
+ "conversation-id": { type: "string" },
149140
+ "max-pages": { type: "string" },
149141
+ out: { type: "string" },
149142
+ output: { type: "string" }
148759
149143
  };
148760
149144
  function parseMessagesArgs(argv) {
148761
149145
  return parseArgs8({
@@ -148782,6 +149166,115 @@ async function runMessagesSubcommand(argv) {
148782
149166
  }
148783
149167
  try {
148784
149168
  const client = await getClient();
149169
+ const renderText = (value) => {
149170
+ if (typeof value === "string")
149171
+ return value;
149172
+ if (!Array.isArray(value))
149173
+ return "";
149174
+ return value.map((part) => {
149175
+ if (part && typeof part === "object" && "type" in part && part.type === "text" && "text" in part) {
149176
+ return typeof part.text === "string" ? part.text : "";
149177
+ }
149178
+ return "";
149179
+ }).filter((text) => text.length > 0).join(`
149180
+ `);
149181
+ };
149182
+ const renderUnknown = (value) => {
149183
+ if (typeof value === "string")
149184
+ return value;
149185
+ if (value === null || value === undefined)
149186
+ return "";
149187
+ try {
149188
+ return JSON.stringify(value);
149189
+ } catch {
149190
+ return String(value);
149191
+ }
149192
+ };
149193
+ const safeTypeLabel = (msg) => msg.message_type || "unknown_message";
149194
+ const formatEntry = (msg) => {
149195
+ const timestamp = msg.date || "unknown-time";
149196
+ const type = safeTypeLabel(msg);
149197
+ if (type === "user_message") {
149198
+ const text = renderText(msg.content);
149199
+ return [`[${timestamp}] user`, text || "(empty)"];
149200
+ }
149201
+ if (type === "assistant_message") {
149202
+ const text = renderText(msg.content);
149203
+ return [`[${timestamp}] assistant`, text || "(empty)"];
149204
+ }
149205
+ if (type === "reasoning_message") {
149206
+ return [
149207
+ `[${timestamp}] reasoning`,
149208
+ msg.reasoning && msg.reasoning.length > 0 ? msg.reasoning : "(empty)"
149209
+ ];
149210
+ }
149211
+ if (type === "tool_call_message" || type === "approval_request_message") {
149212
+ const calls = Array.isArray(msg.tool_calls) ? msg.tool_calls : msg.tool_call ? [msg.tool_call] : [];
149213
+ if (calls.length === 0) {
149214
+ return [`[${timestamp}] ${type}`, "(no tool call payload)"];
149215
+ }
149216
+ return calls.flatMap((call) => {
149217
+ const header = `[${timestamp}] tool_call ${call.name || "unknown"} (${call.tool_call_id || "no-id"})`;
149218
+ const args = call.arguments ? call.arguments : "{}";
149219
+ return [header, args];
149220
+ });
149221
+ }
149222
+ if (type === "tool_return_message") {
149223
+ const returns = Array.isArray(msg.tool_returns) ? msg.tool_returns : [
149224
+ {
149225
+ tool_call_id: msg.tool_call_id,
149226
+ status: msg.status,
149227
+ tool_return: msg.tool_return,
149228
+ func_response: msg.func_response
149229
+ }
149230
+ ];
149231
+ return returns.flatMap((ret) => {
149232
+ const header = `[${timestamp}] tool_return (${ret.tool_call_id || "no-id"}) status=${ret.status || "unknown"}`;
149233
+ const body = renderUnknown(ret.tool_return ?? ret.func_response);
149234
+ return [header, body || "(empty)"];
149235
+ });
149236
+ }
149237
+ const fallbackText = renderText(msg.content) || renderUnknown(msg.content) || "(no content)";
149238
+ return [`[${timestamp}] ${type}`, fallbackText];
149239
+ };
149240
+ const sortChronological3 = (messages) => {
149241
+ return [...messages].sort((a, b) => {
149242
+ const ta = a.date ? new Date(a.date).getTime() : 0;
149243
+ const tb = b.date ? new Date(b.date).getTime() : 0;
149244
+ return ta - tb;
149245
+ });
149246
+ };
149247
+ const fetchConversationMessages = async (conversationId, agentIdForDefault, pageLimit, maxPages) => {
149248
+ const collected = [];
149249
+ const seenIds = new Set;
149250
+ let cursorBefore;
149251
+ for (let pageIndex = 0;pageIndex < maxPages; pageIndex += 1) {
149252
+ const page = await client.conversations.messages.list(conversationId, {
149253
+ limit: pageLimit,
149254
+ order: "desc",
149255
+ ...conversationId === "default" && agentIdForDefault ? { agent_id: agentIdForDefault } : {},
149256
+ ...cursorBefore ? { before: cursorBefore } : {}
149257
+ });
149258
+ const items = page.getPaginatedItems();
149259
+ if (items.length === 0) {
149260
+ break;
149261
+ }
149262
+ let newItems = 0;
149263
+ for (const item of items) {
149264
+ const id = item.id;
149265
+ if (id && !seenIds.has(id)) {
149266
+ seenIds.add(id);
149267
+ collected.push(item);
149268
+ newItems += 1;
149269
+ }
149270
+ }
149271
+ cursorBefore = items[items.length - 1]?.id;
149272
+ if (newItems === 0 || items.length < pageLimit) {
149273
+ break;
149274
+ }
149275
+ }
149276
+ return sortChronological3(collected);
149277
+ };
148785
149278
  if (action === "search") {
148786
149279
  const query = parsed.values.query;
148787
149280
  if (!query || typeof query !== "string") {
@@ -148846,6 +149339,44 @@ async function runMessagesSubcommand(argv) {
148846
149339
  console.log(JSON.stringify(sorted, null, 2));
148847
149340
  return 0;
148848
149341
  }
149342
+ if (action === "transcript") {
149343
+ const conversationId = parsed.values.conversation || parsed.values["conversation-id"];
149344
+ if (!conversationId || typeof conversationId !== "string") {
149345
+ console.error("Missing conversation id. Pass --conversation <id> or --conversation-id <id>.");
149346
+ return 1;
149347
+ }
149348
+ const agentId = getAgentId5(parsed.values.agent, parsed.values["agent-id"]);
149349
+ if (conversationId === "default" && !agentId) {
149350
+ console.error('Conversation "default" requires an agent id. Set LETTA_AGENT_ID or pass --agent/--agent-id.');
149351
+ return 1;
149352
+ }
149353
+ const pageLimit = Math.max(1, parseLimit3(parsed.values.limit, 100));
149354
+ const maxPages = Math.max(1, parseLimit3(parsed.values["max-pages"], 200));
149355
+ const outputPathRaw = parsed.values.out || parsed.values.output;
149356
+ const messages = await fetchConversationMessages(conversationId, agentId || undefined, pageLimit, maxPages);
149357
+ const transcript = messages.flatMap((msg) => formatEntry(msg)).join(`
149358
+
149359
+ `).trim();
149360
+ if (outputPathRaw && typeof outputPathRaw === "string") {
149361
+ const outputPath = resolve23(process.cwd(), outputPathRaw);
149362
+ await writeFile6(outputPath, `${transcript}
149363
+ `, "utf-8");
149364
+ console.log(JSON.stringify({
149365
+ conversation_id: conversationId,
149366
+ agent_id: agentId || null,
149367
+ message_count: messages.length,
149368
+ output_path: outputPath
149369
+ }, null, 2));
149370
+ return 0;
149371
+ }
149372
+ console.log(JSON.stringify({
149373
+ conversation_id: conversationId,
149374
+ agent_id: agentId || null,
149375
+ message_count: messages.length,
149376
+ transcript
149377
+ }, null, 2));
149378
+ return 0;
149379
+ }
148849
149380
  } catch (error) {
148850
149381
  console.error(error instanceof Error ? error.message : String(error));
148851
149382
  return 1;
@@ -148886,7 +149417,7 @@ async function runSubcommand(argv) {
148886
149417
  init_readOnlyShell();
148887
149418
  init_shell_command_normalization();
148888
149419
  import { homedir as homedir23 } from "node:os";
148889
- import { isAbsolute as isAbsolute16, join as join29, relative as relative12, resolve as resolve23 } from "node:path";
149420
+ import { isAbsolute as isAbsolute16, join as join29, relative as relative12, resolve as resolve24 } from "node:path";
148890
149421
  var MODE_KEY2 = Symbol.for("@letta/permissionMode");
148891
149422
  var PLAN_FILE_KEY2 = Symbol.for("@letta/planFilePath");
148892
149423
  var MODE_BEFORE_PLAN_KEY2 = Symbol.for("@letta/permissionModeBeforePlan");
@@ -148922,12 +149453,12 @@ function resolvePlanTargetPath2(targetPath, workingDirectory) {
148922
149453
  if (!trimmedPath)
148923
149454
  return null;
148924
149455
  if (trimmedPath.startsWith("~/")) {
148925
- return resolve23(homedir23(), trimmedPath.slice(2));
149456
+ return resolve24(homedir23(), trimmedPath.slice(2));
148926
149457
  }
148927
149458
  if (isAbsolute16(trimmedPath)) {
148928
- return resolve23(trimmedPath);
149459
+ return resolve24(trimmedPath);
148929
149460
  }
148930
- return resolve23(workingDirectory, trimmedPath);
149461
+ return resolve24(workingDirectory, trimmedPath);
148931
149462
  }
148932
149463
  function isPathInPlansDir2(path23, plansDir) {
148933
149464
  if (!path23.endsWith(".md"))
@@ -149203,7 +149734,7 @@ await __promiseAll([
149203
149734
  ]);
149204
149735
  import { randomUUID as randomUUID4 } from "node:crypto";
149205
149736
  import { homedir as homedir24 } from "node:os";
149206
- import { join as join30, resolve as resolve24 } from "node:path";
149737
+ import { join as join30, resolve as resolve25 } from "node:path";
149207
149738
  var DEFAULT_SETTINGS3 = {
149208
149739
  lastAgent: null,
149209
149740
  tokenStreaming: false,
@@ -149228,6 +149759,9 @@ var DEFAULT_LETTA_API_URL2 = "https://api.letta.com";
149228
149759
  function isSubagentProcess2() {
149229
149760
  return process.env.LETTA_CODE_AGENT_ROLE === "subagent";
149230
149761
  }
149762
+ function shouldPersistSessionState2() {
149763
+ return process.env.LETTA_CODE_AGENT_ROLE !== "subagent" && process.env.LETTA_DISABLE_SESSION_PERSIST !== "1";
149764
+ }
149231
149765
  function normalizeBaseUrl2(baseUrl) {
149232
149766
  let normalized = baseUrl.replace(/^https?:\/\//, "");
149233
149767
  normalized = normalized.replace(/\/$/, "");
@@ -149654,7 +150188,7 @@ class SettingsManager2 {
149654
150188
  return join30(workingDirectory, ".letta", "settings.json");
149655
150189
  }
149656
150190
  isProjectSettingsPathCollidingWithGlobal(workingDirectory) {
149657
- return resolve24(this.getProjectSettingsPath(workingDirectory)) === resolve24(this.getSettingsPath());
150191
+ return resolve25(this.getProjectSettingsPath(workingDirectory)) === resolve25(this.getSettingsPath());
149658
150192
  }
149659
150193
  getLocalProjectSettingsPath(workingDirectory) {
149660
150194
  return join30(workingDirectory, ".letta", "settings.local.json");
@@ -150616,8 +151150,8 @@ function acquireSwitchLock2() {
150616
151150
  const lock = getSwitchLock2();
150617
151151
  lock.refCount++;
150618
151152
  if (lock.refCount === 1) {
150619
- lock.promise = new Promise((resolve25) => {
150620
- lock.resolve = resolve25;
151153
+ lock.promise = new Promise((resolve26) => {
151154
+ lock.resolve = resolve26;
150621
151155
  });
150622
151156
  }
150623
151157
  }
@@ -150888,6 +151422,7 @@ SUBCOMMANDS (JSON-only)
150888
151422
  letta agents list [--query <text> | --name <name> | --tags <tags>]
150889
151423
  letta messages search --query <text> [--all-agents]
150890
151424
  letta messages list [--agent <id>]
151425
+ letta messages transcript --conversation <id> [--out <path>]
150891
151426
  letta blocks list --agent <id>
150892
151427
  letta blocks copy --block-id <id> [--label <label>] [--agent <id>] [--override]
150893
151428
  letta blocks attach --block-id <id> [--agent <id>] [--read-only] [--override]
@@ -151076,14 +151611,6 @@ async function main() {
151076
151611
  }
151077
151612
  const { checkAndAutoUpdate: checkAndAutoUpdate2 } = await init_auto_update().then(() => exports_auto_update);
151078
151613
  const autoUpdatePromise = startStartupAutoUpdateCheck(checkAndAutoUpdate2);
151079
- const { startDockerVersionCheck: startDockerVersionCheck2 } = await init_startup_docker_check().then(() => exports_startup_docker_check);
151080
- startDockerVersionCheck2().catch(() => {});
151081
- const { cleanupOldOverflowFiles: cleanupOldOverflowFiles2 } = await Promise.resolve().then(() => (init_overflow2(), exports_overflow));
151082
- Promise.resolve().then(() => {
151083
- try {
151084
- cleanupOldOverflowFiles2(process.cwd());
151085
- } catch {}
151086
- });
151087
151614
  const processedArgs = preprocessCliArgs(process.argv);
151088
151615
  let values;
151089
151616
  let positionals;
@@ -151109,7 +151636,7 @@ Note: Flags should use double dashes for full names (e.g., --yolo, not -yolo)`);
151109
151636
  printHelp();
151110
151637
  const helpDelayMs = Number.parseInt(process.env.LETTA_TEST_HELP_EXIT_DELAY_MS ?? "", 10);
151111
151638
  if (Number.isFinite(helpDelayMs) && helpDelayMs > 0) {
151112
- await new Promise((resolve32) => setTimeout(resolve32, helpDelayMs));
151639
+ await new Promise((resolve33) => setTimeout(resolve33, helpDelayMs));
151113
151640
  }
151114
151641
  process.exit(0);
151115
151642
  }
@@ -151194,6 +151721,16 @@ Note: Flags should use double dashes for full names (e.g., --yolo, not -yolo)`);
151194
151721
  const isHeadless = values.prompt || values.run || !process.stdin.isTTY;
151195
151722
  telemetry2.setSurface(isHeadless ? "headless" : "tui");
151196
151723
  telemetry2.init();
151724
+ if (!isHeadless) {
151725
+ const { startDockerVersionCheck: startDockerVersionCheck2 } = await init_startup_docker_check().then(() => exports_startup_docker_check);
151726
+ startDockerVersionCheck2().catch(() => {});
151727
+ const { cleanupOldOverflowFiles: cleanupOldOverflowFiles2 } = await Promise.resolve().then(() => (init_overflow2(), exports_overflow));
151728
+ Promise.resolve().then(() => {
151729
+ try {
151730
+ cleanupOldOverflowFiles2(process.cwd());
151731
+ } catch {}
151732
+ });
151733
+ }
151197
151734
  if (command && !isHeadless) {
151198
151735
  console.error(`Error: Unknown command or argument "${command}"`);
151199
151736
  console.error("Run 'letta --help' for usage information.");
@@ -151321,9 +151858,9 @@ Note: Flags should use double dashes for full names (e.g., --yolo, not -yolo)`);
151321
151858
  process.exit(1);
151322
151859
  }
151323
151860
  } else {
151324
- const { resolve: resolve32 } = await import("path");
151861
+ const { resolve: resolve33 } = await import("path");
151325
151862
  const { existsSync: existsSync40 } = await import("fs");
151326
- const resolvedPath = resolve32(fromAfFile);
151863
+ const resolvedPath = resolve33(fromAfFile);
151327
151864
  if (!existsSync40(resolvedPath)) {
151328
151865
  console.error(`Error: AgentFile not found: ${resolvedPath}`);
151329
151866
  process.exit(1);
@@ -151798,6 +152335,7 @@ Error: ${message}`);
151798
152335
  setLoadingState("initializing");
151799
152336
  const { createAgent: createAgent3 } = await init_create3().then(() => exports_create2);
151800
152337
  let agent = null;
152338
+ let autoEnableMemfsForFreshAgent = false;
151801
152339
  if (fromAfFile2) {
151802
152340
  setLoadingState("importing");
151803
152341
  let result;
@@ -151882,10 +152420,7 @@ Error: ${message}`);
151882
152420
  });
151883
152421
  agent = result.agent;
151884
152422
  setAgentProvenance(result.provenance);
151885
- if (willAutoEnableMemfs) {
151886
- const { enableMemfsIfCloud: enableMemfsIfCloud3 } = await Promise.resolve().then(() => (init_memoryFilesystem2(), exports_memoryFilesystem2));
151887
- await enableMemfsIfCloud3(agent.id);
151888
- }
152423
+ autoEnableMemfsForFreshAgent = willAutoEnableMemfs;
151889
152424
  }
151890
152425
  if (!agent && resumingAgentId) {
151891
152426
  try {
@@ -151908,11 +152443,12 @@ Error: ${message}`);
151908
152443
  settingsManager2.updateLocalProjectSettings({ lastAgent: agent.id });
151909
152444
  settingsManager2.updateSettings({ lastAgent: agent.id });
151910
152445
  setAgentContext(agent.id, skillsDirectory2, resolvedSkillSources);
151911
- const isSubagent = process.env.LETTA_CODE_AGENT_ROLE === "subagent";
151912
152446
  const agentId2 = agent.id;
151913
152447
  const agentTags = agent.tags ?? undefined;
151914
- const memfsSyncPromise = Promise.resolve().then(() => (init_memoryFilesystem2(), exports_memoryFilesystem2)).then(({ applyMemfsFlags: applyMemfsFlags3 }) => applyMemfsFlags3(agentId2, memfsFlag, noMemfsFlag, {
151915
- agentTags
152448
+ const startupMemfsFlag = autoEnableMemfsForFreshAgent ? true : memfsFlag;
152449
+ const memfsSyncPromise = Promise.resolve().then(() => (init_memoryFilesystem2(), exports_memoryFilesystem2)).then(({ applyMemfsFlags: applyMemfsFlags3 }) => applyMemfsFlags3(agentId2, startupMemfsFlag, noMemfsFlag, {
152450
+ agentTags,
152451
+ skipPromptUpdate: shouldCreateNew
151916
152452
  }));
151917
152453
  const secretsInitPromise = init_secretsStore2().then(() => exports_secretsStore2).then(({ initSecretsFromServer: initSecretsFromServer3 }) => initSecretsFromServer3(agentId2));
151918
152454
  const isResumingProject = !shouldCreateNew && !!resumingAgentId;
@@ -152053,7 +152589,7 @@ Error: ${message}`);
152053
152589
  }
152054
152590
  }
152055
152591
  }
152056
- if (!isSubagent) {
152592
+ if (shouldPersistSessionState2()) {
152057
152593
  settingsManager2.persistSession(agent.id, conversationIdToUse);
152058
152594
  }
152059
152595
  setAgentId(agent.id);
@@ -152199,4 +152735,4 @@ Error during initialization: ${message}`);
152199
152735
  }
152200
152736
  main();
152201
152737
 
152202
- //# debugId=594F8E4675FBE99864756E2164756E21
152738
+ //# debugId=AA1793542E1A081B64756E2164756E21