@letta-ai/letta-code 0.21.8 → 0.21.10

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.8",
3272
+ version: "0.21.10",
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.
6867
6901
 
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.
6902
+ Avoid low-value summaries like:
6903
+ - "User is direct"
6904
+ - "Project uses TypeScript"
6905
+ - "Uses conventional commits"
6872
6906
 
6873
- **What NOT to store**: Raw quotes, one-off events, session-by-session summaries, anything that can be retrieved from conversation history on demand.
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.
6911
+
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.
6914
+
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 },
@@ -77004,8 +77100,8 @@ function isValidApprovalResponseBody(value) {
77004
77100
  if (decision.behavior === "allow") {
77005
77101
  const hasMessage = decision.message === undefined || typeof decision.message === "string";
77006
77102
  const hasUpdatedInput = decision.updated_input === undefined || decision.updated_input === null || typeof decision.updated_input === "object";
77007
- const hasUpdatedPermissions = decision.updated_permissions === undefined || Array.isArray(decision.updated_permissions) && decision.updated_permissions.every((entry) => typeof entry === "string");
77008
- return hasMessage && hasUpdatedInput && hasUpdatedPermissions;
77103
+ const hasSelectedPermissionSuggestionIds = decision.selected_permission_suggestion_ids === undefined || Array.isArray(decision.selected_permission_suggestion_ids) && decision.selected_permission_suggestion_ids.every((entry) => typeof entry === "string");
77104
+ return hasMessage && hasUpdatedInput && hasSelectedPermissionSuggestionIds;
77009
77105
  }
77010
77106
  if (decision.behavior === "deny") {
77011
77107
  return typeof decision.message === "string";
@@ -77097,6 +77193,7 @@ function requestApprovalOverWS(runtime, socket, requestId, controlRequest) {
77097
77193
  controlRequest
77098
77194
  });
77099
77195
  runtime.listener.approvalRuntimeKeyByRequestId.set(requestId, runtime.key);
77196
+ runtime.lastStopReason = "requires_approval";
77100
77197
  setLoopStatus(runtime, "WAITING_ON_APPROVAL");
77101
77198
  emitLoopStatusIfOpen(runtime.listener, {
77102
77199
  agent_id: runtime.agentId,
@@ -77565,97 +77662,72 @@ var init_permissionMode = __esm(() => {
77565
77662
  init_remote_settings();
77566
77663
  });
77567
77664
 
77568
- // src/cli/helpers/safeJsonParse.ts
77569
- function safeJsonParse(json) {
77570
- try {
77571
- const data = JSON.parse(json);
77572
- return { success: true, data };
77573
- } catch (error) {
77574
- return {
77575
- success: false,
77576
- error: error instanceof Error ? error.message : String(error)
77577
- };
77665
+ // src/websocket/listener/recoverable-notices.ts
77666
+ function getRecoverableStatusNoticeVisibility(kind) {
77667
+ switch (kind) {
77668
+ case "stale_approval_conflict_recovery":
77669
+ return "debug_only";
77670
+ default:
77671
+ return "transcript";
77578
77672
  }
77579
77673
  }
77580
- function safeJsonParseOr(json, defaultValue) {
77581
- const result = safeJsonParse(json);
77582
- return result.success ? result.data : defaultValue;
77674
+ function getRecoverableRetryNoticeVisibility(kind, attempt) {
77675
+ switch (kind) {
77676
+ case "transient_provider_retry":
77677
+ return attempt === 1 ? "debug_only" : "transcript";
77678
+ default:
77679
+ return "transcript";
77680
+ }
77583
77681
  }
77584
-
77585
- // src/cli/helpers/approvalClassification.ts
77586
- async function getMissingRequiredArgs(toolName, parsedArgs) {
77587
- const schema = getToolSchema(toolName);
77588
- const required = schema?.input_schema?.required || [];
77589
- return required.filter((key) => !(key in parsedArgs) || parsedArgs[key] == null);
77682
+ function isDesktopDebugPanelMirrorEnabled() {
77683
+ return process.env.LETTA_DESKTOP_DEBUG_PANEL === "1";
77590
77684
  }
77591
- async function classifyApprovals(approvals, opts = {}) {
77592
- const needsUserInput = [];
77593
- const autoAllowed = [];
77594
- const autoDenied = [];
77595
- const denyReasonForAsk = opts.denyReasonForAsk ?? "Tool requires approval (headless mode)";
77596
- const missingNameReason = opts.missingNameReason ?? "Tool call incomplete - missing name";
77597
- for (const approval of approvals) {
77598
- const toolName = approval.toolName;
77599
- if (!toolName) {
77600
- autoDenied.push({
77601
- approval,
77602
- permission: { decision: "deny", reason: missingNameReason },
77603
- context: null,
77604
- parsedArgs: {},
77605
- denyReason: missingNameReason
77606
- });
77607
- continue;
77608
- }
77609
- const parsedArgs = safeJsonParseOr(approval.toolArgs || "{}", {});
77610
- const permission = await checkToolPermission(toolName, parsedArgs, opts.workingDirectory, opts.permissionModeState);
77611
- const context3 = opts.getContext ? await opts.getContext(toolName, parsedArgs, opts.workingDirectory) : null;
77612
- let decision = permission.decision;
77613
- if (opts.alwaysRequiresUserInput?.(toolName) && decision === "allow") {
77614
- decision = "ask";
77615
- }
77616
- if (decision === "ask" && opts.treatAskAsDeny) {
77617
- autoDenied.push({
77618
- approval,
77619
- permission,
77620
- context: context3,
77621
- parsedArgs,
77622
- denyReason: denyReasonForAsk
77623
- });
77624
- continue;
77625
- }
77626
- if (decision === "allow" && opts.requireArgsForAutoApprove) {
77627
- const missingRequiredArgs = await getMissingRequiredArgs(toolName, parsedArgs);
77628
- if (missingRequiredArgs.length > 0) {
77629
- const denyReason = opts.missingArgsReason ? opts.missingArgsReason(missingRequiredArgs) : `Missing required parameter${missingRequiredArgs.length > 1 ? "s" : ""}: ${missingRequiredArgs.join(", ")}`;
77630
- autoDenied.push({
77631
- approval,
77632
- permission,
77633
- context: context3,
77634
- parsedArgs,
77635
- missingRequiredArgs,
77636
- denyReason
77637
- });
77638
- continue;
77639
- }
77640
- }
77641
- const entry = {
77642
- approval,
77643
- permission,
77644
- context: context3,
77645
- parsedArgs
77646
- };
77647
- if (decision === "ask") {
77648
- needsUserInput.push(entry);
77649
- } else if (decision === "deny") {
77650
- autoDenied.push(entry);
77651
- } else {
77652
- autoAllowed.push(entry);
77653
- }
77685
+ function mirrorRecoverableNoticeToDesktopDebugPanel(message) {
77686
+ if (!isDesktopDebugPanelMirrorEnabled()) {
77687
+ return;
77654
77688
  }
77655
- return { needsUserInput, autoAllowed, autoDenied };
77689
+ try {
77690
+ process.stderr.write(`${DESKTOP_DEBUG_PANEL_INFO_PREFIX} ${message}
77691
+ `);
77692
+ } catch {}
77656
77693
  }
77657
- var init_approvalClassification = __esm(async () => {
77658
- await init_manager3();
77694
+ function emitRecoverableStatusNotice(socket, runtime, params) {
77695
+ const visibility = getRecoverableStatusNoticeVisibility(params.kind);
77696
+ if (visibility === "debug_only") {
77697
+ debugLog("recovery", `Debug-only lifecycle notice (${params.kind}): ${params.message}`);
77698
+ mirrorRecoverableNoticeToDesktopDebugPanel(params.message);
77699
+ return;
77700
+ }
77701
+ emitStatusDelta(socket, runtime, {
77702
+ message: params.message,
77703
+ level: params.level,
77704
+ runId: params.runId,
77705
+ agentId: params.agentId,
77706
+ conversationId: params.conversationId
77707
+ });
77708
+ }
77709
+ function emitRecoverableRetryNotice(socket, runtime, params) {
77710
+ const visibility = getRecoverableRetryNoticeVisibility(params.kind, params.attempt);
77711
+ if (visibility === "debug_only") {
77712
+ debugLog("recovery", `Debug-only retry notice (${params.kind}, attempt ${params.attempt}/${params.maxAttempts}): ${params.message}`);
77713
+ mirrorRecoverableNoticeToDesktopDebugPanel(params.message);
77714
+ return;
77715
+ }
77716
+ emitRetryDelta(socket, runtime, {
77717
+ message: params.message,
77718
+ reason: params.reason,
77719
+ attempt: params.attempt,
77720
+ maxAttempts: params.maxAttempts,
77721
+ delayMs: params.delayMs,
77722
+ runId: params.runId,
77723
+ agentId: params.agentId,
77724
+ conversationId: params.conversationId
77725
+ });
77726
+ }
77727
+ var DESKTOP_DEBUG_PANEL_INFO_PREFIX = "[LETTA_DESKTOP_DEBUG_PANEL_INFO]";
77728
+ var init_recoverable_notices = __esm(async () => {
77729
+ init_debug();
77730
+ await init_protocol_outbound();
77659
77731
  });
77660
77732
 
77661
77733
  // node_modules/diff/libesm/diff/base.js
@@ -79017,6 +79089,162 @@ async function computeDiffPreviews(toolName, toolArgs, workingDirectory = proces
79017
79089
  var cachedDiffDeps = null;
79018
79090
  var init_diffPreview = () => {};
79019
79091
 
79092
+ // src/cli/helpers/safeJsonParse.ts
79093
+ function safeJsonParse(json) {
79094
+ try {
79095
+ const data = JSON.parse(json);
79096
+ return { success: true, data };
79097
+ } catch (error) {
79098
+ return {
79099
+ success: false,
79100
+ error: error instanceof Error ? error.message : String(error)
79101
+ };
79102
+ }
79103
+ }
79104
+ function safeJsonParseOr(json, defaultValue) {
79105
+ const result = safeJsonParse(json);
79106
+ return result.success ? result.data : defaultValue;
79107
+ }
79108
+
79109
+ // src/cli/helpers/approvalClassification.ts
79110
+ async function getMissingRequiredArgs(toolName, parsedArgs) {
79111
+ const schema = getToolSchema(toolName);
79112
+ const required = schema?.input_schema?.required || [];
79113
+ return required.filter((key) => !(key in parsedArgs) || parsedArgs[key] == null);
79114
+ }
79115
+ async function classifyApprovals(approvals, opts = {}) {
79116
+ const needsUserInput = [];
79117
+ const autoAllowed = [];
79118
+ const autoDenied = [];
79119
+ const denyReasonForAsk = opts.denyReasonForAsk ?? "Tool requires approval (headless mode)";
79120
+ const missingNameReason = opts.missingNameReason ?? "Tool call incomplete - missing name";
79121
+ for (const approval of approvals) {
79122
+ const toolName = approval.toolName;
79123
+ if (!toolName) {
79124
+ autoDenied.push({
79125
+ approval,
79126
+ permission: { decision: "deny", reason: missingNameReason },
79127
+ context: null,
79128
+ parsedArgs: {},
79129
+ denyReason: missingNameReason
79130
+ });
79131
+ continue;
79132
+ }
79133
+ const parsedArgs = safeJsonParseOr(approval.toolArgs || "{}", {});
79134
+ const permission = await checkToolPermission(toolName, parsedArgs, opts.workingDirectory, opts.permissionModeState);
79135
+ const context3 = opts.getContext ? await opts.getContext(toolName, parsedArgs, opts.workingDirectory) : null;
79136
+ let decision = permission.decision;
79137
+ if (opts.alwaysRequiresUserInput?.(toolName) && decision === "allow") {
79138
+ decision = "ask";
79139
+ }
79140
+ if (decision === "ask" && opts.treatAskAsDeny) {
79141
+ autoDenied.push({
79142
+ approval,
79143
+ permission,
79144
+ context: context3,
79145
+ parsedArgs,
79146
+ denyReason: denyReasonForAsk
79147
+ });
79148
+ continue;
79149
+ }
79150
+ if (decision === "allow" && opts.requireArgsForAutoApprove) {
79151
+ const missingRequiredArgs = await getMissingRequiredArgs(toolName, parsedArgs);
79152
+ if (missingRequiredArgs.length > 0) {
79153
+ const denyReason = opts.missingArgsReason ? opts.missingArgsReason(missingRequiredArgs) : `Missing required parameter${missingRequiredArgs.length > 1 ? "s" : ""}: ${missingRequiredArgs.join(", ")}`;
79154
+ autoDenied.push({
79155
+ approval,
79156
+ permission,
79157
+ context: context3,
79158
+ parsedArgs,
79159
+ missingRequiredArgs,
79160
+ denyReason
79161
+ });
79162
+ continue;
79163
+ }
79164
+ }
79165
+ const entry = {
79166
+ approval,
79167
+ permission,
79168
+ context: context3,
79169
+ parsedArgs
79170
+ };
79171
+ if (decision === "ask") {
79172
+ needsUserInput.push(entry);
79173
+ } else if (decision === "deny") {
79174
+ autoDenied.push(entry);
79175
+ } else {
79176
+ autoAllowed.push(entry);
79177
+ }
79178
+ }
79179
+ return { needsUserInput, autoAllowed, autoDenied };
79180
+ }
79181
+ var init_approvalClassification = __esm(async () => {
79182
+ await init_manager3();
79183
+ });
79184
+
79185
+ // src/websocket/listener/approval-suggestions.ts
79186
+ function getSuggestedPermissionRule(context3) {
79187
+ if (!context3?.allowPersistence || context3.recommendedRule.trim().length === 0) {
79188
+ return null;
79189
+ }
79190
+ return context3.recommendedRule;
79191
+ }
79192
+ function getApprovalPermissionSuggestions(context3) {
79193
+ const suggestedRule = getSuggestedPermissionRule(context3);
79194
+ if (suggestedRule === null || !context3) {
79195
+ return [];
79196
+ }
79197
+ const text = context3.approveAlwaysText.trim();
79198
+ if (text.length === 0) {
79199
+ return [];
79200
+ }
79201
+ return [
79202
+ {
79203
+ suggestion: {
79204
+ id: "save-default",
79205
+ text
79206
+ },
79207
+ rule: suggestedRule,
79208
+ scope: context3.defaultScope
79209
+ }
79210
+ ];
79211
+ }
79212
+ function buildApprovalSuggestionPayload(context3) {
79213
+ return {
79214
+ permission_suggestions: getApprovalPermissionSuggestions(context3).map(({ suggestion }) => suggestion)
79215
+ };
79216
+ }
79217
+ async function classifyApprovalsWithSuggestions(approvals, opts = {}) {
79218
+ return classifyApprovals(approvals, {
79219
+ ...opts,
79220
+ getContext: async (toolName, parsedArgs, workingDirectory) => analyzeToolApproval(toolName, parsedArgs, workingDirectory)
79221
+ });
79222
+ }
79223
+ async function applySuggestedPermissionsForApproval(params) {
79224
+ const { decision, context: context3, workingDirectory } = params;
79225
+ if (!context3?.allowPersistence || context3.defaultScope === undefined) {
79226
+ return false;
79227
+ }
79228
+ const selectedIds = decision.selected_permission_suggestion_ids ?? [];
79229
+ if (selectedIds.length === 0) {
79230
+ return false;
79231
+ }
79232
+ const matchedSuggestions = getApprovalPermissionSuggestions(context3).filter(({ suggestion }) => selectedIds.includes(suggestion.id));
79233
+ if (matchedSuggestions.length === 0) {
79234
+ return false;
79235
+ }
79236
+ for (const matchedSuggestion of matchedSuggestions) {
79237
+ await savePermissionRule2(matchedSuggestion.rule, "allow", matchedSuggestion.scope, workingDirectory);
79238
+ }
79239
+ return true;
79240
+ }
79241
+ var init_approval_suggestions = __esm(async () => {
79242
+ await __promiseAll([
79243
+ init_approvalClassification(),
79244
+ init_manager3()
79245
+ ]);
79246
+ });
79247
+
79020
79248
  // src/websocket/listener/recovery.ts
79021
79249
  function isApprovalToolCallDesyncError(detail) {
79022
79250
  if (isInvalidToolCallIdsError(detail) || isApprovalPendingError(detail)) {
@@ -79232,12 +79460,13 @@ async function recoverApprovalStateForSync(runtime, scope) {
79232
79460
  return;
79233
79461
  }
79234
79462
  const workingDirectory = getConversationWorkingDirectory(runtime.listener, scope.agent_id, scope.conversation_id);
79235
- const { needsUserInput, autoAllowed, autoDenied } = await classifyApprovals(pendingApprovals, {
79463
+ const permissionModeState = getOrCreateConversationPermissionModeStateRef(runtime.listener, scope.agent_id, scope.conversation_id);
79464
+ const { needsUserInput, autoAllowed, autoDenied } = await classifyApprovalsWithSuggestions(pendingApprovals, {
79236
79465
  alwaysRequiresUserInput: isInteractiveApprovalTool,
79237
79466
  requireArgsForAutoApprove: true,
79238
79467
  missingNameReason: "Tool call incomplete - missing name",
79239
79468
  workingDirectory,
79240
- permissionModeState: getOrCreateConversationPermissionModeStateRef(runtime.listener, scope.agent_id, scope.conversation_id)
79469
+ permissionModeState
79241
79470
  });
79242
79471
  const autoDecisions = buildRecoveredAutoDecisions(autoAllowed, autoDenied);
79243
79472
  if (needsUserInput.length === 0) {
@@ -79252,6 +79481,7 @@ async function recoverApprovalStateForSync(runtime, scope) {
79252
79481
  const diffs = await computeDiffPreviews(approval.toolName, input, workingDirectory);
79253
79482
  approvalsByRequestId.set(requestId, {
79254
79483
  approval,
79484
+ approvalContext: approvalEntry.context,
79255
79485
  controlRequest: {
79256
79486
  type: "control_request",
79257
79487
  request_id: requestId,
@@ -79260,7 +79490,7 @@ async function recoverApprovalStateForSync(runtime, scope) {
79260
79490
  tool_name: approval.toolName,
79261
79491
  input,
79262
79492
  tool_call_id: approval.toolCallId,
79263
- permission_suggestions: [],
79493
+ ...buildApprovalSuggestionPayload(approvalEntry.context),
79264
79494
  blocked_path: null,
79265
79495
  ...diffs.length > 0 ? { diffs } : {}
79266
79496
  },
@@ -79290,6 +79520,40 @@ async function resolveRecoveredApprovalResponse(runtime, socket, response, proce
79290
79520
  }
79291
79521
  recovered.responsesByRequestId.set(requestId, response);
79292
79522
  recovered.pendingRequestIds.delete(requestId);
79523
+ const workingDirectory = getConversationWorkingDirectory(runtime.listener, recovered.agentId, recovered.conversationId);
79524
+ const respondedEntry = recovered.approvalsByRequestId.get(requestId);
79525
+ if (respondedEntry && "decision" in response && response.decision.behavior === "allow") {
79526
+ const savedSuggestions = await applySuggestedPermissionsForApproval({
79527
+ decision: response.decision,
79528
+ context: respondedEntry.approvalContext,
79529
+ workingDirectory
79530
+ });
79531
+ if (savedSuggestions && recovered.pendingRequestIds.size > 0) {
79532
+ const remainingRecoveredEntries = [...recovered.pendingRequestIds].map((id) => recovered.approvalsByRequestId.get(id)).filter((entry) => !!entry);
79533
+ const reclassified = await classifyApprovalsWithSuggestions(remainingRecoveredEntries.map((entry) => entry.approval), {
79534
+ alwaysRequiresUserInput: isInteractiveApprovalTool,
79535
+ requireArgsForAutoApprove: true,
79536
+ missingNameReason: "Tool call incomplete - missing name",
79537
+ workingDirectory,
79538
+ permissionModeState: getOrCreateConversationPermissionModeStateRef(runtime.listener, recovered.agentId, recovered.conversationId)
79539
+ });
79540
+ if (reclassified.autoAllowed.length > 0 || reclassified.autoDenied.length > 0) {
79541
+ recovered.autoDecisions = [
79542
+ ...recovered.autoDecisions ?? [],
79543
+ ...buildRecoveredAutoDecisions(reclassified.autoAllowed, reclassified.autoDenied)
79544
+ ];
79545
+ const reclassifiedToolCallIds = new Set([...reclassified.autoAllowed, ...reclassified.autoDenied].map((entry) => entry.approval.toolCallId));
79546
+ for (const pendingId of [...recovered.pendingRequestIds]) {
79547
+ const pendingEntry = recovered.approvalsByRequestId.get(pendingId);
79548
+ if (pendingEntry && reclassifiedToolCallIds.has(pendingEntry.approval.toolCallId)) {
79549
+ recovered.pendingRequestIds.delete(pendingId);
79550
+ recovered.approvalsByRequestId.delete(pendingId);
79551
+ recovered.responsesByRequestId.delete(pendingId);
79552
+ }
79553
+ }
79554
+ }
79555
+ }
79556
+ }
79293
79557
  if (recovered.pendingRequestIds.size > 0) {
79294
79558
  emitRuntimeStateUpdates(runtime, {
79295
79559
  agent_id: recovered.agentId,
@@ -79342,7 +79606,7 @@ async function resolveRecoveredApprovalResponse(runtime, socket, response, proce
79342
79606
  recovered.pendingRequestIds.clear();
79343
79607
  emitRuntimeStateUpdates(runtime, scope);
79344
79608
  runtime.isProcessing = true;
79345
- runtime.activeWorkingDirectory = getConversationWorkingDirectory(runtime.listener, recovered.agentId, recovered.conversationId);
79609
+ runtime.activeWorkingDirectory = workingDirectory;
79346
79610
  runtime.activeExecutingToolCallIds = [...approvedToolCallIds];
79347
79611
  setLoopStatus(runtime, "EXECUTING_CLIENT_SIDE_TOOL", scope);
79348
79612
  emitRuntimeStateUpdates(runtime, scope);
@@ -79367,7 +79631,7 @@ async function resolveRecoveredApprovalResponse(runtime, socket, response, proce
79367
79631
  const approvalResults = await executeApprovalBatch(decisions, undefined, {
79368
79632
  abortSignal: recoveryAbortController.signal,
79369
79633
  toolContextId: preparedToolContext.preparedToolContext.contextId,
79370
- workingDirectory: getConversationWorkingDirectory(runtime.listener, recovered.agentId, recovered.conversationId),
79634
+ workingDirectory,
79371
79635
  parentScope: recovered.agentId && recovered.conversationId ? {
79372
79636
  agentId: recovered.agentId,
79373
79637
  conversationId: recovered.conversationId
@@ -79434,9 +79698,9 @@ var init_recovery = __esm(async () => {
79434
79698
  init_approval_execution(),
79435
79699
  init_client2(),
79436
79700
  init_accumulator(),
79437
- init_approvalClassification(),
79438
79701
  init_stream(),
79439
79702
  init_toolset(),
79703
+ init_approval_suggestions(),
79440
79704
  init_interrupts(),
79441
79705
  init_protocol_outbound(),
79442
79706
  init_queue()
@@ -79526,12 +79790,13 @@ async function resolveStaleApprovals(runtime, socket, abortSignal, deps = {}) {
79526
79790
  throw new Error("Ambiguous pending approval batch mapping during recovery");
79527
79791
  }
79528
79792
  rememberPendingApprovalBatchIds(runtime, pendingApprovals, recoveryBatchId);
79529
- const { autoAllowed, autoDenied, needsUserInput } = await classifyApprovals(pendingApprovals, {
79793
+ const permissionModeState = getOrCreateConversationPermissionModeStateRef(runtime.listener, runtime.agentId, runtime.conversationId);
79794
+ const { autoAllowed, autoDenied, needsUserInput } = await classifyApprovalsWithSuggestions(pendingApprovals, {
79530
79795
  alwaysRequiresUserInput: isInteractiveApprovalTool,
79531
79796
  requireArgsForAutoApprove: true,
79532
79797
  missingNameReason: "Tool call incomplete - missing name",
79533
79798
  workingDirectory: recoveryWorkingDirectory,
79534
- permissionModeState: getOrCreateConversationPermissionModeStateRef(runtime.listener, runtime.agentId, runtime.conversationId)
79799
+ permissionModeState
79535
79800
  });
79536
79801
  const decisions = [
79537
79802
  ...autoAllowed.map((ac) => ({
@@ -79544,11 +79809,13 @@ async function resolveStaleApprovals(runtime, socket, abortSignal, deps = {}) {
79544
79809
  reason: ac.denyReason || ac.permission.reason || "Permission denied"
79545
79810
  }))
79546
79811
  ];
79547
- if (needsUserInput.length > 0) {
79548
- runtime.lastStopReason = "requires_approval";
79549
- setLoopStatus(runtime, "WAITING_ON_APPROVAL", scope);
79550
- emitRuntimeStateUpdates(runtime, scope);
79551
- for (const ac of needsUserInput) {
79812
+ let pendingNeedsUserInput = [...needsUserInput];
79813
+ if (pendingNeedsUserInput.length > 0) {
79814
+ while (pendingNeedsUserInput.length > 0) {
79815
+ const ac = pendingNeedsUserInput.shift();
79816
+ if (!ac) {
79817
+ break;
79818
+ }
79552
79819
  if (abortSignal.aborted)
79553
79820
  throw new Error("Cancelled");
79554
79821
  const requestId = `perm-${ac.approval.toolCallId}`;
@@ -79561,7 +79828,7 @@ async function resolveStaleApprovals(runtime, socket, abortSignal, deps = {}) {
79561
79828
  tool_name: ac.approval.toolName,
79562
79829
  input: ac.parsedArgs,
79563
79830
  tool_call_id: ac.approval.toolCallId,
79564
- permission_suggestions: [],
79831
+ ...buildApprovalSuggestionPayload(ac.context),
79565
79832
  blocked_path: null,
79566
79833
  ...diffs.length > 0 ? { diffs } : {}
79567
79834
  },
@@ -79572,6 +79839,11 @@ async function resolveStaleApprovals(runtime, socket, abortSignal, deps = {}) {
79572
79839
  if ("decision" in responseBody) {
79573
79840
  const response = responseBody.decision;
79574
79841
  if (response.behavior === "allow") {
79842
+ const savedSuggestions = await applySuggestedPermissionsForApproval({
79843
+ decision: response,
79844
+ context: ac.context,
79845
+ workingDirectory: recoveryWorkingDirectory
79846
+ });
79575
79847
  decisions.push({
79576
79848
  type: "approve",
79577
79849
  approval: response.updated_input ? {
@@ -79580,6 +79852,24 @@ async function resolveStaleApprovals(runtime, socket, abortSignal, deps = {}) {
79580
79852
  } : ac.approval,
79581
79853
  reason: response.message
79582
79854
  });
79855
+ if (savedSuggestions && pendingNeedsUserInput.length > 0) {
79856
+ const reclassified = await classifyApprovalsWithSuggestions(pendingNeedsUserInput.map((entry) => entry.approval), {
79857
+ alwaysRequiresUserInput: isInteractiveApprovalTool,
79858
+ requireArgsForAutoApprove: true,
79859
+ missingNameReason: "Tool call incomplete - missing name",
79860
+ workingDirectory: recoveryWorkingDirectory,
79861
+ permissionModeState
79862
+ });
79863
+ decisions.push(...reclassified.autoAllowed.map((entry) => ({
79864
+ type: "approve",
79865
+ approval: entry.approval
79866
+ })), ...reclassified.autoDenied.map((entry) => ({
79867
+ type: "deny",
79868
+ approval: entry.approval,
79869
+ reason: entry.denyReason || entry.permission.reason || "Permission denied"
79870
+ })));
79871
+ pendingNeedsUserInput = [...reclassified.needsUserInput];
79872
+ }
79583
79873
  } else {
79584
79874
  decisions.push({
79585
79875
  type: "deny",
@@ -79929,9 +80219,9 @@ var init_send = __esm(async () => {
79929
80219
  init_approval_recovery(),
79930
80220
  init_client2(),
79931
80221
  init_message(),
79932
- init_approvalClassification(),
79933
80222
  init_toolset(),
79934
80223
  init_approval(),
80224
+ init_approval_suggestions(),
79935
80225
  init_interrupts(),
79936
80226
  init_protocol_outbound(),
79937
80227
  init_queue(),
@@ -79997,7 +80287,7 @@ async function handleApprovalStop(params) {
79997
80287
  }
79998
80288
  clearPendingApprovalBatchIds(runtime, approvals);
79999
80289
  rememberPendingApprovalBatchIds(runtime, approvals, dequeuedBatchId);
80000
- const { autoAllowed, autoDenied, needsUserInput } = await classifyApprovals(approvals, {
80290
+ const { autoAllowed, autoDenied, needsUserInput } = await classifyApprovalsWithSuggestions(approvals, {
80001
80291
  alwaysRequiresUserInput: isInteractiveApprovalTool,
80002
80292
  treatAskAsDeny: false,
80003
80293
  requireArgsForAutoApprove: true,
@@ -80005,7 +80295,8 @@ async function handleApprovalStop(params) {
80005
80295
  workingDirectory: turnWorkingDirectory,
80006
80296
  permissionModeState: turnPermissionModeState
80007
80297
  });
80008
- const lastNeedsUserInputToolCallIds = needsUserInput.map((ac) => ac.approval.toolCallId);
80298
+ let pendingNeedsUserInput = [...needsUserInput];
80299
+ let lastNeedsUserInputToolCallIds = pendingNeedsUserInput.map((ac) => ac.approval.toolCallId);
80009
80300
  let lastExecutionResults = null;
80010
80301
  let lastExecutingToolCallIds = [];
80011
80302
  const shouldInterrupt = () => abortController.signal.aborted || runtime.cancelRequested;
@@ -80044,16 +80335,15 @@ async function handleApprovalStop(params) {
80044
80335
  if (shouldInterrupt()) {
80045
80336
  return interruptTermination();
80046
80337
  }
80047
- if (needsUserInput.length > 0) {
80338
+ if (pendingNeedsUserInput.length > 0) {
80048
80339
  if (shouldInterrupt()) {
80049
80340
  return interruptTermination();
80050
80341
  }
80051
- runtime.lastStopReason = "requires_approval";
80052
- setLoopStatus(runtime, "WAITING_ON_APPROVAL", {
80053
- agent_id: agentId,
80054
- conversation_id: conversationId
80055
- });
80056
- for (const ac of needsUserInput) {
80342
+ while (pendingNeedsUserInput.length > 0) {
80343
+ const ac = pendingNeedsUserInput.shift();
80344
+ if (!ac) {
80345
+ break;
80346
+ }
80057
80347
  if (shouldInterrupt()) {
80058
80348
  return interruptTermination();
80059
80349
  }
@@ -80070,7 +80360,7 @@ async function handleApprovalStop(params) {
80070
80360
  tool_name: ac.approval.toolName,
80071
80361
  input: ac.parsedArgs,
80072
80362
  tool_call_id: ac.approval.toolCallId,
80073
- permission_suggestions: [],
80363
+ ...buildApprovalSuggestionPayload(ac.context),
80074
80364
  blocked_path: null,
80075
80365
  ...diffs.length > 0 ? { diffs } : {}
80076
80366
  },
@@ -80092,6 +80382,11 @@ async function handleApprovalStop(params) {
80092
80382
  if ("decision" in responseBody) {
80093
80383
  const response = responseBody.decision;
80094
80384
  if (response.behavior === "allow") {
80385
+ const savedSuggestions = await applySuggestedPermissionsForApproval({
80386
+ decision: response,
80387
+ context: ac.context,
80388
+ workingDirectory: turnWorkingDirectory
80389
+ });
80095
80390
  const finalApproval = response.updated_input ? {
80096
80391
  ...ac.approval,
80097
80392
  toolArgs: JSON.stringify(response.updated_input)
@@ -80101,6 +80396,26 @@ async function handleApprovalStop(params) {
80101
80396
  approval: finalApproval,
80102
80397
  reason: response.message
80103
80398
  });
80399
+ if (savedSuggestions && pendingNeedsUserInput.length > 0) {
80400
+ const reclassified = await classifyApprovalsWithSuggestions(pendingNeedsUserInput.map((entry) => entry.approval), {
80401
+ alwaysRequiresUserInput: isInteractiveApprovalTool,
80402
+ treatAskAsDeny: false,
80403
+ requireArgsForAutoApprove: true,
80404
+ missingNameReason: "Tool call incomplete - missing name",
80405
+ workingDirectory: turnWorkingDirectory,
80406
+ permissionModeState: turnPermissionModeState
80407
+ });
80408
+ decisions.push(...reclassified.autoAllowed.map((entry) => ({
80409
+ type: "approve",
80410
+ approval: entry.approval
80411
+ })), ...reclassified.autoDenied.map((entry) => ({
80412
+ type: "deny",
80413
+ approval: entry.approval,
80414
+ reason: entry.denyReason || entry.permission.reason || "Permission denied"
80415
+ })));
80416
+ pendingNeedsUserInput = [...reclassified.needsUserInput];
80417
+ lastNeedsUserInputToolCallIds = pendingNeedsUserInput.map((entry) => entry.approval.toolCallId);
80418
+ }
80104
80419
  } else {
80105
80420
  decisions.push({
80106
80421
  type: "deny",
@@ -80248,8 +80563,8 @@ var init_turn_approval = __esm(async () => {
80248
80563
  init_skill_injection();
80249
80564
  await __promiseAll([
80250
80565
  init_approval_execution(),
80251
- init_approvalClassification(),
80252
80566
  init_approval(),
80567
+ init_approval_suggestions(),
80253
80568
  init_interrupts(),
80254
80569
  init_protocol_outbound(),
80255
80570
  init_queue(),
@@ -80659,7 +80974,8 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
80659
80974
  latestErrorText
80660
80975
  })) {
80661
80976
  postStopApprovalRecoveryRetries += 1;
80662
- emitStatusDelta(socket, runtime, {
80977
+ emitRecoverableStatusNotice(socket, runtime, {
80978
+ kind: "stale_approval_conflict_recovery",
80663
80979
  message: "Recovering from stale approval conflict after interrupted/reconnected turn",
80664
80980
  level: "warning",
80665
80981
  runId: lastRunId || undefined,
@@ -80755,7 +81071,8 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
80755
81071
  detail: errorDetail
80756
81072
  });
80757
81073
  const retryMessage = getRetryStatusMessage(errorDetail) || `LLM API error encountered, retrying (attempt ${attempt}/${LLM_API_ERROR_MAX_RETRIES})...`;
80758
- emitRetryDelta(socket, runtime, {
81074
+ emitRecoverableRetryNotice(socket, runtime, {
81075
+ kind: "transient_provider_retry",
80759
81076
  message: retryMessage,
80760
81077
  reason: "llm_api_error",
80761
81078
  attempt,
@@ -80955,6 +81272,7 @@ var init_turn = __esm(async () => {
80955
81272
  init_toolset(),
80956
81273
  init_interrupts(),
80957
81274
  init_protocol_outbound(),
81275
+ init_recoverable_notices(),
80958
81276
  init_recovery(),
80959
81277
  init_send(),
80960
81278
  init_turn_approval()
@@ -81278,7 +81596,7 @@ function buildLoopStatus(runtime, params) {
81278
81596
  const conversationRuntime = getConversationRuntime(listener, scopedAgentId, scopedConversationId);
81279
81597
  const interruptedCacheActive = hasInterruptedCacheForScope(listener, scope);
81280
81598
  const recovered = getRecoveredApprovalStateForScope(listener, scope);
81281
- 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";
81599
+ 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";
81282
81600
  return {
81283
81601
  status,
81284
81602
  active_run_ids: interruptedCacheActive && !conversationRuntime?.isProcessing ? [] : conversationRuntime?.activeRunId ? [conversationRuntime.activeRunId] : []
@@ -81440,9 +81758,6 @@ function resolveSubagentScopeForSnapshot(runtime, scope) {
81440
81758
  function buildSubagentSnapshot(runtime, scope) {
81441
81759
  const runtimeScope = resolveSubagentScopeForSnapshot(runtime, scope);
81442
81760
  return getSubagents().filter((a) => {
81443
- if (a.status !== "pending" && a.status !== "running") {
81444
- return false;
81445
- }
81446
81761
  if (a.silent && a.isBackground !== true) {
81447
81762
  return false;
81448
81763
  }
@@ -85178,7 +85493,7 @@ var init_skills2 = __esm(() => {
85178
85493
  var exports_fs = {};
85179
85494
  __export(exports_fs, {
85180
85495
  writeJsonFile: () => writeJsonFile,
85181
- writeFile: () => writeFile6,
85496
+ writeFile: () => writeFile7,
85182
85497
  readJsonFile: () => readJsonFile,
85183
85498
  readFile: () => readFile9,
85184
85499
  mkdir: () => mkdir6,
@@ -85194,7 +85509,7 @@ import { dirname as dirname13 } from "node:path";
85194
85509
  async function readFile9(path23) {
85195
85510
  return fsReadFileSync2(path23, { encoding: "utf-8" });
85196
85511
  }
85197
- async function writeFile6(path23, content) {
85512
+ async function writeFile7(path23, content) {
85198
85513
  const dir = dirname13(path23);
85199
85514
  if (!existsSync25(dir)) {
85200
85515
  mkdirSync17(dir, { recursive: true });
@@ -85215,7 +85530,7 @@ async function writeJsonFile(path23, data, options) {
85215
85530
  const indent = options?.indent ?? 2;
85216
85531
  const content = `${JSON.stringify(data, null, indent)}
85217
85532
  `;
85218
- await writeFile6(path23, content);
85533
+ await writeFile7(path23, content);
85219
85534
  }
85220
85535
  var init_fs2 = () => {};
85221
85536
 
@@ -85240,7 +85555,7 @@ async function queryTerminalBackground(timeoutMs = 100) {
85240
85555
  }
85241
85556
  const wasRaw = process.stdin.isRaw;
85242
85557
  const wasFlowing = process.stdin.readableFlowing;
85243
- return new Promise((resolve25) => {
85558
+ return new Promise((resolve26) => {
85244
85559
  let response = "";
85245
85560
  let resolved = false;
85246
85561
  const cleanup = () => {
@@ -85257,7 +85572,7 @@ async function queryTerminalBackground(timeoutMs = 100) {
85257
85572
  };
85258
85573
  const timeout = setTimeout(() => {
85259
85574
  cleanup();
85260
- resolve25(null);
85575
+ resolve26(null);
85261
85576
  }, timeoutMs);
85262
85577
  const onData = (data) => {
85263
85578
  response += data.toString();
@@ -85267,7 +85582,7 @@ async function queryTerminalBackground(timeoutMs = 100) {
85267
85582
  if (match3) {
85268
85583
  clearTimeout(timeout);
85269
85584
  cleanup();
85270
- resolve25({
85585
+ resolve26({
85271
85586
  r: parseHexComponent(match3[1] ?? "0"),
85272
85587
  g: parseHexComponent(match3[2] ?? "0"),
85273
85588
  b: parseHexComponent(match3[3] ?? "0")
@@ -85282,7 +85597,7 @@ async function queryTerminalBackground(timeoutMs = 100) {
85282
85597
  } catch {
85283
85598
  clearTimeout(timeout);
85284
85599
  cleanup();
85285
- resolve25(null);
85600
+ resolve26(null);
85286
85601
  }
85287
85602
  });
85288
85603
  }
@@ -86491,10 +86806,10 @@ __export(exports_setup, {
86491
86806
  runSetup: () => runSetup
86492
86807
  });
86493
86808
  async function runSetup() {
86494
- return new Promise((resolve26) => {
86809
+ return new Promise((resolve27) => {
86495
86810
  const { waitUntilExit } = render_default(import_react32.default.createElement(SetupUI, {
86496
86811
  onComplete: () => {
86497
- resolve26();
86812
+ resolve27();
86498
86813
  }
86499
86814
  }));
86500
86815
  waitUntilExit().catch((error) => {
@@ -86969,6 +87284,20 @@ function validateRegistryHandleOrThrow2(handle) {
86969
87284
  }
86970
87285
  }
86971
87286
 
87287
+ // src/runtime-context.ts
87288
+ import { AsyncLocalStorage } from "node:async_hooks";
87289
+ function getCurrentWorkingDirectory() {
87290
+ const workingDirectory = runtimeContextStorage.getStore()?.workingDirectory;
87291
+ if (typeof workingDirectory === "string" && workingDirectory.length > 0) {
87292
+ return workingDirectory;
87293
+ }
87294
+ return process.env.USER_CWD || process.cwd();
87295
+ }
87296
+ var runtimeContextStorage;
87297
+ var init_runtime_context = __esm(() => {
87298
+ runtimeContextStorage = new AsyncLocalStorage;
87299
+ });
87300
+
86972
87301
  // src/agent/github-utils.ts
86973
87302
  var exports_github_utils = {};
86974
87303
  __export(exports_github_utils, {
@@ -87009,11 +87338,11 @@ __export(exports_import, {
87009
87338
  extractSkillsFromAf: () => extractSkillsFromAf
87010
87339
  });
87011
87340
  import { createReadStream } from "node:fs";
87012
- import { chmod, mkdir as mkdir7, readFile as readFile10, writeFile as writeFile7 } from "node:fs/promises";
87013
- import { dirname as dirname14, resolve as resolve26 } from "node:path";
87341
+ import { chmod, mkdir as mkdir7, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
87342
+ import { dirname as dirname14, resolve as resolve27 } from "node:path";
87014
87343
  async function importAgentFromFile(options) {
87015
87344
  const client = await getClient();
87016
- const resolvedPath = resolve26(options.filePath);
87345
+ const resolvedPath = resolve27(options.filePath);
87017
87346
  const file = createReadStream(resolvedPath);
87018
87347
  const importResponse = await client.agents.importFile({
87019
87348
  file,
@@ -87048,7 +87377,7 @@ async function extractSkillsFromAf(afPath, destDir) {
87048
87377
  return [];
87049
87378
  }
87050
87379
  for (const skill2 of afData.skills) {
87051
- const skillDir = resolve26(destDir, skill2.name);
87380
+ const skillDir = resolve27(destDir, skill2.name);
87052
87381
  await mkdir7(skillDir, { recursive: true });
87053
87382
  if (skill2.files) {
87054
87383
  await writeSkillFiles(skillDir, skill2.files);
@@ -87068,9 +87397,9 @@ async function writeSkillFiles(skillDir, files) {
87068
87397
  }
87069
87398
  }
87070
87399
  async function writeSkillFile(skillDir, filePath, content) {
87071
- const fullPath = resolve26(skillDir, filePath);
87400
+ const fullPath = resolve27(skillDir, filePath);
87072
87401
  await mkdir7(dirname14(fullPath), { recursive: true });
87073
- await writeFile7(fullPath, content, "utf-8");
87402
+ await writeFile8(fullPath, content, "utf-8");
87074
87403
  const isScript = filePath.startsWith("scripts/") || content.trimStart().startsWith("#!");
87075
87404
  if (isScript) {
87076
87405
  try {
@@ -87123,7 +87452,7 @@ function parseRegistryHandle(handle) {
87123
87452
  async function importAgentFromRegistry(options) {
87124
87453
  const { tmpdir: tmpdir4 } = await import("node:os");
87125
87454
  const { join: join35 } = await import("node:path");
87126
- const { writeFile: writeFile8, unlink: unlink3 } = await import("node:fs/promises");
87455
+ const { writeFile: writeFile9, unlink: unlink3 } = await import("node:fs/promises");
87127
87456
  const { author, name } = parseRegistryHandle(options.handle);
87128
87457
  const rawUrl = `https://raw.githubusercontent.com/${AGENT_REGISTRY_OWNER}/${AGENT_REGISTRY_REPO}/refs/heads/${AGENT_REGISTRY_BRANCH}/agents/@${author}/${name}/${name}.af`;
87129
87458
  const response = await fetch(rawUrl);
@@ -87135,7 +87464,7 @@ async function importAgentFromRegistry(options) {
87135
87464
  }
87136
87465
  const afContent = await response.text();
87137
87466
  const tempPath = join35(tmpdir4(), `letta-import-${author}-${name}-${Date.now()}.af`);
87138
- await writeFile8(tempPath, afContent, "utf-8");
87467
+ await writeFile9(tempPath, afContent, "utf-8");
87139
87468
  try {
87140
87469
  const result = await importAgentFromFile({
87141
87470
  filePath: tempPath,
@@ -87366,7 +87695,7 @@ async function prepareHeadlessToolExecutionContext(params) {
87366
87695
  agentId: params.agentId,
87367
87696
  conversationId: params.conversationId,
87368
87697
  overrideModel: params.overrideModel,
87369
- workingDirectory: process.env.USER_CWD || process.cwd(),
87698
+ workingDirectory: getCurrentWorkingDirectory(),
87370
87699
  exclude: ["AskUserQuestion"]
87371
87700
  });
87372
87701
  return {
@@ -87374,6 +87703,20 @@ async function prepareHeadlessToolExecutionContext(params) {
87374
87703
  availableTools: preparedToolContext.preparedToolContext.clientTools.map((tool) => tool.name)
87375
87704
  };
87376
87705
  }
87706
+ async function flushAndExit(code) {
87707
+ const flushWritable = (stream2) => new Promise((resolve28) => {
87708
+ if (stream2.destroyed || stream2.writableEnded) {
87709
+ resolve28();
87710
+ return;
87711
+ }
87712
+ stream2.write("", () => resolve28());
87713
+ });
87714
+ await Promise.allSettled([
87715
+ flushWritable(process.stdout),
87716
+ flushWritable(process.stderr)
87717
+ ]);
87718
+ process.exit(code);
87719
+ }
87377
87720
  async function handleHeadlessCommand(parsedArgs, model, skillsDirectoryOverride, skillSourcesOverride, systemInfoReminderEnabledOverride) {
87378
87721
  const { values, positionals } = parsedArgs;
87379
87722
  telemetry.setSurface("headless");
@@ -87444,6 +87787,7 @@ In headless mode, use:
87444
87787
  let forceNewConversation = values.new ?? false;
87445
87788
  const fromAgentId = values["from-agent"];
87446
87789
  let agent = null;
87790
+ let autoEnableMemfsForFreshAgent = false;
87447
87791
  let specifiedAgentId = values.agent;
87448
87792
  const specifiedAgentName = values.name;
87449
87793
  let specifiedConversationId = values.conversation;
@@ -87726,7 +88070,7 @@ In headless mode, use:
87726
88070
  }
87727
88071
  if (!agent && forceNew) {
87728
88072
  const updateArgs = getModelUpdateArgs2(model);
87729
- const { isLettaCloud: isLettaCloud2, enableMemfsIfCloud: enableMemfsIfCloud2 } = await Promise.resolve().then(() => (init_memoryFilesystem(), exports_memoryFilesystem));
88073
+ const { isLettaCloud: isLettaCloud2 } = await Promise.resolve().then(() => (init_memoryFilesystem(), exports_memoryFilesystem));
87730
88074
  const willAutoEnableMemfs = shouldAutoEnableMemfsForNewAgent && await isLettaCloud2();
87731
88075
  const effectiveMemoryMode = requestedMemoryPromptMode ?? (willAutoEnableMemfs ? "memfs" : undefined);
87732
88076
  const createOptions = {
@@ -87746,13 +88090,11 @@ In headless mode, use:
87746
88090
  };
87747
88091
  const result = await createAgent(createOptions);
87748
88092
  agent = result.agent;
87749
- if (willAutoEnableMemfs) {
87750
- await enableMemfsIfCloud2(agent.id);
87751
- }
88093
+ autoEnableMemfsForFreshAgent = willAutoEnableMemfs;
87752
88094
  }
87753
88095
  if (!agent) {
87754
88096
  await settingsManager.loadLocalProjectSettings();
87755
- const localAgentId = settingsManager.getLocalLastAgentId(process.cwd());
88097
+ const localAgentId = settingsManager.getLocalLastAgentId(getCurrentWorkingDirectory());
87756
88098
  if (localAgentId) {
87757
88099
  try {
87758
88100
  agent = await client.agents.retrieve(localAgentId);
@@ -87807,13 +88149,14 @@ In headless mode, use:
87807
88149
  let conversationId;
87808
88150
  let effectiveReflectionSettings;
87809
88151
  const isSubagent = process.env.LETTA_CODE_AGENT_ROLE === "subagent";
88152
+ const startupMemfsFlag = autoEnableMemfsForFreshAgent ? true : memfsFlag;
87810
88153
  let memfsBgPromise;
87811
88154
  const secretsAgentId = agent?.id;
87812
88155
  const secretsInitPromise = secretsAgentId ? init_secretsStore().then(() => exports_secretsStore).then(({ initSecretsFromServer: initSecretsFromServer2 }) => initSecretsFromServer2(secretsAgentId)) : Promise.resolve();
87813
88156
  if (memfsStartupPolicy === "skip") {
87814
88157
  try {
87815
88158
  const { applyMemfsFlags: applyMemfsFlags2 } = await Promise.resolve().then(() => (init_memoryFilesystem(), exports_memoryFilesystem));
87816
- await applyMemfsFlags2(agent.id, memfsFlag, noMemfsFlag, {
88159
+ await applyMemfsFlags2(agent.id, startupMemfsFlag, noMemfsFlag, {
87817
88160
  pullOnExistingRepo: false,
87818
88161
  agentTags: agent.tags,
87819
88162
  skipPromptUpdate: forceNew
@@ -87825,7 +88168,7 @@ In headless mode, use:
87825
88168
  }
87826
88169
  } else if (memfsStartupPolicy === "background") {
87827
88170
  const { applyMemfsFlags: applyMemfsFlags2 } = await Promise.resolve().then(() => (init_memoryFilesystem(), exports_memoryFilesystem));
87828
- memfsBgPromise = applyMemfsFlags2(agent.id, memfsFlag, noMemfsFlag, {
88171
+ memfsBgPromise = applyMemfsFlags2(agent.id, startupMemfsFlag, noMemfsFlag, {
87829
88172
  pullOnExistingRepo: true,
87830
88173
  agentTags: agent.tags,
87831
88174
  skipPromptUpdate: forceNew
@@ -87836,7 +88179,7 @@ In headless mode, use:
87836
88179
  } else {
87837
88180
  try {
87838
88181
  const { applyMemfsFlags: applyMemfsFlags2 } = await Promise.resolve().then(() => (init_memoryFilesystem(), exports_memoryFilesystem));
87839
- const memfsResult = await applyMemfsFlags2(agent.id, memfsFlag, noMemfsFlag, {
88182
+ const memfsResult = await applyMemfsFlags2(agent.id, startupMemfsFlag, noMemfsFlag, {
87840
88183
  pullOnExistingRepo: true,
87841
88184
  agentTags: agent.tags,
87842
88185
  skipPromptUpdate: forceNew
@@ -87939,7 +88282,7 @@ In headless mode, use:
87939
88282
  }
87940
88283
  markMilestone("HEADLESS_CONVERSATION_READY");
87941
88284
  setConversationId2(conversationId);
87942
- if (!isSubagent) {
88285
+ if (shouldPersistSessionState()) {
87943
88286
  await settingsManager.loadLocalProjectSettings();
87944
88287
  settingsManager.persistSession(agent.id, conversationId);
87945
88288
  }
@@ -87978,7 +88321,7 @@ In headless mode, use:
87978
88321
  conversation_id: conversationId,
87979
88322
  model: agent.llm_config?.model ?? "",
87980
88323
  tools: availableTools,
87981
- cwd: process.cwd(),
88324
+ cwd: getCurrentWorkingDirectory(),
87982
88325
  mcp_servers: [],
87983
88326
  permission_mode: "",
87984
88327
  slash_commands: [],
@@ -88139,6 +88482,7 @@ ${SYSTEM_REMINDER_CLOSE}
88139
88482
  },
88140
88483
  state: sharedReminderState,
88141
88484
  sessionContextReminderEnabled: systemInfoReminderEnabled,
88485
+ workingDirectory: getCurrentWorkingDirectory(),
88142
88486
  reflectionSettings: effectiveReflectionSettings,
88143
88487
  skillSources: resolvedSkillSources,
88144
88488
  resolvePlanModeReminder: async () => {
@@ -88312,7 +88656,7 @@ ${loadedContents.join(`
88312
88656
  } else {
88313
88657
  console.error(`Conversation is busy, waiting ${Math.round(retryDelayMs / 1000)}s and retrying...`);
88314
88658
  }
88315
- await new Promise((resolve27) => setTimeout(resolve27, retryDelayMs));
88659
+ await new Promise((resolve28) => setTimeout(resolve28, retryDelayMs));
88316
88660
  continue;
88317
88661
  }
88318
88662
  }
@@ -88361,7 +88705,7 @@ ${loadedContents.join(`
88361
88705
  const delaySeconds = Math.round(delayMs / 1000);
88362
88706
  console.error(`Transient API error before streaming (attempt ${attempt} of ${LLM_API_ERROR_MAX_RETRIES2}), retrying in ${delaySeconds}s...`);
88363
88707
  }
88364
- await new Promise((resolve27) => setTimeout(resolve27, delayMs));
88708
+ await new Promise((resolve28) => setTimeout(resolve28, delayMs));
88365
88709
  conversationBusyRetries = 0;
88366
88710
  continue;
88367
88711
  }
@@ -88600,7 +88944,7 @@ ${loadedContents.join(`
88600
88944
  const delaySeconds = Math.round(delayMs / 1000);
88601
88945
  console.error(`LLM API error encountered (attempt ${attempt} of ${LLM_API_ERROR_MAX_RETRIES2}), retrying in ${delaySeconds}s...`);
88602
88946
  }
88603
- await new Promise((resolve27) => setTimeout(resolve27, delayMs));
88947
+ await new Promise((resolve28) => setTimeout(resolve28, delayMs));
88604
88948
  refreshCurrentInputOtids();
88605
88949
  continue;
88606
88950
  }
@@ -88686,7 +89030,7 @@ ${loadedContents.join(`
88686
89030
  } else {
88687
89031
  console.error(`Empty LLM response, retrying (attempt ${attempt} of ${EMPTY_RESPONSE_MAX_RETRIES2})...`);
88688
89032
  }
88689
- await new Promise((resolve27) => setTimeout(resolve27, delayMs));
89033
+ await new Promise((resolve28) => setTimeout(resolve28, delayMs));
88690
89034
  refreshCurrentInputOtids();
88691
89035
  continue;
88692
89036
  }
@@ -88714,7 +89058,7 @@ ${loadedContents.join(`
88714
89058
  const delaySeconds = Math.round(delayMs / 1000);
88715
89059
  console.error(`LLM API error encountered (attempt ${attempt} of ${LLM_API_ERROR_MAX_RETRIES2}), retrying in ${delaySeconds}s...`);
88716
89060
  }
88717
- await new Promise((resolve27) => setTimeout(resolve27, delayMs));
89061
+ await new Promise((resolve28) => setTimeout(resolve28, delayMs));
88718
89062
  refreshCurrentInputOtids();
88719
89063
  continue;
88720
89064
  }
@@ -88843,6 +89187,7 @@ ${loadedContents.join(`
88843
89187
  }
88844
89188
  markMilestone("HEADLESS_COMPLETE");
88845
89189
  reportAllMilestones();
89190
+ await flushAndExit(0);
88846
89191
  }
88847
89192
  async function runBidirectionalMode(agent, conversationId, client, _outputFormat, includePartialMessages, availableTools, skillSources, systemInfoReminderEnabled, reflectionSettings) {
88848
89193
  const sessionId = agent.id;
@@ -88855,7 +89200,7 @@ async function runBidirectionalMode(agent, conversationId, client, _outputFormat
88855
89200
  conversation_id: conversationId,
88856
89201
  model: agent.llm_config?.model,
88857
89202
  tools: availableTools,
88858
- cwd: process.cwd(),
89203
+ cwd: getCurrentWorkingDirectory(),
88859
89204
  memfs_enabled: settingsManager.isMemfsEnabled(agent.id),
88860
89205
  skill_sources: skillSources,
88861
89206
  system_info_reminder_enabled: systemInfoReminderEnabled,
@@ -89047,9 +89392,9 @@ async function runBidirectionalMode(agent, conversationId, client, _outputFormat
89047
89392
  const syntheticUserLine = serializeQueuedMessageAsUserLine(queuedMessage);
89048
89393
  maybeNotifyBlocked(syntheticUserLine);
89049
89394
  if (lineResolver) {
89050
- const resolve27 = lineResolver;
89395
+ const resolve28 = lineResolver;
89051
89396
  lineResolver = null;
89052
- resolve27(syntheticUserLine);
89397
+ resolve28(syntheticUserLine);
89053
89398
  return;
89054
89399
  }
89055
89400
  lineQueue.push(syntheticUserLine);
@@ -89057,9 +89402,9 @@ async function runBidirectionalMode(agent, conversationId, client, _outputFormat
89057
89402
  rl.on("line", (line) => {
89058
89403
  maybeNotifyBlocked(line);
89059
89404
  if (lineResolver) {
89060
- const resolve27 = lineResolver;
89405
+ const resolve28 = lineResolver;
89061
89406
  lineResolver = null;
89062
- resolve27(line);
89407
+ resolve28(line);
89063
89408
  } else {
89064
89409
  lineQueue.push(line);
89065
89410
  }
@@ -89068,17 +89413,17 @@ async function runBidirectionalMode(agent, conversationId, client, _outputFormat
89068
89413
  setMessageQueueAdder(null);
89069
89414
  msgQueueRuntime.clear("shutdown");
89070
89415
  if (lineResolver) {
89071
- const resolve27 = lineResolver;
89416
+ const resolve28 = lineResolver;
89072
89417
  lineResolver = null;
89073
- resolve27(null);
89418
+ resolve28(null);
89074
89419
  }
89075
89420
  });
89076
89421
  async function getNextLine() {
89077
89422
  if (lineQueue.length > 0) {
89078
89423
  return lineQueue.shift() ?? null;
89079
89424
  }
89080
- return new Promise((resolve27) => {
89081
- lineResolver = resolve27;
89425
+ return new Promise((resolve28) => {
89426
+ lineResolver = resolve28;
89082
89427
  });
89083
89428
  }
89084
89429
  async function requestPermission(toolCallId, toolName, toolInput) {
@@ -89497,6 +89842,7 @@ async function runBidirectionalMode(agent, conversationId, client, _outputFormat
89497
89842
  },
89498
89843
  state: sharedReminderState,
89499
89844
  sessionContextReminderEnabled: systemInfoReminderEnabled,
89845
+ workingDirectory: getCurrentWorkingDirectory(),
89500
89846
  reflectionSettings,
89501
89847
  skillSources,
89502
89848
  resolvePlanModeReminder: async () => {
@@ -89581,7 +89927,7 @@ async function runBidirectionalMode(agent, conversationId, client, _outputFormat
89581
89927
  uuid: `retry-bidir-${randomUUID8()}`
89582
89928
  };
89583
89929
  console.log(JSON.stringify(retryMsg));
89584
- await new Promise((resolve27) => setTimeout(resolve27, delayMs));
89930
+ await new Promise((resolve28) => setTimeout(resolve28, delayMs));
89585
89931
  continue;
89586
89932
  }
89587
89933
  throw preStreamError;
@@ -89822,6 +90168,7 @@ var init_headless = __esm(async () => {
89822
90168
  init_constants();
89823
90169
  init_diffPreview();
89824
90170
  init_queueRuntime();
90171
+ init_runtime_context();
89825
90172
  init_interactivePolicy();
89826
90173
  init_debug();
89827
90174
  init_timing();
@@ -89876,10 +90223,10 @@ async function detectAndEnableKittyProtocol() {
89876
90223
  detectionComplete = true;
89877
90224
  return;
89878
90225
  }
89879
- return new Promise((resolve27) => {
90226
+ return new Promise((resolve28) => {
89880
90227
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
89881
90228
  detectionComplete = true;
89882
- resolve27();
90229
+ resolve28();
89883
90230
  return;
89884
90231
  }
89885
90232
  const originalRawMode = process.stdin.isRaw;
@@ -89912,7 +90259,7 @@ async function detectAndEnableKittyProtocol() {
89912
90259
  console.error("[kitty] protocol query unsupported; enabled anyway (best-effort)");
89913
90260
  }
89914
90261
  detectionComplete = true;
89915
- resolve27();
90262
+ resolve28();
89916
90263
  };
89917
90264
  const handleData = (data) => {
89918
90265
  if (timeoutId === undefined) {
@@ -112098,7 +112445,7 @@ var init_pasteRegistry = __esm(() => {
112098
112445
  import { execFileSync as execFileSync3 } from "node:child_process";
112099
112446
  import { existsSync as existsSync29, readFileSync as readFileSync16, statSync as statSync10, unlinkSync as unlinkSync10 } from "node:fs";
112100
112447
  import { tmpdir as tmpdir4 } from "node:os";
112101
- import { basename as basename4, extname as extname5, isAbsolute as isAbsolute18, join as join38, resolve as resolve27 } from "node:path";
112448
+ import { basename as basename4, extname as extname5, isAbsolute as isAbsolute18, join as join38, resolve as resolve28 } from "node:path";
112102
112449
  function countLines2(text) {
112103
112450
  return (text.match(/\r\n|\r|\n/g) || []).length + 1;
112104
112451
  }
@@ -112146,7 +112493,7 @@ function translatePasteForImages(paste) {
112146
112493
  } catch {}
112147
112494
  }
112148
112495
  if (!isAbsolute18(filePath))
112149
- filePath = resolve27(process.cwd(), filePath);
112496
+ filePath = resolve28(process.cwd(), filePath);
112150
112497
  const ext3 = extname5(filePath || "").toLowerCase();
112151
112498
  if (IMAGE_EXTS.has(ext3) && existsSync29(filePath) && statSync10(filePath).isFile()) {
112152
112499
  const buf = readFileSync16(filePath);
@@ -113292,9 +113639,16 @@ var init_registry = __esm(async () => {
113292
113639
  }
113293
113640
  },
113294
113641
  "/reflect": {
113295
- desc: "Launch a background reflection agent to update memory",
113642
+ desc: "Launch reflection (/reflect [transcript_file])",
113643
+ args: "[transcript_file]",
113296
113644
  order: 50,
113297
- noArgs: true,
113645
+ handler: () => {
113646
+ return "Launching reflection agent...";
113647
+ }
113648
+ },
113649
+ "/reflection": {
113650
+ desc: "Alias for /reflect",
113651
+ args: "[transcript_file]",
113298
113652
  handler: () => {
113299
113653
  return "Launching reflection agent...";
113300
113654
  }
@@ -113654,14 +114008,6 @@ Location: ${keybindingsPath}`;
113654
114008
  return "Clearing credentials...";
113655
114009
  }
113656
114010
  },
113657
- "/empanada": {
113658
- desc: "Order empanadas from Empanada Empire",
113659
- order: 44.5,
113660
- args: "[address]",
113661
- handler: () => {
113662
- return "Checking Empanada Empire...";
113663
- }
113664
- },
113665
114011
  "/ralph": {
113666
114012
  desc: 'Start Ralph Wiggum loop (/ralph [prompt] [--completion-promise "X"] [--max-iterations N])',
113667
114013
  order: 45,
@@ -114166,11 +114512,11 @@ var init_HelpDialog = __esm(async () => {
114166
114512
 
114167
114513
  // src/hooks/writer.ts
114168
114514
  import { homedir as homedir31 } from "node:os";
114169
- import { resolve as resolve28 } from "node:path";
114515
+ import { resolve as resolve29 } from "node:path";
114170
114516
  function isProjectSettingsPathCollidingWithGlobal2(workingDirectory) {
114171
114517
  const home = process.env.HOME || homedir31();
114172
- const globalSettingsPath = resolve28(home, ".letta", "settings.json");
114173
- const projectSettingsPath = resolve28(workingDirectory, ".letta", "settings.json");
114518
+ const globalSettingsPath = resolve29(home, ".letta", "settings.json");
114519
+ const projectSettingsPath = resolve29(workingDirectory, ".letta", "settings.json");
114174
114520
  return globalSettingsPath === projectSettingsPath;
114175
114521
  }
114176
114522
  function loadHooksFromLocation(location, workingDirectory = process.cwd()) {
@@ -116808,7 +117154,7 @@ var init_AgentInfoBar = __esm(async () => {
116808
117154
 
116809
117155
  // src/cli/helpers/fileSearch.ts
116810
117156
  import { readdirSync as readdirSync13, statSync as statSync11 } from "node:fs";
116811
- import { join as join41, relative as relative15, resolve as resolve29 } from "node:path";
117157
+ import { join as join41, relative as relative15, resolve as resolve30 } from "node:path";
116812
117158
  function searchDirectoryRecursive(dir, pattern, maxResults = 200, results = [], depth = 0, maxDepth = 10, lowerPattern = pattern.toLowerCase()) {
116813
117159
  if (results.length >= maxResults || depth >= maxDepth) {
116814
117160
  return results;
@@ -116851,7 +117197,7 @@ async function searchFiles(query, deep = false) {
116851
117197
  const dirPart = query.slice(0, lastSlashIndex);
116852
117198
  const pattern = query.slice(lastSlashIndex + 1);
116853
117199
  try {
116854
- const resolvedDir = resolve29(getIndexRoot(), dirPart);
117200
+ const resolvedDir = resolve30(getIndexRoot(), dirPart);
116855
117201
  try {
116856
117202
  statSync11(resolvedDir);
116857
117203
  searchDir = resolvedDir;
@@ -131650,7 +131996,7 @@ async function executeStatusLineCommand(command, payload, options) {
131650
131996
  };
131651
131997
  }
131652
131998
  function runWithLauncher(launcher, inputJson, timeout, signal, workingDirectory, startTime) {
131653
- return new Promise((resolve30, reject) => {
131999
+ return new Promise((resolve31, reject) => {
131654
132000
  const [executable, ...args] = launcher;
131655
132001
  if (!executable) {
131656
132002
  reject(new Error("Empty launcher"));
@@ -131663,7 +132009,7 @@ function runWithLauncher(launcher, inputJson, timeout, signal, workingDirectory,
131663
132009
  const safeResolve = (result) => {
131664
132010
  if (!resolved) {
131665
132011
  resolved = true;
131666
- resolve30(result);
132012
+ resolve31(result);
131667
132013
  }
131668
132014
  };
131669
132015
  let child;
@@ -132840,14 +133186,14 @@ __export(exports_export, {
132840
133186
  packageSkills: () => packageSkills
132841
133187
  });
132842
133188
  import { readdir as readdir10, readFile as readFile13 } from "node:fs/promises";
132843
- import { relative as relative16, resolve as resolve30 } from "node:path";
133189
+ import { relative as relative16, resolve as resolve31 } from "node:path";
132844
133190
  async function packageSkills(agentId, skillsDir) {
132845
133191
  const skills = [];
132846
133192
  const skillNames = new Set;
132847
133193
  const dirsToCheck = skillsDir ? [skillsDir] : [
132848
133194
  agentId && getAgentSkillsDir(agentId),
132849
- resolve30(process.cwd(), ".skills"),
132850
- resolve30(process.env.HOME || "~", ".letta", "skills")
133195
+ resolve31(process.cwd(), ".skills"),
133196
+ resolve31(process.env.HOME || "~", ".letta", "skills")
132851
133197
  ].filter((dir) => Boolean(dir));
132852
133198
  for (const baseDir of dirsToCheck) {
132853
133199
  try {
@@ -132857,8 +133203,8 @@ async function packageSkills(agentId, skillsDir) {
132857
133203
  continue;
132858
133204
  if (skillNames.has(entry.name))
132859
133205
  continue;
132860
- const skillDir = resolve30(baseDir, entry.name);
132861
- const skillMdPath = resolve30(skillDir, "SKILL.md");
133206
+ const skillDir = resolve31(baseDir, entry.name);
133207
+ const skillMdPath = resolve31(skillDir, "SKILL.md");
132862
133208
  try {
132863
133209
  await readFile13(skillMdPath, "utf-8");
132864
133210
  } catch {
@@ -132888,7 +133234,7 @@ async function readSkillFiles(skillDir) {
132888
133234
  async function walk(dir) {
132889
133235
  const entries = await readdir10(dir, { withFileTypes: true });
132890
133236
  for (const entry of entries) {
132891
- const fullPath = resolve30(dir, entry.name);
133237
+ const fullPath = resolve31(dir, entry.name);
132892
133238
  if (entry.isDirectory()) {
132893
133239
  await walk(fullPath);
132894
133240
  } else {
@@ -135372,7 +135718,7 @@ ${newState.originalPrompt}`,
135372
135718
  cancelled = true;
135373
135719
  break;
135374
135720
  }
135375
- await new Promise((resolve31) => setTimeout(resolve31, 100));
135721
+ await new Promise((resolve32) => setTimeout(resolve32, 100));
135376
135722
  }
135377
135723
  buffersRef.current.byId.delete(statusId);
135378
135724
  buffersRef.current.order = buffersRef.current.order.filter((id) => id !== statusId);
@@ -135436,7 +135782,7 @@ ${newState.originalPrompt}`,
135436
135782
  cancelled = true;
135437
135783
  break;
135438
135784
  }
135439
- await new Promise((resolve31) => setTimeout(resolve31, 100));
135785
+ await new Promise((resolve32) => setTimeout(resolve32, 100));
135440
135786
  }
135441
135787
  if (retryStatusId) {
135442
135788
  buffersRef.current.byId.delete(retryStatusId);
@@ -136194,7 +136540,7 @@ ${feedback}
136194
136540
  });
136195
136541
  buffersRef.current.order.push(statusId);
136196
136542
  refreshDerived();
136197
- await new Promise((resolve31) => setTimeout(resolve31, delayMs));
136543
+ await new Promise((resolve32) => setTimeout(resolve32, delayMs));
136198
136544
  buffersRef.current.byId.delete(statusId);
136199
136545
  buffersRef.current.order = buffersRef.current.order.filter((id) => id !== statusId);
136200
136546
  refreshDerived();
@@ -136254,7 +136600,7 @@ ${feedback}
136254
136600
  cancelled = true;
136255
136601
  break;
136256
136602
  }
136257
- await new Promise((resolve31) => setTimeout(resolve31, 100));
136603
+ await new Promise((resolve32) => setTimeout(resolve32, 100));
136258
136604
  }
136259
136605
  if (retryStatusId) {
136260
136606
  buffersRef.current.byId.delete(retryStatusId);
@@ -143468,11 +143814,11 @@ __export(exports_import2, {
143468
143814
  extractSkillsFromAf: () => extractSkillsFromAf2
143469
143815
  });
143470
143816
  import { createReadStream as createReadStream2 } from "node:fs";
143471
- import { chmod as chmod2, mkdir as mkdir8, readFile as readFile14, writeFile as writeFile8 } from "node:fs/promises";
143472
- import { dirname as dirname19, resolve as resolve31 } from "node:path";
143817
+ import { chmod as chmod2, mkdir as mkdir8, readFile as readFile14, writeFile as writeFile9 } from "node:fs/promises";
143818
+ import { dirname as dirname19, resolve as resolve32 } from "node:path";
143473
143819
  async function importAgentFromFile2(options) {
143474
143820
  const client = await getClient();
143475
- const resolvedPath = resolve31(options.filePath);
143821
+ const resolvedPath = resolve32(options.filePath);
143476
143822
  const file = createReadStream2(resolvedPath);
143477
143823
  const importResponse = await client.agents.importFile({
143478
143824
  file,
@@ -143507,7 +143853,7 @@ async function extractSkillsFromAf2(afPath, destDir) {
143507
143853
  return [];
143508
143854
  }
143509
143855
  for (const skill2 of afData.skills) {
143510
- const skillDir = resolve31(destDir, skill2.name);
143856
+ const skillDir = resolve32(destDir, skill2.name);
143511
143857
  await mkdir8(skillDir, { recursive: true });
143512
143858
  if (skill2.files) {
143513
143859
  await writeSkillFiles2(skillDir, skill2.files);
@@ -143527,9 +143873,9 @@ async function writeSkillFiles2(skillDir, files) {
143527
143873
  }
143528
143874
  }
143529
143875
  async function writeSkillFile2(skillDir, filePath, content) {
143530
- const fullPath = resolve31(skillDir, filePath);
143876
+ const fullPath = resolve32(skillDir, filePath);
143531
143877
  await mkdir8(dirname19(fullPath), { recursive: true });
143532
- await writeFile8(fullPath, content, "utf-8");
143878
+ await writeFile9(fullPath, content, "utf-8");
143533
143879
  const isScript = filePath.startsWith("scripts/") || content.trimStart().startsWith("#!");
143534
143880
  if (isScript) {
143535
143881
  try {
@@ -143582,7 +143928,7 @@ function parseRegistryHandle2(handle) {
143582
143928
  async function importAgentFromRegistry2(options) {
143583
143929
  const { tmpdir: tmpdir7 } = await import("node:os");
143584
143930
  const { join: join50 } = await import("node:path");
143585
- const { writeFile: writeFile9, unlink: unlink3 } = await import("node:fs/promises");
143931
+ const { writeFile: writeFile10, unlink: unlink3 } = await import("node:fs/promises");
143586
143932
  const { author, name } = parseRegistryHandle2(options.handle);
143587
143933
  const rawUrl = `https://raw.githubusercontent.com/${AGENT_REGISTRY_OWNER2}/${AGENT_REGISTRY_REPO2}/refs/heads/${AGENT_REGISTRY_BRANCH2}/agents/@${author}/${name}/${name}.af`;
143588
143934
  const response = await fetch(rawUrl);
@@ -143594,7 +143940,7 @@ async function importAgentFromRegistry2(options) {
143594
143940
  }
143595
143941
  const afContent = await response.text();
143596
143942
  const tempPath = join50(tmpdir7(), `letta-import-${author}-${name}-${Date.now()}.af`);
143597
- await writeFile9(tempPath, afContent, "utf-8");
143943
+ await writeFile10(tempPath, afContent, "utf-8");
143598
143944
  try {
143599
143945
  const result = await importAgentFromFile2({
143600
143946
  filePath: tempPath,
@@ -148529,12 +148875,15 @@ async function runMemfsSubcommand(argv) {
148529
148875
 
148530
148876
  // src/cli/subcommands/messages.ts
148531
148877
  await init_client2();
148878
+ import { writeFile as writeFile6 } from "node:fs/promises";
148879
+ import { resolve as resolve23 } from "node:path";
148532
148880
  import { parseArgs as parseArgs8 } from "node:util";
148533
148881
  function printUsage5() {
148534
148882
  console.log(`
148535
148883
  Usage:
148536
148884
  letta messages search --query <text> [options]
148537
148885
  letta messages list [options]
148886
+ letta messages transcript --conversation <id> [options]
148538
148887
 
148539
148888
  Search options:
148540
148889
  --query <text> Search query (required)
@@ -148556,6 +148905,16 @@ List options:
148556
148905
  --start-date <date> Client-side filter: after this date (ISO format)
148557
148906
  --end-date <date> Client-side filter: before this date (ISO format)
148558
148907
 
148908
+ Transcript options:
148909
+ --conversation <id> Conversation ID to export (required)
148910
+ --conversation-id <id> Alias for --conversation
148911
+ --agent <id> Required when conversation is "default"
148912
+ --agent-id <id> Alias for --agent
148913
+ --limit <n> Page size while fetching (default: 100)
148914
+ --max-pages <n> Max pagination pages to fetch (default: 200)
148915
+ --out <path> Write transcript text to file
148916
+ --output <path> Alias for --out
148917
+
148559
148918
  Notes:
148560
148919
  - Output is JSON only.
148561
148920
  - Uses CLI auth; override with LETTA_API_KEY/LETTA_BASE_URL if needed.
@@ -148597,7 +148956,12 @@ var MESSAGES_OPTIONS = {
148597
148956
  "agent-id": { type: "string" },
148598
148957
  after: { type: "string" },
148599
148958
  before: { type: "string" },
148600
- order: { type: "string" }
148959
+ order: { type: "string" },
148960
+ conversation: { type: "string" },
148961
+ "conversation-id": { type: "string" },
148962
+ "max-pages": { type: "string" },
148963
+ out: { type: "string" },
148964
+ output: { type: "string" }
148601
148965
  };
148602
148966
  function parseMessagesArgs(argv) {
148603
148967
  return parseArgs8({
@@ -148624,6 +148988,115 @@ async function runMessagesSubcommand(argv) {
148624
148988
  }
148625
148989
  try {
148626
148990
  const client = await getClient();
148991
+ const renderText = (value) => {
148992
+ if (typeof value === "string")
148993
+ return value;
148994
+ if (!Array.isArray(value))
148995
+ return "";
148996
+ return value.map((part) => {
148997
+ if (part && typeof part === "object" && "type" in part && part.type === "text" && "text" in part) {
148998
+ return typeof part.text === "string" ? part.text : "";
148999
+ }
149000
+ return "";
149001
+ }).filter((text) => text.length > 0).join(`
149002
+ `);
149003
+ };
149004
+ const renderUnknown = (value) => {
149005
+ if (typeof value === "string")
149006
+ return value;
149007
+ if (value === null || value === undefined)
149008
+ return "";
149009
+ try {
149010
+ return JSON.stringify(value);
149011
+ } catch {
149012
+ return String(value);
149013
+ }
149014
+ };
149015
+ const safeTypeLabel = (msg) => msg.message_type || "unknown_message";
149016
+ const formatEntry = (msg) => {
149017
+ const timestamp = msg.date || "unknown-time";
149018
+ const type = safeTypeLabel(msg);
149019
+ if (type === "user_message") {
149020
+ const text = renderText(msg.content);
149021
+ return [`[${timestamp}] user`, text || "(empty)"];
149022
+ }
149023
+ if (type === "assistant_message") {
149024
+ const text = renderText(msg.content);
149025
+ return [`[${timestamp}] assistant`, text || "(empty)"];
149026
+ }
149027
+ if (type === "reasoning_message") {
149028
+ return [
149029
+ `[${timestamp}] reasoning`,
149030
+ msg.reasoning && msg.reasoning.length > 0 ? msg.reasoning : "(empty)"
149031
+ ];
149032
+ }
149033
+ if (type === "tool_call_message" || type === "approval_request_message") {
149034
+ const calls = Array.isArray(msg.tool_calls) ? msg.tool_calls : msg.tool_call ? [msg.tool_call] : [];
149035
+ if (calls.length === 0) {
149036
+ return [`[${timestamp}] ${type}`, "(no tool call payload)"];
149037
+ }
149038
+ return calls.flatMap((call) => {
149039
+ const header = `[${timestamp}] tool_call ${call.name || "unknown"} (${call.tool_call_id || "no-id"})`;
149040
+ const args = call.arguments ? call.arguments : "{}";
149041
+ return [header, args];
149042
+ });
149043
+ }
149044
+ if (type === "tool_return_message") {
149045
+ const returns = Array.isArray(msg.tool_returns) ? msg.tool_returns : [
149046
+ {
149047
+ tool_call_id: msg.tool_call_id,
149048
+ status: msg.status,
149049
+ tool_return: msg.tool_return,
149050
+ func_response: msg.func_response
149051
+ }
149052
+ ];
149053
+ return returns.flatMap((ret) => {
149054
+ const header = `[${timestamp}] tool_return (${ret.tool_call_id || "no-id"}) status=${ret.status || "unknown"}`;
149055
+ const body = renderUnknown(ret.tool_return ?? ret.func_response);
149056
+ return [header, body || "(empty)"];
149057
+ });
149058
+ }
149059
+ const fallbackText = renderText(msg.content) || renderUnknown(msg.content) || "(no content)";
149060
+ return [`[${timestamp}] ${type}`, fallbackText];
149061
+ };
149062
+ const sortChronological3 = (messages) => {
149063
+ return [...messages].sort((a, b) => {
149064
+ const ta = a.date ? new Date(a.date).getTime() : 0;
149065
+ const tb = b.date ? new Date(b.date).getTime() : 0;
149066
+ return ta - tb;
149067
+ });
149068
+ };
149069
+ const fetchConversationMessages = async (conversationId, agentIdForDefault, pageLimit, maxPages) => {
149070
+ const collected = [];
149071
+ const seenIds = new Set;
149072
+ let cursorBefore;
149073
+ for (let pageIndex = 0;pageIndex < maxPages; pageIndex += 1) {
149074
+ const page = await client.conversations.messages.list(conversationId, {
149075
+ limit: pageLimit,
149076
+ order: "desc",
149077
+ ...conversationId === "default" && agentIdForDefault ? { agent_id: agentIdForDefault } : {},
149078
+ ...cursorBefore ? { before: cursorBefore } : {}
149079
+ });
149080
+ const items = page.getPaginatedItems();
149081
+ if (items.length === 0) {
149082
+ break;
149083
+ }
149084
+ let newItems = 0;
149085
+ for (const item of items) {
149086
+ const id = item.id;
149087
+ if (id && !seenIds.has(id)) {
149088
+ seenIds.add(id);
149089
+ collected.push(item);
149090
+ newItems += 1;
149091
+ }
149092
+ }
149093
+ cursorBefore = items[items.length - 1]?.id;
149094
+ if (newItems === 0 || items.length < pageLimit) {
149095
+ break;
149096
+ }
149097
+ }
149098
+ return sortChronological3(collected);
149099
+ };
148627
149100
  if (action === "search") {
148628
149101
  const query = parsed.values.query;
148629
149102
  if (!query || typeof query !== "string") {
@@ -148688,6 +149161,44 @@ async function runMessagesSubcommand(argv) {
148688
149161
  console.log(JSON.stringify(sorted, null, 2));
148689
149162
  return 0;
148690
149163
  }
149164
+ if (action === "transcript") {
149165
+ const conversationId = parsed.values.conversation || parsed.values["conversation-id"];
149166
+ if (!conversationId || typeof conversationId !== "string") {
149167
+ console.error("Missing conversation id. Pass --conversation <id> or --conversation-id <id>.");
149168
+ return 1;
149169
+ }
149170
+ const agentId = getAgentId5(parsed.values.agent, parsed.values["agent-id"]);
149171
+ if (conversationId === "default" && !agentId) {
149172
+ console.error('Conversation "default" requires an agent id. Set LETTA_AGENT_ID or pass --agent/--agent-id.');
149173
+ return 1;
149174
+ }
149175
+ const pageLimit = Math.max(1, parseLimit3(parsed.values.limit, 100));
149176
+ const maxPages = Math.max(1, parseLimit3(parsed.values["max-pages"], 200));
149177
+ const outputPathRaw = parsed.values.out || parsed.values.output;
149178
+ const messages = await fetchConversationMessages(conversationId, agentId || undefined, pageLimit, maxPages);
149179
+ const transcript = messages.flatMap((msg) => formatEntry(msg)).join(`
149180
+
149181
+ `).trim();
149182
+ if (outputPathRaw && typeof outputPathRaw === "string") {
149183
+ const outputPath = resolve23(process.cwd(), outputPathRaw);
149184
+ await writeFile6(outputPath, `${transcript}
149185
+ `, "utf-8");
149186
+ console.log(JSON.stringify({
149187
+ conversation_id: conversationId,
149188
+ agent_id: agentId || null,
149189
+ message_count: messages.length,
149190
+ output_path: outputPath
149191
+ }, null, 2));
149192
+ return 0;
149193
+ }
149194
+ console.log(JSON.stringify({
149195
+ conversation_id: conversationId,
149196
+ agent_id: agentId || null,
149197
+ message_count: messages.length,
149198
+ transcript
149199
+ }, null, 2));
149200
+ return 0;
149201
+ }
148691
149202
  } catch (error) {
148692
149203
  console.error(error instanceof Error ? error.message : String(error));
148693
149204
  return 1;
@@ -148728,7 +149239,7 @@ async function runSubcommand(argv) {
148728
149239
  init_readOnlyShell();
148729
149240
  init_shell_command_normalization();
148730
149241
  import { homedir as homedir23 } from "node:os";
148731
- import { isAbsolute as isAbsolute16, join as join29, relative as relative12, resolve as resolve23 } from "node:path";
149242
+ import { isAbsolute as isAbsolute16, join as join29, relative as relative12, resolve as resolve24 } from "node:path";
148732
149243
  var MODE_KEY2 = Symbol.for("@letta/permissionMode");
148733
149244
  var PLAN_FILE_KEY2 = Symbol.for("@letta/planFilePath");
148734
149245
  var MODE_BEFORE_PLAN_KEY2 = Symbol.for("@letta/permissionModeBeforePlan");
@@ -148764,12 +149275,12 @@ function resolvePlanTargetPath2(targetPath, workingDirectory) {
148764
149275
  if (!trimmedPath)
148765
149276
  return null;
148766
149277
  if (trimmedPath.startsWith("~/")) {
148767
- return resolve23(homedir23(), trimmedPath.slice(2));
149278
+ return resolve24(homedir23(), trimmedPath.slice(2));
148768
149279
  }
148769
149280
  if (isAbsolute16(trimmedPath)) {
148770
- return resolve23(trimmedPath);
149281
+ return resolve24(trimmedPath);
148771
149282
  }
148772
- return resolve23(workingDirectory, trimmedPath);
149283
+ return resolve24(workingDirectory, trimmedPath);
148773
149284
  }
148774
149285
  function isPathInPlansDir2(path23, plansDir) {
148775
149286
  if (!path23.endsWith(".md"))
@@ -149045,7 +149556,7 @@ await __promiseAll([
149045
149556
  ]);
149046
149557
  import { randomUUID as randomUUID4 } from "node:crypto";
149047
149558
  import { homedir as homedir24 } from "node:os";
149048
- import { join as join30, resolve as resolve24 } from "node:path";
149559
+ import { join as join30, resolve as resolve25 } from "node:path";
149049
149560
  var DEFAULT_SETTINGS3 = {
149050
149561
  lastAgent: null,
149051
149562
  tokenStreaming: false,
@@ -149070,6 +149581,9 @@ var DEFAULT_LETTA_API_URL2 = "https://api.letta.com";
149070
149581
  function isSubagentProcess2() {
149071
149582
  return process.env.LETTA_CODE_AGENT_ROLE === "subagent";
149072
149583
  }
149584
+ function shouldPersistSessionState2() {
149585
+ return process.env.LETTA_CODE_AGENT_ROLE !== "subagent" && process.env.LETTA_DISABLE_SESSION_PERSIST !== "1";
149586
+ }
149073
149587
  function normalizeBaseUrl2(baseUrl) {
149074
149588
  let normalized = baseUrl.replace(/^https?:\/\//, "");
149075
149589
  normalized = normalized.replace(/\/$/, "");
@@ -149496,7 +150010,7 @@ class SettingsManager2 {
149496
150010
  return join30(workingDirectory, ".letta", "settings.json");
149497
150011
  }
149498
150012
  isProjectSettingsPathCollidingWithGlobal(workingDirectory) {
149499
- return resolve24(this.getProjectSettingsPath(workingDirectory)) === resolve24(this.getSettingsPath());
150013
+ return resolve25(this.getProjectSettingsPath(workingDirectory)) === resolve25(this.getSettingsPath());
149500
150014
  }
149501
150015
  getLocalProjectSettingsPath(workingDirectory) {
149502
150016
  return join30(workingDirectory, ".letta", "settings.local.json");
@@ -150458,8 +150972,8 @@ function acquireSwitchLock2() {
150458
150972
  const lock = getSwitchLock2();
150459
150973
  lock.refCount++;
150460
150974
  if (lock.refCount === 1) {
150461
- lock.promise = new Promise((resolve25) => {
150462
- lock.resolve = resolve25;
150975
+ lock.promise = new Promise((resolve26) => {
150976
+ lock.resolve = resolve26;
150463
150977
  });
150464
150978
  }
150465
150979
  }
@@ -150730,6 +151244,7 @@ SUBCOMMANDS (JSON-only)
150730
151244
  letta agents list [--query <text> | --name <name> | --tags <tags>]
150731
151245
  letta messages search --query <text> [--all-agents]
150732
151246
  letta messages list [--agent <id>]
151247
+ letta messages transcript --conversation <id> [--out <path>]
150733
151248
  letta blocks list --agent <id>
150734
151249
  letta blocks copy --block-id <id> [--label <label>] [--agent <id>] [--override]
150735
151250
  letta blocks attach --block-id <id> [--agent <id>] [--read-only] [--override]
@@ -150918,14 +151433,6 @@ async function main() {
150918
151433
  }
150919
151434
  const { checkAndAutoUpdate: checkAndAutoUpdate2 } = await init_auto_update().then(() => exports_auto_update);
150920
151435
  const autoUpdatePromise = startStartupAutoUpdateCheck(checkAndAutoUpdate2);
150921
- const { startDockerVersionCheck: startDockerVersionCheck2 } = await init_startup_docker_check().then(() => exports_startup_docker_check);
150922
- startDockerVersionCheck2().catch(() => {});
150923
- const { cleanupOldOverflowFiles: cleanupOldOverflowFiles2 } = await Promise.resolve().then(() => (init_overflow2(), exports_overflow));
150924
- Promise.resolve().then(() => {
150925
- try {
150926
- cleanupOldOverflowFiles2(process.cwd());
150927
- } catch {}
150928
- });
150929
151436
  const processedArgs = preprocessCliArgs(process.argv);
150930
151437
  let values;
150931
151438
  let positionals;
@@ -150951,7 +151458,7 @@ Note: Flags should use double dashes for full names (e.g., --yolo, not -yolo)`);
150951
151458
  printHelp();
150952
151459
  const helpDelayMs = Number.parseInt(process.env.LETTA_TEST_HELP_EXIT_DELAY_MS ?? "", 10);
150953
151460
  if (Number.isFinite(helpDelayMs) && helpDelayMs > 0) {
150954
- await new Promise((resolve32) => setTimeout(resolve32, helpDelayMs));
151461
+ await new Promise((resolve33) => setTimeout(resolve33, helpDelayMs));
150955
151462
  }
150956
151463
  process.exit(0);
150957
151464
  }
@@ -151036,6 +151543,16 @@ Note: Flags should use double dashes for full names (e.g., --yolo, not -yolo)`);
151036
151543
  const isHeadless = values.prompt || values.run || !process.stdin.isTTY;
151037
151544
  telemetry2.setSurface(isHeadless ? "headless" : "tui");
151038
151545
  telemetry2.init();
151546
+ if (!isHeadless) {
151547
+ const { startDockerVersionCheck: startDockerVersionCheck2 } = await init_startup_docker_check().then(() => exports_startup_docker_check);
151548
+ startDockerVersionCheck2().catch(() => {});
151549
+ const { cleanupOldOverflowFiles: cleanupOldOverflowFiles2 } = await Promise.resolve().then(() => (init_overflow2(), exports_overflow));
151550
+ Promise.resolve().then(() => {
151551
+ try {
151552
+ cleanupOldOverflowFiles2(process.cwd());
151553
+ } catch {}
151554
+ });
151555
+ }
151039
151556
  if (command && !isHeadless) {
151040
151557
  console.error(`Error: Unknown command or argument "${command}"`);
151041
151558
  console.error("Run 'letta --help' for usage information.");
@@ -151163,9 +151680,9 @@ Note: Flags should use double dashes for full names (e.g., --yolo, not -yolo)`);
151163
151680
  process.exit(1);
151164
151681
  }
151165
151682
  } else {
151166
- const { resolve: resolve32 } = await import("path");
151683
+ const { resolve: resolve33 } = await import("path");
151167
151684
  const { existsSync: existsSync40 } = await import("fs");
151168
- const resolvedPath = resolve32(fromAfFile);
151685
+ const resolvedPath = resolve33(fromAfFile);
151169
151686
  if (!existsSync40(resolvedPath)) {
151170
151687
  console.error(`Error: AgentFile not found: ${resolvedPath}`);
151171
151688
  process.exit(1);
@@ -151640,6 +152157,7 @@ Error: ${message}`);
151640
152157
  setLoadingState("initializing");
151641
152158
  const { createAgent: createAgent3 } = await init_create3().then(() => exports_create2);
151642
152159
  let agent = null;
152160
+ let autoEnableMemfsForFreshAgent = false;
151643
152161
  if (fromAfFile2) {
151644
152162
  setLoadingState("importing");
151645
152163
  let result;
@@ -151724,10 +152242,7 @@ Error: ${message}`);
151724
152242
  });
151725
152243
  agent = result.agent;
151726
152244
  setAgentProvenance(result.provenance);
151727
- if (willAutoEnableMemfs) {
151728
- const { enableMemfsIfCloud: enableMemfsIfCloud3 } = await Promise.resolve().then(() => (init_memoryFilesystem2(), exports_memoryFilesystem2));
151729
- await enableMemfsIfCloud3(agent.id);
151730
- }
152245
+ autoEnableMemfsForFreshAgent = willAutoEnableMemfs;
151731
152246
  }
151732
152247
  if (!agent && resumingAgentId) {
151733
152248
  try {
@@ -151750,11 +152265,12 @@ Error: ${message}`);
151750
152265
  settingsManager2.updateLocalProjectSettings({ lastAgent: agent.id });
151751
152266
  settingsManager2.updateSettings({ lastAgent: agent.id });
151752
152267
  setAgentContext(agent.id, skillsDirectory2, resolvedSkillSources);
151753
- const isSubagent = process.env.LETTA_CODE_AGENT_ROLE === "subagent";
151754
152268
  const agentId2 = agent.id;
151755
152269
  const agentTags = agent.tags ?? undefined;
151756
- const memfsSyncPromise = Promise.resolve().then(() => (init_memoryFilesystem2(), exports_memoryFilesystem2)).then(({ applyMemfsFlags: applyMemfsFlags3 }) => applyMemfsFlags3(agentId2, memfsFlag, noMemfsFlag, {
151757
- agentTags
152270
+ const startupMemfsFlag = autoEnableMemfsForFreshAgent ? true : memfsFlag;
152271
+ const memfsSyncPromise = Promise.resolve().then(() => (init_memoryFilesystem2(), exports_memoryFilesystem2)).then(({ applyMemfsFlags: applyMemfsFlags3 }) => applyMemfsFlags3(agentId2, startupMemfsFlag, noMemfsFlag, {
152272
+ agentTags,
152273
+ skipPromptUpdate: shouldCreateNew
151758
152274
  }));
151759
152275
  const secretsInitPromise = init_secretsStore2().then(() => exports_secretsStore2).then(({ initSecretsFromServer: initSecretsFromServer3 }) => initSecretsFromServer3(agentId2));
151760
152276
  const isResumingProject = !shouldCreateNew && !!resumingAgentId;
@@ -151895,7 +152411,7 @@ Error: ${message}`);
151895
152411
  }
151896
152412
  }
151897
152413
  }
151898
- if (!isSubagent) {
152414
+ if (shouldPersistSessionState2()) {
151899
152415
  settingsManager2.persistSession(agent.id, conversationIdToUse);
151900
152416
  }
151901
152417
  setAgentId(agent.id);
@@ -152041,4 +152557,4 @@ Error during initialization: ${message}`);
152041
152557
  }
152042
152558
  main();
152043
152559
 
152044
- //# debugId=55BEE0B212809B3B64756E2164756E21
152560
+ //# debugId=275910C1663139CA64756E2164756E21