@letta-ai/letta-code 0.12.0 → 0.12.2

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.
Files changed (2) hide show
  1. package/letta.js +889 -179
  2. package/package.json +1 -1
package/letta.js CHANGED
@@ -3237,7 +3237,7 @@ var package_default;
3237
3237
  var init_package = __esm(() => {
3238
3238
  package_default = {
3239
3239
  name: "@letta-ai/letta-code",
3240
- version: "0.12.0",
3240
+ version: "0.12.2",
3241
3241
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
3242
3242
  type: "module",
3243
3243
  bin: {
@@ -29951,6 +29951,10 @@ var init_WelcomeScreen = __esm(async () => {
29951
29951
  });
29952
29952
 
29953
29953
  // src/permissions/readOnlyShell.ts
29954
+ function isReadOnlySkillScript(scriptPath) {
29955
+ const normalized = scriptPath.replace(/\\/g, "/");
29956
+ return BUNDLED_READ_ONLY_SCRIPTS.some((pattern) => normalized.endsWith(pattern));
29957
+ }
29954
29958
  function isReadOnlyShellCommand(command) {
29955
29959
  if (!command) {
29956
29960
  return false;
@@ -30018,6 +30022,13 @@ function isSafeSegment(segment) {
30018
30022
  if (command === "sort") {
30019
30023
  return !/\s-o\b/.test(segment);
30020
30024
  }
30025
+ if (command === "npx" && tokens[1] === "tsx") {
30026
+ const scriptPath = tokens[2];
30027
+ if (scriptPath && isReadOnlySkillScript(scriptPath)) {
30028
+ return true;
30029
+ }
30030
+ return false;
30031
+ }
30021
30032
  return false;
30022
30033
  }
30023
30034
  return true;
@@ -30050,7 +30061,7 @@ function extractDashCArgument(tokens) {
30050
30061
  }
30051
30062
  return;
30052
30063
  }
30053
- var ALWAYS_SAFE_COMMANDS, SAFE_GIT_SUBCOMMANDS, DANGEROUS_OPERATOR_PATTERN;
30064
+ var ALWAYS_SAFE_COMMANDS, SAFE_GIT_SUBCOMMANDS, BUNDLED_READ_ONLY_SCRIPTS, DANGEROUS_OPERATOR_PATTERN;
30054
30065
  var init_readOnlyShell = __esm(() => {
30055
30066
  ALWAYS_SAFE_COMMANDS = new Set([
30056
30067
  "cat",
@@ -30113,6 +30124,12 @@ var init_readOnlyShell = __esm(() => {
30113
30124
  "tag",
30114
30125
  "remote"
30115
30126
  ]);
30127
+ BUNDLED_READ_ONLY_SCRIPTS = [
30128
+ "/skills/searching-messages/scripts/search-messages.ts",
30129
+ "/skills/searching-messages/scripts/get-messages.ts",
30130
+ "/skills/builtin/searching-messages/scripts/search-messages.ts",
30131
+ "/skills/builtin/searching-messages/scripts/get-messages.ts"
30132
+ ];
30116
30133
  DANGEROUS_OPERATOR_PATTERN = /(>>|>|\$\(|`)/;
30117
30134
  });
30118
30135
 
@@ -31629,6 +31646,46 @@ Remember: You're planning, not implementing. Don't make changes, just create a r
31629
31646
  `;
31630
31647
  var init_plan = () => {};
31631
31648
 
31649
+ // src/agent/subagents/builtin/recall.md
31650
+ var recall_default = `---
31651
+ name: recall
31652
+ description: Search conversation history to recall past discussions, decisions, and context
31653
+ tools: Skill, Bash, Read, BashOutput
31654
+ model: haiku
31655
+ memoryBlocks: human, persona
31656
+ mode: stateless
31657
+ ---
31658
+
31659
+ You are a subagent launched via the Task tool to search conversation history. You run autonomously and return a single final report when done. You CANNOT ask questions mid-execution.
31660
+
31661
+ ## Instructions
31662
+
31663
+ ### Step 1: Load the searching-messages skill
31664
+ \`\`\`
31665
+ Skill({ command: "load", skills: ["searching-messages"] })
31666
+ \`\`\`
31667
+
31668
+ The skill content will appear in your loaded_skills block with script paths and search strategies.
31669
+
31670
+ ### Step 2: Search the parent agent's history
31671
+
31672
+ **CRITICAL - Two rules:**
31673
+
31674
+ 1. **DO NOT use \`conversation_search\`** - That tool only searches YOUR history (empty). You MUST use the Bash scripts from the skill.
31675
+
31676
+ 2. **ALWAYS add \`--agent-id $LETTA_PARENT_AGENT_ID\`** - This searches the parent agent's history. The only exception is \`--all-agents\` searches.
31677
+
31678
+ Follow the strategies documented in the loaded skill.
31679
+
31680
+ ## Output Format
31681
+
31682
+ 1. **Direct answer** - What the user asked about
31683
+ 2. **Key findings** - Relevant quotes or summaries from past conversations
31684
+ 3. **When discussed** - Timestamps of relevant discussions
31685
+ 4. **Outcome/Decision** - What was decided or concluded (if applicable)
31686
+ `;
31687
+ var init_recall = () => {};
31688
+
31632
31689
  // src/agent/subagents/index.ts
31633
31690
  var exports_subagents = {};
31634
31691
  __export(exports_subagents, {
@@ -31793,7 +31850,13 @@ var init_subagents = __esm(() => {
31793
31850
  init_explore();
31794
31851
  init_general_purpose();
31795
31852
  init_plan();
31796
- BUILTIN_SOURCES = [explore_default, general_purpose_default, plan_default];
31853
+ init_recall();
31854
+ BUILTIN_SOURCES = [
31855
+ explore_default,
31856
+ general_purpose_default,
31857
+ plan_default,
31858
+ recall_default
31859
+ ];
31797
31860
  GLOBAL_AGENTS_DIR = join3(process.env.HOME || process.env.USERPROFILE || "~", ".letta/agents");
31798
31861
  VALID_MEMORY_BLOCKS = new Set(MEMORY_BLOCK_LABELS);
31799
31862
  cache3 = {
@@ -33424,6 +33487,82 @@ var init_shellEnv = __esm(async () => {
33424
33487
  await init_settings_manager();
33425
33488
  });
33426
33489
 
33490
+ // src/tools/impl/shellLaunchers.ts
33491
+ function pushUnique(list, seen, entry) {
33492
+ if (!entry.length || !entry[0])
33493
+ return;
33494
+ const key = entry.join(SEP2);
33495
+ if (seen.has(key))
33496
+ return;
33497
+ seen.add(key);
33498
+ list.push(entry);
33499
+ }
33500
+ function windowsLaunchers(command) {
33501
+ const trimmed = command.trim();
33502
+ if (!trimmed)
33503
+ return [];
33504
+ const launchers = [];
33505
+ const seen = new Set;
33506
+ pushUnique(launchers, seen, [
33507
+ "powershell.exe",
33508
+ "-NoProfile",
33509
+ "-Command",
33510
+ trimmed
33511
+ ]);
33512
+ pushUnique(launchers, seen, ["pwsh", "-NoProfile", "-Command", trimmed]);
33513
+ const envComSpecRaw = process.env.ComSpec || process.env.COMSPEC;
33514
+ const envComSpec = envComSpecRaw?.trim();
33515
+ if (envComSpec) {
33516
+ pushUnique(launchers, seen, [envComSpec, "/d", "/s", "/c", trimmed]);
33517
+ }
33518
+ pushUnique(launchers, seen, ["cmd.exe", "/d", "/s", "/c", trimmed]);
33519
+ return launchers;
33520
+ }
33521
+ function unixLaunchers(command) {
33522
+ const trimmed = command.trim();
33523
+ if (!trimmed)
33524
+ return [];
33525
+ const launchers = [];
33526
+ const seen = new Set;
33527
+ if (process.platform === "darwin") {
33528
+ pushUnique(launchers, seen, ["/bin/zsh", "-c", trimmed]);
33529
+ }
33530
+ const envShell = process.env.SHELL?.trim();
33531
+ if (envShell) {
33532
+ pushUnique(launchers, seen, [envShell, "-c", trimmed]);
33533
+ }
33534
+ const defaults2 = process.platform === "darwin" ? [
33535
+ ["/bin/zsh", "-c", trimmed],
33536
+ ["bash", "-c", trimmed],
33537
+ ["/bin/bash", "-c", trimmed],
33538
+ ["/usr/bin/bash", "-c", trimmed],
33539
+ ["/bin/sh", "-c", trimmed],
33540
+ ["/bin/ash", "-c", trimmed],
33541
+ ["/usr/bin/env", "zsh", "-c", trimmed],
33542
+ ["/usr/bin/env", "bash", "-c", trimmed],
33543
+ ["/usr/bin/env", "sh", "-c", trimmed],
33544
+ ["/usr/bin/env", "ash", "-c", trimmed]
33545
+ ] : [
33546
+ ["/bin/bash", "-c", trimmed],
33547
+ ["/usr/bin/bash", "-c", trimmed],
33548
+ ["/bin/zsh", "-c", trimmed],
33549
+ ["/bin/sh", "-c", trimmed],
33550
+ ["/bin/ash", "-c", trimmed],
33551
+ ["/usr/bin/env", "bash", "-c", trimmed],
33552
+ ["/usr/bin/env", "zsh", "-c", trimmed],
33553
+ ["/usr/bin/env", "sh", "-c", trimmed],
33554
+ ["/usr/bin/env", "ash", "-c", trimmed]
33555
+ ];
33556
+ for (const entry of defaults2) {
33557
+ pushUnique(launchers, seen, entry);
33558
+ }
33559
+ return launchers;
33560
+ }
33561
+ function buildShellLaunchers(command) {
33562
+ return process.platform === "win32" ? windowsLaunchers(command) : unixLaunchers(command);
33563
+ }
33564
+ var SEP2 = "\x00";
33565
+
33427
33566
  // src/tools/impl/truncation.ts
33428
33567
  function truncateByChars(text, maxChars, _toolName = "output") {
33429
33568
  if (text.length <= maxChars) {
@@ -33457,35 +33596,24 @@ __export(exports_Bash, {
33457
33596
  bash: () => bash
33458
33597
  });
33459
33598
  import { spawn } from "node:child_process";
33460
- import { existsSync as existsSync4 } from "node:fs";
33461
- function getShellConfig() {
33462
- if (cachedShellConfig) {
33463
- return cachedShellConfig;
33464
- }
33465
- if (process.platform === "win32") {
33466
- cachedShellConfig = {
33467
- executable: "powershell.exe",
33468
- args: (cmd) => ["-NoProfile", "-Command", cmd]
33469
- };
33470
- return cachedShellConfig;
33471
- }
33472
- if (process.platform === "darwin" && existsSync4("/bin/zsh")) {
33473
- cachedShellConfig = {
33474
- executable: "/bin/zsh",
33475
- args: (cmd) => ["-c", cmd]
33476
- };
33477
- return cachedShellConfig;
33599
+ function getBackgroundLauncher(command) {
33600
+ if (cachedWorkingLauncher) {
33601
+ const [executable, ...launcherArgs] = cachedWorkingLauncher;
33602
+ if (executable) {
33603
+ return [executable, ...launcherArgs.slice(0, -1), command];
33604
+ }
33478
33605
  }
33479
- cachedShellConfig = {
33480
- executable: "bash",
33481
- args: (cmd) => ["-c", cmd]
33482
- };
33483
- return cachedShellConfig;
33606
+ const launchers = buildShellLaunchers(command);
33607
+ return launchers[0] || [];
33484
33608
  }
33485
- function spawnCommand(command, options) {
33609
+ function spawnWithLauncher(launcher, options) {
33486
33610
  return new Promise((resolve2, reject) => {
33487
- const { executable, args } = getShellConfig();
33488
- const childProcess = spawn(executable, args(command), {
33611
+ const [executable, ...args] = launcher;
33612
+ if (!executable) {
33613
+ reject(new Error("Empty launcher"));
33614
+ return;
33615
+ }
33616
+ const childProcess = spawn(executable, args, {
33489
33617
  cwd: options.cwd,
33490
33618
  env: options.env,
33491
33619
  shell: false,
@@ -33547,6 +33675,52 @@ function spawnCommand(command, options) {
33547
33675
  });
33548
33676
  });
33549
33677
  }
33678
+ async function spawnCommand(command, options) {
33679
+ if (process.platform !== "win32") {
33680
+ const executable = process.platform === "darwin" ? "/bin/zsh" : "bash";
33681
+ return spawnWithLauncher([executable, "-c", command], options);
33682
+ }
33683
+ if (cachedWorkingLauncher) {
33684
+ const [executable, ...launcherArgs] = cachedWorkingLauncher;
33685
+ if (executable) {
33686
+ const newLauncher = [executable, ...launcherArgs.slice(0, -1), command];
33687
+ try {
33688
+ const result = await spawnWithLauncher(newLauncher, options);
33689
+ return result;
33690
+ } catch (error) {
33691
+ const err = error;
33692
+ if (err.code !== "ENOENT") {
33693
+ throw error;
33694
+ }
33695
+ cachedWorkingLauncher = null;
33696
+ }
33697
+ }
33698
+ }
33699
+ const launchers = buildShellLaunchers(command);
33700
+ if (launchers.length === 0) {
33701
+ throw new Error("No shell launchers available");
33702
+ }
33703
+ const tried = [];
33704
+ let lastError = null;
33705
+ for (const launcher of launchers) {
33706
+ try {
33707
+ const result = await spawnWithLauncher(launcher, options);
33708
+ cachedWorkingLauncher = launcher;
33709
+ return result;
33710
+ } catch (error) {
33711
+ const err = error;
33712
+ if (err.code === "ENOENT") {
33713
+ tried.push(launcher[0] || "unknown");
33714
+ lastError = err;
33715
+ continue;
33716
+ }
33717
+ throw error;
33718
+ }
33719
+ }
33720
+ const suffix = tried.filter(Boolean).join(", ");
33721
+ const reason = lastError?.message || "Shell unavailable";
33722
+ throw new Error(suffix ? `${reason} (tried: ${suffix})` : reason);
33723
+ }
33550
33724
  async function bash(args) {
33551
33725
  validateRequiredParams(args, ["command"], "Bash");
33552
33726
  const {
@@ -33578,8 +33752,15 @@ async function bash(args) {
33578
33752
  }
33579
33753
  if (run_in_background) {
33580
33754
  const bashId = getNextBashId();
33581
- const { executable, args: args2 } = getShellConfig();
33582
- const childProcess = spawn(executable, args2(command), {
33755
+ const launcher = getBackgroundLauncher(command);
33756
+ const [executable, ...launcherArgs] = launcher;
33757
+ if (!executable) {
33758
+ return {
33759
+ content: [{ type: "text", text: "No shell available" }],
33760
+ status: "error"
33761
+ };
33762
+ }
33763
+ const childProcess = spawn(executable, launcherArgs, {
33583
33764
  shell: false,
33584
33765
  cwd: userCwd,
33585
33766
  env: getShellEnv()
@@ -33692,7 +33873,7 @@ ${errorMessage}`;
33692
33873
  };
33693
33874
  }
33694
33875
  }
33695
- var cachedShellConfig = null;
33876
+ var cachedWorkingLauncher = null;
33696
33877
  var init_Bash2 = __esm(async () => {
33697
33878
  init_constants();
33698
33879
  init_process_manager();
@@ -34017,6 +34198,20 @@ class PermissionModeManager2 {
34017
34198
  return "allow";
34018
34199
  }
34019
34200
  }
34201
+ const readOnlySubagentTypes = new Set([
34202
+ "explore",
34203
+ "Explore",
34204
+ "plan",
34205
+ "Plan",
34206
+ "recall",
34207
+ "Recall"
34208
+ ]);
34209
+ if (toolName === "Task" || toolName === "task") {
34210
+ const subagentType = toolArgs?.subagent_type;
34211
+ if (subagentType && readOnlySubagentTypes.has(subagentType)) {
34212
+ return "allow";
34213
+ }
34214
+ }
34020
34215
  const shellTools = [
34021
34216
  "Bash",
34022
34217
  "shell",
@@ -42096,69 +42291,6 @@ var init_SearchFileContentGemini2 = __esm(() => {
42096
42291
  init_Grep2();
42097
42292
  });
42098
42293
 
42099
- // src/tools/impl/shellLaunchers.ts
42100
- function pushUnique(list, seen, entry) {
42101
- if (!entry.length || !entry[0])
42102
- return;
42103
- const key = entry.join(SEP2);
42104
- if (seen.has(key))
42105
- return;
42106
- seen.add(key);
42107
- list.push(entry);
42108
- }
42109
- function windowsLaunchers(command) {
42110
- const trimmed = command.trim();
42111
- if (!trimmed)
42112
- return [];
42113
- const launchers = [];
42114
- const seen = new Set;
42115
- pushUnique(launchers, seen, [
42116
- "powershell.exe",
42117
- "-NoProfile",
42118
- "-Command",
42119
- trimmed
42120
- ]);
42121
- pushUnique(launchers, seen, ["pwsh", "-NoProfile", "-Command", trimmed]);
42122
- const envComSpecRaw = process.env.ComSpec || process.env.COMSPEC;
42123
- const envComSpec = envComSpecRaw?.trim();
42124
- if (envComSpec) {
42125
- pushUnique(launchers, seen, [envComSpec, "/d", "/s", "/c", trimmed]);
42126
- }
42127
- pushUnique(launchers, seen, ["cmd.exe", "/d", "/s", "/c", trimmed]);
42128
- return launchers;
42129
- }
42130
- function unixLaunchers(command) {
42131
- const trimmed = command.trim();
42132
- if (!trimmed)
42133
- return [];
42134
- const launchers = [];
42135
- const seen = new Set;
42136
- const envShell = process.env.SHELL?.trim();
42137
- if (envShell) {
42138
- pushUnique(launchers, seen, [envShell, "-lc", trimmed]);
42139
- pushUnique(launchers, seen, [envShell, "-c", trimmed]);
42140
- }
42141
- const defaults3 = [
42142
- ["/bin/bash", "-lc", trimmed],
42143
- ["/usr/bin/bash", "-lc", trimmed],
42144
- ["/bin/zsh", "-lc", trimmed],
42145
- ["/bin/sh", "-c", trimmed],
42146
- ["/bin/ash", "-c", trimmed],
42147
- ["/usr/bin/env", "bash", "-lc", trimmed],
42148
- ["/usr/bin/env", "zsh", "-lc", trimmed],
42149
- ["/usr/bin/env", "sh", "-c", trimmed],
42150
- ["/usr/bin/env", "ash", "-c", trimmed]
42151
- ];
42152
- for (const entry of defaults3) {
42153
- pushUnique(launchers, seen, entry);
42154
- }
42155
- return launchers;
42156
- }
42157
- function buildShellLaunchers(command) {
42158
- return process.platform === "win32" ? windowsLaunchers(command) : unixLaunchers(command);
42159
- }
42160
- var SEP2 = "\x00";
42161
-
42162
42294
  // src/tools/impl/Shell.ts
42163
42295
  import { spawn as spawn2 } from "node:child_process";
42164
42296
  import * as path12 from "node:path";
@@ -42363,7 +42495,7 @@ __export(exports_skills, {
42363
42495
  SKILLS_DIR: () => SKILLS_DIR,
42364
42496
  GLOBAL_SKILLS_DIR: () => GLOBAL_SKILLS_DIR
42365
42497
  });
42366
- import { existsSync as existsSync5 } from "node:fs";
42498
+ import { existsSync as existsSync4 } from "node:fs";
42367
42499
  import { readdir as readdir4, readFile as readFile3 } from "node:fs/promises";
42368
42500
  import { dirname as dirname4, join as join7 } from "node:path";
42369
42501
  import { fileURLToPath as fileURLToPath6 } from "node:url";
@@ -42381,7 +42513,7 @@ async function getBundledSkills() {
42381
42513
  }
42382
42514
  async function discoverSkillsFromDir(skillsPath, source) {
42383
42515
  const errors = [];
42384
- if (!existsSync5(skillsPath)) {
42516
+ if (!existsSync4(skillsPath)) {
42385
42517
  return { skills: [], errors: [] };
42386
42518
  }
42387
42519
  const skills = [];
@@ -43281,11 +43413,16 @@ async function executeSubagent(type, config, model, userPrompt, baseURL, subagen
43281
43413
  try {
43282
43414
  const cliArgs = buildSubagentArgs(type, config, model, userPrompt);
43283
43415
  const lettaCmd = process.env.LETTA_CODE_BIN || "letta";
43416
+ let parentAgentId;
43417
+ try {
43418
+ parentAgentId = getCurrentAgentId();
43419
+ } catch {}
43284
43420
  const proc2 = spawn3(lettaCmd, cliArgs, {
43285
43421
  cwd: process.cwd(),
43286
43422
  env: {
43287
43423
  ...process.env,
43288
- LETTA_CODE_AGENT_ROLE: "subagent"
43424
+ LETTA_CODE_AGENT_ROLE: "subagent",
43425
+ ...parentAgentId && { LETTA_PARENT_AGENT_ID: parentAgentId }
43289
43426
  }
43290
43427
  });
43291
43428
  let wasAborted = false;
@@ -44943,7 +45080,7 @@ __export(exports_skills2, {
44943
45080
  SKILLS_DIR: () => SKILLS_DIR2,
44944
45081
  GLOBAL_SKILLS_DIR: () => GLOBAL_SKILLS_DIR2
44945
45082
  });
44946
- import { existsSync as existsSync6 } from "node:fs";
45083
+ import { existsSync as existsSync5 } from "node:fs";
44947
45084
  import { readdir as readdir5, readFile as readFile5 } from "node:fs/promises";
44948
45085
  import { dirname as dirname7, join as join9 } from "node:path";
44949
45086
  import { fileURLToPath as fileURLToPath7 } from "node:url";
@@ -44961,7 +45098,7 @@ async function getBundledSkills2() {
44961
45098
  }
44962
45099
  async function discoverSkillsFromDir2(skillsPath, source) {
44963
45100
  const errors = [];
44964
- if (!existsSync6(skillsPath)) {
45101
+ if (!existsSync5(skillsPath)) {
44965
45102
  return { skills: [], errors: [] };
44966
45103
  }
44967
45104
  const skills = [];
@@ -45201,7 +45338,7 @@ __export(exports_fs, {
45201
45338
  exists: () => exists2
45202
45339
  });
45203
45340
  import {
45204
- existsSync as existsSync7,
45341
+ existsSync as existsSync6,
45205
45342
  readFileSync as fsReadFileSync2,
45206
45343
  writeFileSync as fsWriteFileSync2,
45207
45344
  mkdirSync as mkdirSync2
@@ -45212,13 +45349,13 @@ async function readFile6(path14) {
45212
45349
  }
45213
45350
  async function writeFile2(path14, content) {
45214
45351
  const dir = dirname8(path14);
45215
- if (!existsSync7(dir)) {
45352
+ if (!existsSync6(dir)) {
45216
45353
  mkdirSync2(dir, { recursive: true });
45217
45354
  }
45218
45355
  fsWriteFileSync2(path14, content, { encoding: "utf-8", flush: true });
45219
45356
  }
45220
45357
  function exists2(path14) {
45221
- return existsSync7(path14);
45358
+ return existsSync6(path14);
45222
45359
  }
45223
45360
  async function mkdir2(path14, options) {
45224
45361
  mkdirSync2(path14, options);
@@ -45490,7 +45627,7 @@ __export(exports_subagents2, {
45490
45627
  GLOBAL_AGENTS_DIR: () => GLOBAL_AGENTS_DIR2,
45491
45628
  AGENTS_DIR: () => AGENTS_DIR2
45492
45629
  });
45493
- import { existsSync as existsSync8 } from "node:fs";
45630
+ import { existsSync as existsSync7 } from "node:fs";
45494
45631
  import { readdir as readdir6, readFile as readFile7 } from "node:fs/promises";
45495
45632
  import { join as join10 } from "node:path";
45496
45633
  function isValidName2(name) {
@@ -45573,7 +45710,7 @@ function getBuiltinSubagentNames2() {
45573
45710
  return new Set(Object.keys(getBuiltinSubagents2()));
45574
45711
  }
45575
45712
  async function discoverSubagentsFromDir2(agentsDir, seenNames, subagents, errors) {
45576
- if (!existsSync8(agentsDir)) {
45713
+ if (!existsSync7(agentsDir)) {
45577
45714
  return;
45578
45715
  }
45579
45716
  try {
@@ -45644,7 +45781,13 @@ var init_subagents2 = __esm(() => {
45644
45781
  init_explore();
45645
45782
  init_general_purpose();
45646
45783
  init_plan();
45647
- BUILTIN_SOURCES2 = [explore_default, general_purpose_default, plan_default];
45784
+ init_recall();
45785
+ BUILTIN_SOURCES2 = [
45786
+ explore_default,
45787
+ general_purpose_default,
45788
+ plan_default,
45789
+ recall_default
45790
+ ];
45648
45791
  GLOBAL_AGENTS_DIR2 = join10(process.env.HOME || process.env.USERPROFILE || "~", ".letta/agents");
45649
45792
  VALID_MEMORY_BLOCKS2 = new Set(MEMORY_BLOCK_LABELS);
45650
45793
  cache4 = {
@@ -48326,7 +48469,9 @@ var init_checker = __esm(() => {
48326
48469
  "explore",
48327
48470
  "Explore",
48328
48471
  "plan",
48329
- "Plan"
48472
+ "Plan",
48473
+ "recall",
48474
+ "Recall"
48330
48475
  ]);
48331
48476
  });
48332
48477
 
@@ -49510,7 +49655,7 @@ var init_create = __esm(async () => {
49510
49655
  });
49511
49656
 
49512
49657
  // src/agent/message.ts
49513
- async function sendMessageStream(agentId, messages, opts = { streamTokens: true, background: true }) {
49658
+ async function sendMessageStream(agentId, messages, opts = { streamTokens: true, background: true }, requestOptions = { maxRetries: 0 }) {
49514
49659
  const client = await getClient2();
49515
49660
  return client.agents.messages.create(agentId, {
49516
49661
  messages,
@@ -49518,7 +49663,7 @@ async function sendMessageStream(agentId, messages, opts = { streamTokens: true,
49518
49663
  stream_tokens: opts.streamTokens ?? true,
49519
49664
  background: opts.background ?? true,
49520
49665
  client_tools: getClientToolsFromRegistry()
49521
- });
49666
+ }, requestOptions);
49522
49667
  }
49523
49668
  var init_message = __esm(async () => {
49524
49669
  await __promiseAll([
@@ -49636,8 +49781,10 @@ function markCurrentLineAsFinished(b) {
49636
49781
  markAsFinished(b, b.lastOtid);
49637
49782
  } else {}
49638
49783
  }
49639
- function markIncompleteToolsAsCancelled(b) {
49640
- b.interrupted = true;
49784
+ function markIncompleteToolsAsCancelled(b, setInterruptedFlag = true) {
49785
+ if (setInterruptedFlag) {
49786
+ b.interrupted = true;
49787
+ }
49641
49788
  let anyToolsCancelled = false;
49642
49789
  for (const [id, line] of b.byId.entries()) {
49643
49790
  if (line.kind === "tool_call" && line.phase !== "finished") {
@@ -50032,6 +50179,10 @@ async function drainStream(stream2, buffers, refresh, abortSignal, onFirstMessag
50032
50179
  queueMicrotask(refresh);
50033
50180
  break;
50034
50181
  }
50182
+ const errObj = chunk.error;
50183
+ if (errObj?.detail?.includes("No tool call is currently awaiting approval")) {
50184
+ continue;
50185
+ }
50035
50186
  onChunk(buffers, chunk);
50036
50187
  queueMicrotask(refresh);
50037
50188
  if (chunk.message_type === "stop_reason") {
@@ -50113,7 +50264,7 @@ async function drainStreamWithResume(stream2, buffers, refresh, abortSignal, onF
50113
50264
  const resumeStream = await client.runs.messages.stream(result.lastRunId, {
50114
50265
  starting_after: result.lastSeqId,
50115
50266
  batch_size: 1000
50116
- });
50267
+ }, { maxRetries: 0 });
50117
50268
  const resumeResult = await drainStream(resumeStream, buffers, refresh, abortSignal);
50118
50269
  result = resumeResult;
50119
50270
  } catch (_e) {
@@ -52088,6 +52239,89 @@ var init_available_models = __esm(async () => {
52088
52239
  CACHE_TTL_MS = 5 * 60 * 1000;
52089
52240
  });
52090
52241
 
52242
+ // src/ralph/mode.ts
52243
+ function getDefaultState() {
52244
+ return {
52245
+ isActive: false,
52246
+ isYolo: false,
52247
+ originalPrompt: "",
52248
+ completionPromise: null,
52249
+ maxIterations: 0,
52250
+ currentIteration: 0
52251
+ };
52252
+ }
52253
+ function getGlobalState() {
52254
+ const global2 = globalThis;
52255
+ if (!global2[RALPH_KEY]) {
52256
+ global2[RALPH_KEY] = getDefaultState();
52257
+ }
52258
+ return global2[RALPH_KEY];
52259
+ }
52260
+ function setGlobalState(state) {
52261
+ const global2 = globalThis;
52262
+ global2[RALPH_KEY] = state;
52263
+ }
52264
+
52265
+ class RalphModeManager {
52266
+ activate(prompt, completionPromise, maxIterations, isYolo) {
52267
+ let resolvedPromise;
52268
+ if (completionPromise === undefined) {
52269
+ resolvedPromise = DEFAULT_COMPLETION_PROMISE;
52270
+ } else if (completionPromise === null || completionPromise === "" || completionPromise.toLowerCase() === "none") {
52271
+ resolvedPromise = null;
52272
+ } else {
52273
+ resolvedPromise = completionPromise;
52274
+ }
52275
+ setGlobalState({
52276
+ isActive: true,
52277
+ isYolo,
52278
+ originalPrompt: prompt,
52279
+ completionPromise: resolvedPromise,
52280
+ maxIterations,
52281
+ currentIteration: 1
52282
+ });
52283
+ }
52284
+ deactivate() {
52285
+ setGlobalState(getDefaultState());
52286
+ }
52287
+ getState() {
52288
+ return getGlobalState();
52289
+ }
52290
+ incrementIteration() {
52291
+ const state = getGlobalState();
52292
+ setGlobalState({
52293
+ ...state,
52294
+ currentIteration: state.currentIteration + 1
52295
+ });
52296
+ }
52297
+ checkForPromise(text) {
52298
+ const state = getGlobalState();
52299
+ if (!state.completionPromise)
52300
+ return false;
52301
+ const match3 = text.match(/<promise>([\s\S]*?)<\/promise>/i);
52302
+ if (!match3 || match3[1] === undefined)
52303
+ return false;
52304
+ const promiseText = match3[1].trim().replace(/\s+/g, " ");
52305
+ const expected = state.completionPromise.trim().replace(/\s+/g, " ");
52306
+ return promiseText === expected;
52307
+ }
52308
+ shouldContinue() {
52309
+ const state = getGlobalState();
52310
+ if (!state.isActive)
52311
+ return false;
52312
+ if (state.maxIterations > 0 && state.currentIteration >= state.maxIterations) {
52313
+ return false;
52314
+ }
52315
+ return true;
52316
+ }
52317
+ }
52318
+ var DEFAULT_COMPLETION_PROMISE, RALPH_KEY, ralphMode;
52319
+ var init_mode2 = __esm(() => {
52320
+ DEFAULT_COMPLETION_PROMISE = "The task is complete. All requirements have been implemented and verified working. " + "Any tests that were relevant have been run and are passing. The implementation is " + "clean and production-ready. I have not taken any shortcuts or faked anything to " + "meet these requirements.";
52321
+ RALPH_KEY = Symbol.for("@letta/ralphMode");
52322
+ ralphMode = new RalphModeManager;
52323
+ });
52324
+
52091
52325
  // src/settings.ts
52092
52326
  var exports_settings = {};
52093
52327
  __export(exports_settings, {
@@ -55419,9 +55653,9 @@ function getFileEditHeader(toolName, toolArgs) {
55419
55653
  const relPath = relative4(cwd2, filePath);
55420
55654
  const displayPath = relPath.startsWith("..") ? filePath : relPath;
55421
55655
  if (t === "write" || t === "write_file" || t === "writefile" || t === "write_file_gemini" || t === "writefilegemini") {
55422
- const { existsSync: existsSync9 } = __require("node:fs");
55656
+ const { existsSync: existsSync8 } = __require("node:fs");
55423
55657
  try {
55424
- if (existsSync9(filePath)) {
55658
+ if (existsSync8(filePath)) {
55425
55659
  return `Overwrite ${displayPath}?`;
55426
55660
  }
55427
55661
  } catch {}
@@ -56191,7 +56425,7 @@ var init_pasteRegistry = __esm(() => {
56191
56425
 
56192
56426
  // src/cli/helpers/clipboard.ts
56193
56427
  import { execFileSync as execFileSync2 } from "node:child_process";
56194
- import { existsSync as existsSync9, readFileSync as readFileSync2, statSync } from "node:fs";
56428
+ import { existsSync as existsSync8, readFileSync as readFileSync2, statSync } from "node:fs";
56195
56429
  import { basename as basename2, extname, isAbsolute as isAbsolute10, resolve as resolve16 } from "node:path";
56196
56430
  function countLines2(text) {
56197
56431
  return (text.match(/\r\n|\r|\n/g) || []).length + 1;
@@ -56242,7 +56476,7 @@ function translatePasteForImages(paste) {
56242
56476
  if (!isAbsolute10(filePath))
56243
56477
  filePath = resolve16(process.cwd(), filePath);
56244
56478
  const ext3 = extname(filePath || "").toLowerCase();
56245
- if (IMAGE_EXTS.has(ext3) && existsSync9(filePath) && statSync(filePath).isFile()) {
56479
+ if (IMAGE_EXTS.has(ext3) && existsSync8(filePath) && statSync(filePath).isFile()) {
56246
56480
  const buf = readFileSync2(filePath);
56247
56481
  const b64 = buf.toString("base64");
56248
56482
  const mt = ext3 === ".png" ? "image/png" : ext3 === ".jpg" || ext3 === ".jpeg" ? "image/jpeg" : ext3 === ".gif" ? "image/gif" : ext3 === ".webp" ? "image/webp" : ext3 === ".bmp" ? "image/bmp" : ext3 === ".svg" ? "image/svg+xml" : ext3 === ".tif" || ext3 === ".tiff" ? "image/tiff" : ext3 === ".heic" ? "image/heic" : ext3 === ".heif" ? "image/heif" : ext3 === ".avif" ? "image/avif" : "application/octet-stream";
@@ -56830,7 +57064,7 @@ __export(exports_terminalKeybindingInstaller, {
56830
57064
  });
56831
57065
  import {
56832
57066
  copyFileSync,
56833
- existsSync as existsSync10,
57067
+ existsSync as existsSync9,
56834
57068
  mkdirSync as mkdirSync3,
56835
57069
  readFileSync as readFileSync3,
56836
57070
  writeFileSync
@@ -56899,7 +57133,7 @@ function parseKeybindings(content) {
56899
57133
  }
56900
57134
  }
56901
57135
  function keybindingExists(keybindingsPath) {
56902
- if (!existsSync10(keybindingsPath))
57136
+ if (!existsSync9(keybindingsPath))
56903
57137
  return false;
56904
57138
  try {
56905
57139
  const content = readFileSync3(keybindingsPath, { encoding: "utf-8" });
@@ -56912,7 +57146,7 @@ function keybindingExists(keybindingsPath) {
56912
57146
  }
56913
57147
  }
56914
57148
  function createBackup(keybindingsPath) {
56915
- if (!existsSync10(keybindingsPath))
57149
+ if (!existsSync9(keybindingsPath))
56916
57150
  return null;
56917
57151
  const backupPath = `${keybindingsPath}.letta-backup`;
56918
57152
  try {
@@ -56928,12 +57162,12 @@ function installKeybinding(keybindingsPath) {
56928
57162
  return { success: true, alreadyExists: true };
56929
57163
  }
56930
57164
  const parentDir = dirname10(keybindingsPath);
56931
- if (!existsSync10(parentDir)) {
57165
+ if (!existsSync9(parentDir)) {
56932
57166
  mkdirSync3(parentDir, { recursive: true });
56933
57167
  }
56934
57168
  let keybindings = [];
56935
57169
  let backupPath = null;
56936
- if (existsSync10(keybindingsPath)) {
57170
+ if (existsSync9(keybindingsPath)) {
56937
57171
  backupPath = createBackup(keybindingsPath);
56938
57172
  const content = readFileSync3(keybindingsPath, { encoding: "utf-8" });
56939
57173
  const parsed = parseKeybindings(content);
@@ -56963,7 +57197,7 @@ function installKeybinding(keybindingsPath) {
56963
57197
  }
56964
57198
  function removeKeybinding(keybindingsPath) {
56965
57199
  try {
56966
- if (!existsSync10(keybindingsPath)) {
57200
+ if (!existsSync9(keybindingsPath)) {
56967
57201
  return { success: true };
56968
57202
  }
56969
57203
  const content = readFileSync3(keybindingsPath, { encoding: "utf-8" });
@@ -57031,16 +57265,16 @@ function getWezTermConfigPath() {
57031
57265
  const xdgConfig = process.env.XDG_CONFIG_HOME;
57032
57266
  if (xdgConfig) {
57033
57267
  const xdgPath = join14(xdgConfig, "wezterm", "wezterm.lua");
57034
- if (existsSync10(xdgPath))
57268
+ if (existsSync9(xdgPath))
57035
57269
  return xdgPath;
57036
57270
  }
57037
57271
  const configPath = join14(homedir7(), ".config", "wezterm", "wezterm.lua");
57038
- if (existsSync10(configPath))
57272
+ if (existsSync9(configPath))
57039
57273
  return configPath;
57040
57274
  return join14(homedir7(), ".wezterm.lua");
57041
57275
  }
57042
57276
  function wezTermDeleteFixExists(configPath) {
57043
- if (!existsSync10(configPath))
57277
+ if (!existsSync9(configPath))
57044
57278
  return false;
57045
57279
  try {
57046
57280
  const content = readFileSync3(configPath, { encoding: "utf-8" });
@@ -57057,7 +57291,7 @@ function installWezTermDeleteFix() {
57057
57291
  }
57058
57292
  let content = "";
57059
57293
  let backupPath = null;
57060
- if (existsSync10(configPath)) {
57294
+ if (existsSync9(configPath)) {
57061
57295
  backupPath = `${configPath}.letta-backup`;
57062
57296
  copyFileSync(configPath, backupPath);
57063
57297
  content = readFileSync3(configPath, { encoding: "utf-8" });
@@ -57087,7 +57321,7 @@ ${WEZTERM_DELETE_FIX}
57087
57321
  `;
57088
57322
  }
57089
57323
  const parentDir = dirname10(configPath);
57090
- if (!existsSync10(parentDir)) {
57324
+ if (!existsSync9(parentDir)) {
57091
57325
  mkdirSync3(parentDir, { recursive: true });
57092
57326
  }
57093
57327
  writeFileSync(configPath, content, { encoding: "utf-8" });
@@ -57393,6 +57627,20 @@ Location: ${keybindingsPath}`;
57393
57627
  return "Clearing credentials...";
57394
57628
  }
57395
57629
  },
57630
+ "/ralph": {
57631
+ desc: 'Start Ralph Wiggum loop (/ralph [prompt] [--completion-promise "X"] [--max-iterations N])',
57632
+ order: 45,
57633
+ handler: () => {
57634
+ return "Activating ralph mode...";
57635
+ }
57636
+ },
57637
+ "/yolo-ralph": {
57638
+ desc: "Start Ralph loop with bypass permissions (yolo + ralph)",
57639
+ order: 46,
57640
+ handler: () => {
57641
+ return "Activating yolo-ralph mode...";
57642
+ }
57643
+ },
57396
57644
  "/stream": {
57397
57645
  desc: "Toggle token streaming on/off",
57398
57646
  hidden: true,
@@ -57457,7 +57705,7 @@ __export(exports_custom, {
57457
57705
  GLOBAL_COMMANDS_DIR: () => GLOBAL_COMMANDS_DIR,
57458
57706
  COMMANDS_DIR: () => COMMANDS_DIR
57459
57707
  });
57460
- import { existsSync as existsSync11 } from "node:fs";
57708
+ import { existsSync as existsSync10 } from "node:fs";
57461
57709
  import { readdir as readdir7, readFile as readFile8 } from "node:fs/promises";
57462
57710
  import { basename as basename3, dirname as dirname11, join as join15 } from "node:path";
57463
57711
  async function getCustomCommands() {
@@ -57491,7 +57739,7 @@ async function discoverCustomCommands(projectPath = join15(process.cwd(), COMMAN
57491
57739
  return result;
57492
57740
  }
57493
57741
  async function discoverFromDirectory(dirPath, source) {
57494
- if (!existsSync11(dirPath)) {
57742
+ if (!existsSync10(dirPath)) {
57495
57743
  return [];
57496
57744
  }
57497
57745
  const commands2 = [];
@@ -58239,9 +58487,9 @@ function getHeaderText(fileEdit) {
58239
58487
  const relPath = relative4(cwd2, fileEdit.filePath);
58240
58488
  const displayPath = relPath.startsWith("..") ? fileEdit.filePath : relPath;
58241
58489
  if (t === "write" || t === "write_file" || t === "writefile" || t === "write_file_gemini" || t === "writefilegemini") {
58242
- const { existsSync: existsSync12 } = __require("node:fs");
58490
+ const { existsSync: existsSync11 } = __require("node:fs");
58243
58491
  try {
58244
- if (existsSync12(fileEdit.filePath)) {
58492
+ if (existsSync11(fileEdit.filePath)) {
58245
58493
  return `Overwrite ${displayPath}?`;
58246
58494
  }
58247
58495
  } catch {}
@@ -63208,7 +63456,11 @@ function Input({
63208
63456
  currentModelProvider,
63209
63457
  messageQueue,
63210
63458
  onEnterQueueEditMode,
63211
- onEscapeCancel
63459
+ onEscapeCancel,
63460
+ ralphActive = false,
63461
+ ralphPending = false,
63462
+ ralphPendingYolo = false,
63463
+ onRalphExit
63212
63464
  }) {
63213
63465
  const [value, setValue] = import_react54.useState("");
63214
63466
  const [escapePressed, setEscapePressed] = import_react54.useState(false);
@@ -63330,6 +63582,10 @@ function Input({
63330
63582
  console.error(`[debug:InputRich] shift=${key.shift} tab=${key.tab} visible=${visible}`);
63331
63583
  }
63332
63584
  if (key.shift && key.tab) {
63585
+ if (ralphActive && onRalphExit) {
63586
+ onRalphExit();
63587
+ return;
63588
+ }
63333
63589
  const modes = [
63334
63590
  "default",
63335
63591
  "acceptEdits",
@@ -63546,6 +63802,32 @@ function Input({
63546
63802
  setCursorPos(selectedCommand.length);
63547
63803
  };
63548
63804
  const getModeInfo = () => {
63805
+ if (ralphPending) {
63806
+ if (ralphPendingYolo) {
63807
+ return {
63808
+ name: "yolo-ralph (waiting)",
63809
+ color: "#FF8C00"
63810
+ };
63811
+ }
63812
+ return {
63813
+ name: "ralph (waiting)",
63814
+ color: "#FEE19C"
63815
+ };
63816
+ }
63817
+ if (ralphActive) {
63818
+ const ralph = ralphMode.getState();
63819
+ const iterDisplay = ralph.maxIterations > 0 ? `${ralph.currentIteration}/${ralph.maxIterations}` : `${ralph.currentIteration}`;
63820
+ if (ralph.isYolo) {
63821
+ return {
63822
+ name: `yolo-ralph (iter ${iterDisplay})`,
63823
+ color: "#FF8C00"
63824
+ };
63825
+ }
63826
+ return {
63827
+ name: `ralph (iter ${iterDisplay})`,
63828
+ color: "#FEE19C"
63829
+ };
63830
+ }
63549
63831
  switch (currentMode) {
63550
63832
  case "acceptEdits":
63551
63833
  return { name: "accept edits", color: colors.status.processing };
@@ -63710,7 +63992,9 @@ function Input({
63710
63992
  dimColor: true,
63711
63993
  children: [
63712
63994
  " ",
63713
- "(shift+tab to cycle)"
63995
+ "(shift+tab to ",
63996
+ ralphActive || ralphPending ? "exit" : "cycle",
63997
+ ")"
63714
63998
  ]
63715
63999
  }, undefined, true, undefined, this)
63716
64000
  ]
@@ -63744,6 +64028,7 @@ var init_InputRich = __esm(async () => {
63744
64028
  init_oauth();
63745
64029
  init_constants();
63746
64030
  init_mode();
64031
+ init_mode2();
63747
64032
  init_useTerminalWidth();
63748
64033
  init_colors();
63749
64034
  await __promiseAll([
@@ -70372,7 +70657,7 @@ var exports_App = {};
70372
70657
  __export(exports_App, {
70373
70658
  default: () => App2
70374
70659
  });
70375
- import { existsSync as existsSync12, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
70660
+ import { existsSync as existsSync11, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
70376
70661
  function uid4(prefix) {
70377
70662
  return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
70378
70663
  }
@@ -70470,7 +70755,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
70470
70755
  }
70471
70756
  function planFileExists() {
70472
70757
  const planFilePath = permissionMode2.getPlanFilePath();
70473
- return !!planFilePath && existsSync12(planFilePath);
70758
+ return !!planFilePath && existsSync11(planFilePath);
70474
70759
  }
70475
70760
  function getQuestionsFromApproval(approval) {
70476
70761
  const parsed = safeJsonParseOr(approval.toolArgs, {});
@@ -70484,6 +70769,71 @@ function getSkillUnloadReminder() {
70484
70769
  }
70485
70770
  return "";
70486
70771
  }
70772
+ function parseRalphArgs(input) {
70773
+ let rest = input.replace(/^\/(yolo-)?ralph\s*/, "");
70774
+ let completionPromise;
70775
+ const promiseMatch = rest.match(/--completion-promise\s+["']([^"']*)["']/);
70776
+ if (promiseMatch) {
70777
+ const val = promiseMatch[1] ?? "";
70778
+ completionPromise = val === "" || val.toLowerCase() === "none" ? null : val;
70779
+ rest = rest.replace(/--completion-promise\s+["'][^"']*["']\s*/, "");
70780
+ }
70781
+ const maxMatch = rest.match(/--max-iterations\s+(\d+)/);
70782
+ const maxIterations = maxMatch?.[1] ? parseInt(maxMatch[1], 10) : 0;
70783
+ rest = rest.replace(/--max-iterations\s+\d+\s*/, "");
70784
+ const prompt = rest.trim().replace(/^["']|["']$/g, "") || null;
70785
+ return { prompt, completionPromise, maxIterations };
70786
+ }
70787
+ function buildRalphFirstTurnReminder(state) {
70788
+ const iterInfo = state.maxIterations > 0 ? `${state.currentIteration}/${state.maxIterations}` : `${state.currentIteration}`;
70789
+ let reminder = `<system-reminder>
70790
+ \uD83D\uDD04 Ralph Wiggum mode activated (iteration ${iterInfo})
70791
+ `;
70792
+ if (state.completionPromise) {
70793
+ reminder += `
70794
+ ═══════════════════════════════════════════════════════════
70795
+ RALPH LOOP COMPLETION PROMISE
70796
+ ═══════════════════════════════════════════════════════════
70797
+
70798
+ To complete this loop, output this EXACT text:
70799
+ <promise>${state.completionPromise}</promise>
70800
+
70801
+ STRICT REQUIREMENTS (DO NOT VIOLATE):
70802
+ ✓ Use <promise> XML tags EXACTLY as shown above
70803
+ ✓ The statement MUST be completely and unequivocally TRUE
70804
+ ✓ Do NOT output false statements to exit the loop
70805
+ ✓ Do NOT lie even if you think you should exit
70806
+
70807
+ IMPORTANT - Do not circumvent the loop:
70808
+ Even if you believe you're stuck, the task is impossible,
70809
+ or you've been running too long - you MUST NOT output a
70810
+ false promise statement. The loop is designed to continue
70811
+ until the promise is GENUINELY TRUE. Trust the process.
70812
+
70813
+ If the loop should stop, the promise statement will become
70814
+ true naturally. Do not force it by lying.
70815
+ ═══════════════════════════════════════════════════════════
70816
+ `;
70817
+ } else {
70818
+ reminder += `
70819
+ No completion promise set - loop runs until --max-iterations or ESC/Shift+Tab to exit.
70820
+ `;
70821
+ }
70822
+ reminder += `</system-reminder>`;
70823
+ return reminder;
70824
+ }
70825
+ function buildRalphContinuationReminder(state) {
70826
+ const iterInfo = state.maxIterations > 0 ? `${state.currentIteration}/${state.maxIterations}` : `${state.currentIteration}`;
70827
+ if (state.completionPromise) {
70828
+ return `<system-reminder>
70829
+ \uD83D\uDD04 Ralph iteration ${iterInfo} | To stop: output <promise>${state.completionPromise}</promise> (ONLY when statement is TRUE - do not lie to exit!)
70830
+ </system-reminder>`;
70831
+ } else {
70832
+ return `<system-reminder>
70833
+ \uD83D\uDD04 Ralph iteration ${iterInfo} | No completion promise set - loop runs infinitely
70834
+ </system-reminder>`;
70835
+ }
70836
+ }
70487
70837
  function App2({
70488
70838
  agentId: initialAgentId,
70489
70839
  agentState: initialAgentState,
@@ -70528,6 +70878,7 @@ function App2({
70528
70878
  }, [agentId]);
70529
70879
  const [streaming, setStreaming, streamingRef] = useSyncedState(false);
70530
70880
  const processingConversationRef = import_react76.useRef(0);
70881
+ const conversationGenerationRef = import_react76.useRef(0);
70531
70882
  const [interruptRequested, setInterruptRequested] = import_react76.useState(false);
70532
70883
  const [commandRunning, setCommandRunning, commandRunningRef] = useSyncedState(false);
70533
70884
  const [profileConfirmPending, setProfileConfirmPending] = import_react76.useState(null);
@@ -70540,6 +70891,8 @@ function App2({
70540
70891
  const [autoHandledResults, setAutoHandledResults] = import_react76.useState([]);
70541
70892
  const [autoDeniedApprovals, setAutoDeniedApprovals] = import_react76.useState([]);
70542
70893
  const bashCommandCacheRef = import_react76.useRef([]);
70894
+ const [pendingRalphConfig, setPendingRalphConfig] = import_react76.useState(null);
70895
+ const [uiRalphActive, setUiRalphActive] = import_react76.useState(ralphMode.getState().isActive);
70543
70896
  const currentApproval = pendingApprovals[approvalResults.length];
70544
70897
  const currentApprovalContext = approvalContexts[approvalResults.length];
70545
70898
  const activeApprovalId = currentApproval?.toolCallId ?? null;
@@ -70791,8 +71144,8 @@ function App2({
70791
71144
  if (!planFilePath)
70792
71145
  return;
70793
71146
  try {
70794
- const { readFileSync: readFileSync5, existsSync: existsSync13 } = __require("node:fs");
70795
- if (!existsSync13(planFilePath))
71147
+ const { readFileSync: readFileSync5, existsSync: existsSync12 } = __require("node:fs");
71148
+ if (!existsSync12(planFilePath))
70796
71149
  return;
70797
71150
  const planContent = readFileSync5(planFilePath, "utf-8");
70798
71151
  const previewItem = {
@@ -70915,8 +71268,70 @@ function App2({
70915
71268
  }
70916
71269
  }, [refreshDerived, currentModelId]);
70917
71270
  const processConversation = import_react76.useCallback(async (initialInput, options) => {
71271
+ const handleRalphContinuation = () => {
71272
+ const ralphState = ralphMode.getState();
71273
+ const lines2 = toLines(buffersRef.current);
71274
+ const assistantLines = lines2.filter((l) => l.kind === "assistant");
71275
+ const lastAssistantText = assistantLines.length > 0 ? assistantLines[assistantLines.length - 1]?.text ?? "" : "";
71276
+ if (ralphMode.checkForPromise(lastAssistantText)) {
71277
+ const wasYolo = ralphState.isYolo;
71278
+ ralphMode.deactivate();
71279
+ setUiRalphActive(false);
71280
+ if (wasYolo) {
71281
+ permissionMode2.setMode("default");
71282
+ }
71283
+ const statusId = uid4("status");
71284
+ buffersRef.current.byId.set(statusId, {
71285
+ kind: "status",
71286
+ id: statusId,
71287
+ lines: [
71288
+ `✅ Ralph loop complete: promise detected after ${ralphState.currentIteration} iteration(s)`
71289
+ ]
71290
+ });
71291
+ buffersRef.current.order.push(statusId);
71292
+ refreshDerived();
71293
+ return;
71294
+ }
71295
+ if (!ralphMode.shouldContinue()) {
71296
+ const wasYolo = ralphState.isYolo;
71297
+ ralphMode.deactivate();
71298
+ setUiRalphActive(false);
71299
+ if (wasYolo) {
71300
+ permissionMode2.setMode("default");
71301
+ }
71302
+ const statusId = uid4("status");
71303
+ buffersRef.current.byId.set(statusId, {
71304
+ kind: "status",
71305
+ id: statusId,
71306
+ lines: [
71307
+ `\uD83D\uDED1 Ralph loop: Max iterations (${ralphState.maxIterations}) reached`
71308
+ ]
71309
+ });
71310
+ buffersRef.current.order.push(statusId);
71311
+ refreshDerived();
71312
+ return;
71313
+ }
71314
+ ralphMode.incrementIteration();
71315
+ const newState = ralphMode.getState();
71316
+ const systemMsg = buildRalphContinuationReminder(newState);
71317
+ setTimeout(() => {
71318
+ processConversation([
71319
+ {
71320
+ type: "message",
71321
+ role: "user",
71322
+ content: `${systemMsg}
71323
+
71324
+ ${newState.originalPrompt}`
71325
+ }
71326
+ ], { allowReentry: true });
71327
+ }, 0);
71328
+ };
70918
71329
  const currentInput = [...initialInput];
70919
71330
  const allowReentry = options?.allowReentry ?? false;
71331
+ const myGeneration = options?.submissionGeneration ?? conversationGenerationRef.current;
71332
+ if (myGeneration !== conversationGenerationRef.current) {
71333
+ return;
71334
+ }
70920
71335
  if (processingConversationRef.current > 0 && !allowReentry) {
70921
71336
  return;
70922
71337
  }
@@ -70930,20 +71345,29 @@ function App2({
70930
71345
  userCancelledRef.current = false;
70931
71346
  return;
70932
71347
  }
71348
+ if (myGeneration !== conversationGenerationRef.current) {
71349
+ return;
71350
+ }
70933
71351
  setStreaming(true);
70934
71352
  abortControllerRef.current = new AbortController;
70935
- markIncompleteToolsAsCancelled(buffersRef.current);
71353
+ markIncompleteToolsAsCancelled(buffersRef.current, false);
70936
71354
  buffersRef.current.interrupted = false;
70937
71355
  clearCompletedSubagents();
70938
71356
  while (true) {
70939
71357
  const signal = abortControllerRef.current?.signal;
70940
71358
  if (signal?.aborted) {
70941
- setStreaming(false);
71359
+ const isStaleAtAbort = myGeneration !== conversationGenerationRef.current;
71360
+ if (!isStaleAtAbort) {
71361
+ setStreaming(false);
71362
+ }
70942
71363
  return;
70943
71364
  }
70944
71365
  const stream2 = await sendMessageStream(agentIdRef.current, currentInput);
70945
71366
  if (signal?.aborted) {
70946
- setStreaming(false);
71367
+ const isStaleAtAbort = myGeneration !== conversationGenerationRef.current;
71368
+ if (!isStaleAtAbort) {
71369
+ setStreaming(false);
71370
+ }
70947
71371
  return;
70948
71372
  }
70949
71373
  const syncAgentState = async () => {
@@ -70987,6 +71411,10 @@ function App2({
70987
71411
  const wasInterrupted = !!buffersRef.current.interrupted;
70988
71412
  const wasAborted = !!signal?.aborted;
70989
71413
  let stopReasonToHandle = wasAborted ? "cancelled" : stopReason;
71414
+ const isStaleAfterDrain = myGeneration !== conversationGenerationRef.current;
71415
+ if (isStaleAfterDrain) {
71416
+ return;
71417
+ }
70990
71418
  if (!wasInterrupted) {
70991
71419
  refreshDerived();
70992
71420
  }
@@ -71013,6 +71441,10 @@ function App2({
71013
71441
  waitingForQueueCancelRef.current = false;
71014
71442
  queueSnapshotRef.current = [];
71015
71443
  }
71444
+ if (ralphMode.getState().isActive) {
71445
+ handleRalphContinuation();
71446
+ return;
71447
+ }
71016
71448
  return;
71017
71449
  }
71018
71450
  if (stopReasonToHandle === "cancelled") {
@@ -71034,6 +71466,18 @@ function App2({
71034
71466
  if (!EAGER_CANCEL) {
71035
71467
  appendError(INTERRUPT_MESSAGE, true);
71036
71468
  }
71469
+ if (ralphMode.getState().isActive) {
71470
+ const statusId = uid4("status");
71471
+ buffersRef.current.byId.set(statusId, {
71472
+ kind: "status",
71473
+ id: statusId,
71474
+ lines: [
71475
+ `⏸️ Ralph loop paused - type to continue or shift+tab to exit`
71476
+ ]
71477
+ });
71478
+ buffersRef.current.order.push(statusId);
71479
+ refreshDerived();
71480
+ }
71037
71481
  }
71038
71482
  return;
71039
71483
  }
@@ -71306,7 +71750,8 @@ function App2({
71306
71750
  sendDesktopNotification();
71307
71751
  return;
71308
71752
  }
71309
- const isApprovalPayload = currentInput.length === 1 && currentInput[0]?.type === "approval";
71753
+ const hasApprovalInPayload = currentInput.some((item) => item?.type === "approval");
71754
+ const isApprovalOnlyPayload = hasApprovalInPayload && currentInput.length === 1;
71310
71755
  let latestErrorText = null;
71311
71756
  for (let i = buffersRef.current.order.length - 1;i >= 0; i -= 1) {
71312
71757
  const id = buffersRef.current.order[i];
@@ -71321,7 +71766,7 @@ function App2({
71321
71766
  const detailFromRun = await fetchRunErrorDetail(lastRunId);
71322
71767
  const desyncDetected = isApprovalStateDesyncError(detailFromRun) || isApprovalStateDesyncError(latestErrorText);
71323
71768
  const lastFailureMessage = latestErrorText || detailFromRun || null;
71324
- if (isApprovalPayload && desyncDetected) {
71769
+ if (hasApprovalInPayload && desyncDetected) {
71325
71770
  if (llmApiErrorRetriesRef.current < LLM_API_ERROR_MAX_RETRIES2) {
71326
71771
  llmApiErrorRetriesRef.current += 1;
71327
71772
  const statusId = uid4("status");
@@ -71334,7 +71779,16 @@ function App2({
71334
71779
  });
71335
71780
  buffersRef.current.order.push(statusId);
71336
71781
  refreshDerived();
71337
- currentInput.splice(0, currentInput.length, buildApprovalRecoveryMessage());
71782
+ if (isApprovalOnlyPayload) {
71783
+ currentInput.splice(0, currentInput.length, buildApprovalRecoveryMessage());
71784
+ } else {
71785
+ const messageItems = currentInput.filter((item) => item?.type !== "approval");
71786
+ if (messageItems.length > 0) {
71787
+ currentInput.splice(0, currentInput.length, ...messageItems);
71788
+ } else {
71789
+ currentInput.splice(0, currentInput.length, buildApprovalRecoveryMessage());
71790
+ }
71791
+ }
71338
71792
  buffersRef.current.byId.delete(statusId);
71339
71793
  buffersRef.current.order = buffersRef.current.order.filter((id) => id !== statusId);
71340
71794
  refreshDerived();
@@ -71451,8 +71905,11 @@ function App2({
71451
71905
  sendDesktopNotification();
71452
71906
  refreshDerived();
71453
71907
  } finally {
71908
+ const isStale = myGeneration !== conversationGenerationRef.current;
71454
71909
  abortControllerRef.current = null;
71455
- processingConversationRef.current = Math.max(0, processingConversationRef.current - 1);
71910
+ if (!isStale) {
71911
+ processingConversationRef.current = Math.max(0, processingConversationRef.current - 1);
71912
+ }
71456
71913
  }
71457
71914
  }, [
71458
71915
  appendError,
@@ -71495,8 +71952,9 @@ function App2({
71495
71952
  }, 50);
71496
71953
  return;
71497
71954
  }
71498
- if (!streaming || interruptRequested)
71955
+ if (!streaming || interruptRequested) {
71499
71956
  return;
71957
+ }
71500
71958
  if (waitingForQueueCancelRef.current) {
71501
71959
  setRestoreQueueOnCancel(true);
71502
71960
  }
@@ -71509,11 +71967,22 @@ function App2({
71509
71967
  abortControllerRef.current = null;
71510
71968
  }
71511
71969
  userCancelledRef.current = true;
71970
+ conversationGenerationRef.current += 1;
71971
+ processingConversationRef.current = 0;
71512
71972
  setStreaming(false);
71513
71973
  if (!toolsCancelled) {
71514
71974
  appendError(INTERRUPT_MESSAGE, true);
71515
71975
  }
71516
71976
  refreshDerived();
71977
+ if (pendingApprovals.length > 0) {
71978
+ const denialResults = pendingApprovals.map((approval) => ({
71979
+ type: "approval",
71980
+ tool_call_id: approval.toolCallId,
71981
+ approve: false,
71982
+ reason: "User interrupted the stream"
71983
+ }));
71984
+ setQueuedApprovalResults(denialResults);
71985
+ }
71517
71986
  setPendingApprovals([]);
71518
71987
  setApprovalContexts([]);
71519
71988
  setApprovalResults([]);
@@ -71546,7 +72015,8 @@ function App2({
71546
72015
  appendError,
71547
72016
  isExecutingTool,
71548
72017
  refreshDerived,
71549
- setStreaming
72018
+ setStreaming,
72019
+ pendingApprovals
71550
72020
  ]);
71551
72021
  const processConversationRef = import_react76.useRef(processConversation);
71552
72022
  import_react76.useEffect(() => {
@@ -71761,6 +72231,96 @@ function App2({
71761
72231
  }
71762
72232
  refreshDerived();
71763
72233
  }, [refreshDerived]);
72234
+ const checkPendingApprovalsForSlashCommand = import_react76.useCallback(async () => {
72235
+ if (!CHECK_PENDING_APPROVALS_BEFORE_SEND) {
72236
+ return { blocked: false };
72237
+ }
72238
+ try {
72239
+ const client = await getClient2();
72240
+ const agent = await client.agents.retrieve(agentId);
72241
+ const { pendingApprovals: existingApprovals } = await getResumeData2(client, agent);
72242
+ if (!existingApprovals || existingApprovals.length === 0) {
72243
+ return { blocked: false };
72244
+ }
72245
+ const approvalResults2 = await Promise.all(existingApprovals.map(async (approvalItem) => {
72246
+ if (!approvalItem.toolName) {
72247
+ return {
72248
+ approval: approvalItem,
72249
+ permission: {
72250
+ decision: "deny",
72251
+ reason: "Tool call incomplete - missing name"
72252
+ },
72253
+ context: null
72254
+ };
72255
+ }
72256
+ const parsedArgs = safeJsonParseOr(approvalItem.toolArgs, {});
72257
+ const permission = await checkToolPermission(approvalItem.toolName, parsedArgs);
72258
+ const context3 = await analyzeToolApproval(approvalItem.toolName, parsedArgs);
72259
+ return { approval: approvalItem, permission, context: context3 };
72260
+ }));
72261
+ const needsUserInput = [];
72262
+ const autoAllowed = [];
72263
+ const autoDenied = [];
72264
+ for (const ac of approvalResults2) {
72265
+ const { approval, permission } = ac;
72266
+ let decision = permission.decision;
72267
+ if (alwaysRequiresUserInput(approval.toolName) && decision === "allow") {
72268
+ decision = "ask";
72269
+ }
72270
+ if (decision === "ask") {
72271
+ needsUserInput.push(ac);
72272
+ } else if (decision === "deny") {
72273
+ autoDenied.push(ac);
72274
+ } else {
72275
+ autoAllowed.push(ac);
72276
+ }
72277
+ }
72278
+ if (needsUserInput.length > 0) {
72279
+ setPendingApprovals(needsUserInput.map((ac) => ac.approval));
72280
+ setApprovalContexts(needsUserInput.map((ac) => ac.context).filter((ctx) => ctx !== null));
72281
+ return { blocked: true };
72282
+ }
72283
+ const allResults = [];
72284
+ if (autoAllowed.length > 0) {
72285
+ const autoAllowedResults = await executeAutoAllowedTools(autoAllowed, (chunk) => onChunk(buffersRef.current, chunk));
72286
+ allResults.push(...autoAllowedResults.map((ar) => ({
72287
+ type: "tool",
72288
+ tool_call_id: ar.toolCallId,
72289
+ tool_return: ar.result.toolReturn,
72290
+ status: ar.result.status,
72291
+ stdout: ar.result.stdout,
72292
+ stderr: ar.result.stderr
72293
+ })));
72294
+ }
72295
+ for (const ac of autoDenied) {
72296
+ const reason = ac.permission.reason || "Permission denied";
72297
+ onChunk(buffersRef.current, {
72298
+ message_type: "tool_return_message",
72299
+ id: "dummy",
72300
+ date: new Date().toISOString(),
72301
+ tool_call_id: ac.approval.toolCallId,
72302
+ tool_return: `Error: request to call tool denied. User reason: ${reason}`,
72303
+ status: "error",
72304
+ stdout: null,
72305
+ stderr: null
72306
+ });
72307
+ allResults.push({
72308
+ type: "approval",
72309
+ tool_call_id: ac.approval.toolCallId,
72310
+ approve: false,
72311
+ reason
72312
+ });
72313
+ }
72314
+ if (allResults.length > 0) {
72315
+ await processConversation([
72316
+ { type: "approval", approvals: allResults }
72317
+ ]);
72318
+ }
72319
+ return { blocked: false };
72320
+ } catch {
72321
+ return { blocked: false };
72322
+ }
72323
+ }, [agentId, processConversation]);
71764
72324
  const onSubmit = import_react76.useCallback(async (message) => {
71765
72325
  const msg = message?.trim() ?? "";
71766
72326
  if (profileConfirmPending && !msg) {
@@ -71789,6 +72349,7 @@ function App2({
71789
72349
  }
71790
72350
  if (!msg)
71791
72351
  return { submitted: false };
72352
+ const submissionGeneration = conversationGenerationRef.current;
71792
72353
  telemetry2.trackUserInput(msg, "user", currentModelId || "unknown");
71793
72354
  if (pendingApprovals.length > 0) {
71794
72355
  return { submitted: false };
@@ -71801,6 +72362,9 @@ function App2({
71801
72362
  if (!isSlashCommand && streamingRef.current && !waitingForQueueCancelRef.current) {
71802
72363
  waitingForQueueCancelRef.current = true;
71803
72364
  queueSnapshotRef.current = [...newQueue];
72365
+ if (toolAbortControllerRef.current) {
72366
+ toolAbortControllerRef.current.abort();
72367
+ }
71804
72368
  getClient2().then((client) => client.agents.messages.cancel(agentId)).then(() => {}).catch(() => {
71805
72369
  waitingForQueueCancelRef.current = false;
71806
72370
  });
@@ -71809,6 +72373,30 @@ function App2({
71809
72373
  });
71810
72374
  return { submitted: true };
71811
72375
  }
72376
+ let justActivatedRalph = false;
72377
+ if (pendingRalphConfig && !msg.startsWith("/")) {
72378
+ const { completionPromise, maxIterations, isYolo } = pendingRalphConfig;
72379
+ ralphMode.activate(msg, completionPromise, maxIterations, isYolo);
72380
+ setUiRalphActive(true);
72381
+ setPendingRalphConfig(null);
72382
+ justActivatedRalph = true;
72383
+ if (isYolo) {
72384
+ permissionMode2.setMode("bypassPermissions");
72385
+ }
72386
+ const ralphState = ralphMode.getState();
72387
+ const statusId = uid4("status");
72388
+ const promiseDisplay = ralphState.completionPromise ? `"${ralphState.completionPromise.slice(0, 50)}${ralphState.completionPromise.length > 50 ? "..." : ""}"` : "(none)";
72389
+ buffersRef.current.byId.set(statusId, {
72390
+ kind: "status",
72391
+ id: statusId,
72392
+ lines: [
72393
+ `\uD83D\uDD04 ${isYolo ? "yolo-ralph" : "ralph"} mode started (iter 1/${maxIterations || "∞"})`,
72394
+ `Promise: ${promiseDisplay}`
72395
+ ]
72396
+ });
72397
+ buffersRef.current.order.push(statusId);
72398
+ refreshDerived();
72399
+ }
71812
72400
  let aliasedMsg = msg;
71813
72401
  if (msg === "exit" || msg === "quit") {
71814
72402
  aliasedMsg = "/exit";
@@ -72029,6 +72617,59 @@ Tip: Use /clear instead to clear the current message buffer.`;
72029
72617
  }
72030
72618
  return { submitted: true };
72031
72619
  }
72620
+ if (trimmed.startsWith("/yolo-ralph") || trimmed.startsWith("/ralph")) {
72621
+ const isYolo = trimmed.startsWith("/yolo-ralph");
72622
+ const { prompt, completionPromise, maxIterations } = parseRalphArgs(trimmed);
72623
+ const cmdId2 = uid4("cmd");
72624
+ if (prompt) {
72625
+ ralphMode.activate(prompt, completionPromise, maxIterations, isYolo);
72626
+ setUiRalphActive(true);
72627
+ if (isYolo) {
72628
+ permissionMode2.setMode("bypassPermissions");
72629
+ }
72630
+ const ralphState = ralphMode.getState();
72631
+ const promiseDisplay = ralphState.completionPromise ? `"${ralphState.completionPromise.slice(0, 50)}${ralphState.completionPromise.length > 50 ? "..." : ""}"` : "(none)";
72632
+ buffersRef.current.byId.set(cmdId2, {
72633
+ kind: "command",
72634
+ id: cmdId2,
72635
+ input: trimmed,
72636
+ output: `\uD83D\uDD04 ${isYolo ? "yolo-ralph" : "ralph"} mode activated (iter 1/${maxIterations || "∞"})
72637
+ Promise: ${promiseDisplay}`,
72638
+ phase: "finished",
72639
+ success: true
72640
+ });
72641
+ buffersRef.current.order.push(cmdId2);
72642
+ refreshDerived();
72643
+ const systemMsg = buildRalphFirstTurnReminder(ralphState);
72644
+ processConversation([
72645
+ {
72646
+ type: "message",
72647
+ role: "user",
72648
+ content: `${systemMsg}
72649
+
72650
+ ${prompt}`
72651
+ }
72652
+ ]);
72653
+ } else {
72654
+ setPendingRalphConfig({ completionPromise, maxIterations, isYolo });
72655
+ const defaultPromisePreview = DEFAULT_COMPLETION_PROMISE.slice(0, 40);
72656
+ buffersRef.current.byId.set(cmdId2, {
72657
+ kind: "command",
72658
+ id: cmdId2,
72659
+ input: trimmed,
72660
+ output: `\uD83D\uDD04 ${isYolo ? "yolo-ralph" : "ralph"} mode ready (waiting for task)
72661
+ Max iterations: ${maxIterations || "unlimited"}
72662
+ Promise: ${completionPromise === null ? "(none)" : completionPromise ?? `"${defaultPromisePreview}..." (default)`}
72663
+
72664
+ Type your task to begin the loop.`,
72665
+ phase: "finished",
72666
+ success: true
72667
+ });
72668
+ buffersRef.current.order.push(cmdId2);
72669
+ refreshDerived();
72670
+ }
72671
+ return { submitted: true };
72672
+ }
72032
72673
  if (msg.trim() === "/stream") {
72033
72674
  const newValue = !tokenStreamingEnabled;
72034
72675
  const cmdId2 = uid4("cmd");
@@ -72479,6 +73120,10 @@ Press Enter to continue, or type anything to cancel.`, false, "running");
72479
73120
  return { submitted: true };
72480
73121
  }
72481
73122
  if (trimmed.startsWith("/skill")) {
73123
+ const approvalCheck = await checkPendingApprovalsForSlashCommand();
73124
+ if (approvalCheck.blocked) {
73125
+ return { submitted: false };
73126
+ }
72482
73127
  const cmdId2 = uid4("cmd");
72483
73128
  const [, ...rest] = trimmed.split(/\s+/);
72484
73129
  const description = rest.join(" ").trim();
@@ -72537,6 +73182,10 @@ ${SKILL_CREATOR_PROMPT3}${userDescriptionLine}
72537
73182
  return { submitted: true };
72538
73183
  }
72539
73184
  if (trimmed.startsWith("/remember")) {
73185
+ const approvalCheck = await checkPendingApprovalsForSlashCommand();
73186
+ if (approvalCheck.blocked) {
73187
+ return { submitted: false };
73188
+ }
72540
73189
  const cmdId2 = uid4("cmd");
72541
73190
  const [, ...rest] = trimmed.split(/\s+/);
72542
73191
  const userText = rest.join(" ").trim();
@@ -72593,6 +73242,10 @@ The user did not specify what to remember. Look at the recent conversation conte
72593
73242
  return { submitted: true };
72594
73243
  }
72595
73244
  if (trimmed === "/init") {
73245
+ const approvalCheck = await checkPendingApprovalsForSlashCommand();
73246
+ if (approvalCheck.blocked) {
73247
+ return { submitted: false };
73248
+ }
72596
73249
  const cmdId2 = uid4("cmd");
72597
73250
  buffersRef.current.byId.set(cmdId2, {
72598
73251
  kind: "command",
@@ -72709,6 +73362,10 @@ ${gitContext}
72709
73362
  const commandName = trimmed.split(/\s+/)[0]?.slice(1) || "";
72710
73363
  const matchedCustom = await findCustomCommand2(commandName);
72711
73364
  if (matchedCustom) {
73365
+ const approvalCheck = await checkPendingApprovalsForSlashCommand();
73366
+ if (approvalCheck.blocked) {
73367
+ return { submitted: false };
73368
+ }
72712
73369
  const cmdId2 = uid4("cmd");
72713
73370
  const args = trimmed.slice(`/${matchedCustom.id}`.length).trim();
72714
73371
  let prompt = substituteArguments2(matchedCustom.content, args);
@@ -72799,6 +73456,21 @@ ${prompt}
72799
73456
  }
72800
73457
  const contentParts = buildMessageContentFromDisplay(msg);
72801
73458
  const planModeReminder = getPlanModeReminder();
73459
+ let ralphModeReminder = "";
73460
+ if (ralphMode.getState().isActive) {
73461
+ if (justActivatedRalph) {
73462
+ const ralphState = ralphMode.getState();
73463
+ ralphModeReminder = `${buildRalphFirstTurnReminder(ralphState)}
73464
+
73465
+ `;
73466
+ } else {
73467
+ ralphMode.incrementIteration();
73468
+ const ralphState = ralphMode.getState();
73469
+ ralphModeReminder = `${buildRalphContinuationReminder(ralphState)}
73470
+
73471
+ `;
73472
+ }
73473
+ }
72802
73474
  const skillUnloadReminder = getSkillUnloadReminder();
72803
73475
  let sessionContextReminder = "";
72804
73476
  const sessionContextEnabled = settingsManager.getSetting("sessionContextEnabled");
@@ -72830,7 +73502,7 @@ DO NOT respond to these messages or otherwise consider them in your response unl
72830
73502
  }
72831
73503
  const memoryReminderContent = await buildMemoryReminder(turnCountRef.current);
72832
73504
  turnCountRef.current += 1;
72833
- const allReminders = sessionContextReminder + planModeReminder + skillUnloadReminder + bashCommandPrefix + memoryReminderContent;
73505
+ const allReminders = sessionContextReminder + planModeReminder + ralphModeReminder + skillUnloadReminder + bashCommandPrefix + memoryReminderContent;
72834
73506
  const messageContent = allReminders && typeof contentParts === "string" ? allReminders + contentParts : Array.isArray(contentParts) && allReminders ? [{ type: "text", text: allReminders }, ...contentParts] : contentParts;
72835
73507
  const userId = uid4("user");
72836
73508
  buffersRef.current.byId.set(userId, {
@@ -73108,7 +73780,7 @@ DO NOT respond to these messages or otherwise consider them in your response unl
73108
73780
  role: "user",
73109
73781
  content: messageContent
73110
73782
  });
73111
- await processConversation(initialInput);
73783
+ await processConversation(initialInput, { submissionGeneration });
73112
73784
  clearPlaceholdersInText(msg);
73113
73785
  return { submitted: true };
73114
73786
  }, [
@@ -73129,7 +73801,8 @@ DO NOT respond to these messages or otherwise consider them in your response unl
73129
73801
  tokenStreamingEnabled,
73130
73802
  isAgentBusy,
73131
73803
  setStreaming,
73132
- setCommandRunning
73804
+ setCommandRunning,
73805
+ pendingRalphConfig
73133
73806
  ]);
73134
73807
  const onSubmitRef = import_react76.useRef(onSubmit);
73135
73808
  import_react76.useEffect(() => {
@@ -73230,6 +73903,8 @@ DO NOT respond to these messages or otherwise consider them in your response unl
73230
73903
  setQueuedApprovalResults(allResults);
73231
73904
  }
73232
73905
  setStreaming(false);
73906
+ waitingForQueueCancelRef.current = false;
73907
+ queueSnapshotRef.current = [];
73233
73908
  } else {
73234
73909
  await processConversation([
73235
73910
  {
@@ -73727,6 +74402,18 @@ Consider switching to a different system prompt using /system to match.` : null;
73727
74402
  }
73728
74403
  }, [profileConfirmPending, refreshDerived]);
73729
74404
  const [uiPermissionMode, setUiPermissionMode] = import_react76.useState(permissionMode2.getMode());
74405
+ const handleRalphExit = import_react76.useCallback(() => {
74406
+ const ralph = ralphMode.getState();
74407
+ if (ralph.isActive) {
74408
+ const wasYolo = ralph.isYolo;
74409
+ ralphMode.deactivate();
74410
+ setUiRalphActive(false);
74411
+ if (wasYolo) {
74412
+ permissionMode2.setMode("default");
74413
+ setUiPermissionMode("default");
74414
+ }
74415
+ }
74416
+ }, []);
73730
74417
  const handlePermissionModeChange = import_react76.useCallback((mode) => {
73731
74418
  if (mode === "plan") {
73732
74419
  const planPath = generatePlanFilePath();
@@ -74001,7 +74688,6 @@ Plan file path: ${planFilePath}`;
74001
74688
  ]);
74002
74689
  return /* @__PURE__ */ jsx_dev_runtime56.jsxDEV(Box_default, {
74003
74690
  flexDirection: "column",
74004
- gap: 1,
74005
74691
  children: [
74006
74692
  /* @__PURE__ */ jsx_dev_runtime56.jsxDEV(Static, {
74007
74693
  items: staticItems,
@@ -74047,7 +74733,6 @@ Plan file path: ${planFilePath}`;
74047
74733
  }, staticRenderEpoch, false, undefined, this),
74048
74734
  /* @__PURE__ */ jsx_dev_runtime56.jsxDEV(Box_default, {
74049
74735
  flexDirection: "column",
74050
- gap: 1,
74051
74736
  children: [
74052
74737
  loadingState !== "ready" && /* @__PURE__ */ jsx_dev_runtime56.jsxDEV(WelcomeScreen, {
74053
74738
  loadingState,
@@ -74242,7 +74927,7 @@ Plan file path: ${planFilePath}`;
74242
74927
  ]
74243
74928
  }, undefined, true, undefined, this),
74244
74929
  /* @__PURE__ */ jsx_dev_runtime56.jsxDEV(Box_default, {
74245
- marginTop: liveItems.length > 0 ? 0 : 1,
74930
+ marginTop: 1,
74246
74931
  children: /* @__PURE__ */ jsx_dev_runtime56.jsxDEV(Input, {
74247
74932
  visible: !showExitStats && pendingApprovals.length === 0 && !anySelectorOpen,
74248
74933
  streaming: streaming && !abortControllerRef.current?.signal.aborted,
@@ -74261,7 +74946,11 @@ Plan file path: ${planFilePath}`;
74261
74946
  currentModelProvider,
74262
74947
  messageQueue,
74263
74948
  onEnterQueueEditMode: handleEnterQueueEditMode,
74264
- onEscapeCancel: profileConfirmPending ? handleProfileEscapeCancel : undefined
74949
+ onEscapeCancel: profileConfirmPending ? handleProfileEscapeCancel : undefined,
74950
+ ralphActive: uiRalphActive,
74951
+ ralphPending: pendingRalphConfig !== null,
74952
+ ralphPendingYolo: pendingRalphConfig?.isYolo ?? false,
74953
+ onRalphExit: handleRalphExit
74265
74954
  }, undefined, false, undefined, this)
74266
74955
  }, undefined, false, undefined, this),
74267
74956
  activeOverlay === "model" && /* @__PURE__ */ jsx_dev_runtime56.jsxDEV(ModelSelector, {
@@ -74426,6 +75115,7 @@ var init_App2 = __esm(async () => {
74426
75115
  init_context();
74427
75116
  init_model();
74428
75117
  init_mode();
75118
+ init_mode2();
74429
75119
  init_settings();
74430
75120
  init_colors();
74431
75121
  init_SessionStats();
@@ -74514,7 +75204,7 @@ __export(exports_terminalKeybindingInstaller2, {
74514
75204
  });
74515
75205
  import {
74516
75206
  copyFileSync as copyFileSync2,
74517
- existsSync as existsSync13,
75207
+ existsSync as existsSync12,
74518
75208
  mkdirSync as mkdirSync4,
74519
75209
  readFileSync as readFileSync5,
74520
75210
  writeFileSync as writeFileSync3
@@ -74583,7 +75273,7 @@ function parseKeybindings2(content) {
74583
75273
  }
74584
75274
  }
74585
75275
  function keybindingExists2(keybindingsPath) {
74586
- if (!existsSync13(keybindingsPath))
75276
+ if (!existsSync12(keybindingsPath))
74587
75277
  return false;
74588
75278
  try {
74589
75279
  const content = readFileSync5(keybindingsPath, { encoding: "utf-8" });
@@ -74596,7 +75286,7 @@ function keybindingExists2(keybindingsPath) {
74596
75286
  }
74597
75287
  }
74598
75288
  function createBackup2(keybindingsPath) {
74599
- if (!existsSync13(keybindingsPath))
75289
+ if (!existsSync12(keybindingsPath))
74600
75290
  return null;
74601
75291
  const backupPath = `${keybindingsPath}.letta-backup`;
74602
75292
  try {
@@ -74612,12 +75302,12 @@ function installKeybinding2(keybindingsPath) {
74612
75302
  return { success: true, alreadyExists: true };
74613
75303
  }
74614
75304
  const parentDir = dirname12(keybindingsPath);
74615
- if (!existsSync13(parentDir)) {
75305
+ if (!existsSync12(parentDir)) {
74616
75306
  mkdirSync4(parentDir, { recursive: true });
74617
75307
  }
74618
75308
  let keybindings = [];
74619
75309
  let backupPath = null;
74620
- if (existsSync13(keybindingsPath)) {
75310
+ if (existsSync12(keybindingsPath)) {
74621
75311
  backupPath = createBackup2(keybindingsPath);
74622
75312
  const content = readFileSync5(keybindingsPath, { encoding: "utf-8" });
74623
75313
  const parsed = parseKeybindings2(content);
@@ -74647,7 +75337,7 @@ function installKeybinding2(keybindingsPath) {
74647
75337
  }
74648
75338
  function removeKeybinding2(keybindingsPath) {
74649
75339
  try {
74650
- if (!existsSync13(keybindingsPath)) {
75340
+ if (!existsSync12(keybindingsPath)) {
74651
75341
  return { success: true };
74652
75342
  }
74653
75343
  const content = readFileSync5(keybindingsPath, { encoding: "utf-8" });
@@ -74715,16 +75405,16 @@ function getWezTermConfigPath2() {
74715
75405
  const xdgConfig = process.env.XDG_CONFIG_HOME;
74716
75406
  if (xdgConfig) {
74717
75407
  const xdgPath = join17(xdgConfig, "wezterm", "wezterm.lua");
74718
- if (existsSync13(xdgPath))
75408
+ if (existsSync12(xdgPath))
74719
75409
  return xdgPath;
74720
75410
  }
74721
75411
  const configPath = join17(homedir8(), ".config", "wezterm", "wezterm.lua");
74722
- if (existsSync13(configPath))
75412
+ if (existsSync12(configPath))
74723
75413
  return configPath;
74724
75414
  return join17(homedir8(), ".wezterm.lua");
74725
75415
  }
74726
75416
  function wezTermDeleteFixExists2(configPath) {
74727
- if (!existsSync13(configPath))
75417
+ if (!existsSync12(configPath))
74728
75418
  return false;
74729
75419
  try {
74730
75420
  const content = readFileSync5(configPath, { encoding: "utf-8" });
@@ -74741,7 +75431,7 @@ function installWezTermDeleteFix2() {
74741
75431
  }
74742
75432
  let content = "";
74743
75433
  let backupPath = null;
74744
- if (existsSync13(configPath)) {
75434
+ if (existsSync12(configPath)) {
74745
75435
  backupPath = `${configPath}.letta-backup`;
74746
75436
  copyFileSync2(configPath, backupPath);
74747
75437
  content = readFileSync5(configPath, { encoding: "utf-8" });
@@ -74771,7 +75461,7 @@ ${WEZTERM_DELETE_FIX2}
74771
75461
  `;
74772
75462
  }
74773
75463
  const parentDir = dirname12(configPath);
74774
- if (!existsSync13(parentDir)) {
75464
+ if (!existsSync12(parentDir)) {
74775
75465
  mkdirSync4(parentDir, { recursive: true });
74776
75466
  }
74777
75467
  writeFileSync3(configPath, content, { encoding: "utf-8" });
@@ -75941,6 +76631,20 @@ class PermissionModeManager {
75941
76631
  return "allow";
75942
76632
  }
75943
76633
  }
76634
+ const readOnlySubagentTypes = new Set([
76635
+ "explore",
76636
+ "Explore",
76637
+ "plan",
76638
+ "Plan",
76639
+ "recall",
76640
+ "Recall"
76641
+ ]);
76642
+ if (toolName === "Task" || toolName === "task") {
76643
+ const subagentType = toolArgs?.subagent_type;
76644
+ if (subagentType && readOnlySubagentTypes.has(subagentType)) {
76645
+ return "allow";
76646
+ }
76647
+ }
75944
76648
  const shellTools = [
75945
76649
  "Bash",
75946
76650
  "shell",
@@ -77314,9 +78018,9 @@ Note: Flags should use double dashes for full names (e.g., --yolo, not -yolo)`);
77314
78018
  process.exit(1);
77315
78019
  }
77316
78020
  const { resolve: resolve19 } = await import("path");
77317
- const { existsSync: existsSync14 } = await import("fs");
78021
+ const { existsSync: existsSync13 } = await import("fs");
77318
78022
  const resolvedPath = resolve19(fromAfFile);
77319
- if (!existsSync14(resolvedPath)) {
78023
+ if (!existsSync13(resolvedPath)) {
77320
78024
  console.error(`Error: AgentFile not found: ${resolvedPath}`);
77321
78025
  process.exit(1);
77322
78026
  }
@@ -77556,13 +78260,19 @@ Error: ${message}`);
77556
78260
  try {
77557
78261
  await client.agents.retrieve(localProjectSettings.lastAgent);
77558
78262
  resumingAgentId = localProjectSettings.lastAgent;
77559
- } catch {}
78263
+ } catch {
78264
+ setLoadingState("selecting_global");
78265
+ return;
78266
+ }
77560
78267
  }
77561
78268
  if (!resumingAgentId && continueSession && settings.lastAgent) {
77562
78269
  try {
77563
78270
  await client.agents.retrieve(settings.lastAgent);
77564
78271
  resumingAgentId = settings.lastAgent;
77565
- } catch {}
78272
+ } catch {
78273
+ setLoadingState("selecting_global");
78274
+ return;
78275
+ }
77566
78276
  }
77567
78277
  if (!resumingAgentId && selectedGlobalAgentId) {
77568
78278
  try {
@@ -77617,22 +78327,22 @@ Error: ${message}`);
77617
78327
  agent = result.agent;
77618
78328
  setAgentProvenance(result.provenance);
77619
78329
  }
77620
- if (!agent) {
77621
- await settingsManager2.loadLocalProjectSettings();
77622
- const localProjectSettings = settingsManager2.getLocalProjectSettings();
77623
- if (localProjectSettings?.lastAgent) {
77624
- try {
77625
- agent = await client.agents.retrieve(localProjectSettings.lastAgent);
77626
- } catch (error) {
77627
- console.error(`Project agent ${localProjectSettings.lastAgent} not found (error: ${JSON.stringify(error)}), creating new one...`);
77628
- }
78330
+ if (!agent && resumingAgentId) {
78331
+ try {
78332
+ agent = await client.agents.retrieve(resumingAgentId);
78333
+ } catch (error) {
78334
+ console.error(`Agent ${resumingAgentId} not found (error: ${JSON.stringify(error)})`);
78335
+ setLoadingState("selecting_global");
78336
+ return;
77629
78337
  }
77630
78338
  }
77631
78339
  if (!agent && continueSession && settings.lastAgent) {
77632
78340
  try {
77633
78341
  agent = await client.agents.retrieve(settings.lastAgent);
77634
78342
  } catch (error) {
77635
- console.error(`Previous agent ${settings.lastAgent} not found (error: ${JSON.stringify(error)}), creating new one...`);
78343
+ console.error(`Previous agent ${settings.lastAgent} not found (error: ${JSON.stringify(error)})`);
78344
+ setLoadingState("selecting_global");
78345
+ return;
77636
78346
  }
77637
78347
  }
77638
78348
  if (!agent) {
@@ -77795,4 +78505,4 @@ Error during initialization: ${message}`);
77795
78505
  }
77796
78506
  main();
77797
78507
 
77798
- //# debugId=6EE8B666F8368EAC64756E2164756E21
78508
+ //# debugId=D15B3A68114566C264756E2164756E21