@letta-ai/letta-code 0.34.35 → 0.34.36

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
@@ -380,6 +380,39 @@ var init_error = __esm(() => {
380
380
  };
381
381
  });
382
382
 
383
+ // src/agent/agent-tags.ts
384
+ var exports_agent_tags = {};
385
+ __export(exports_agent_tags, {
386
+ isStatelessSubagentTags: () => isStatelessSubagentTags,
387
+ isMemfsOptOutCreateTags: () => isMemfsOptOutCreateTags,
388
+ buildCreatedAgentTags: () => buildCreatedAgentTags,
389
+ ONBOARDING_ORIGIN_TAG: () => ONBOARDING_ORIGIN_TAG,
390
+ MEMFS_DISABLED_TAG: () => MEMFS_DISABLED_TAG,
391
+ LETTA_CODE_SUBAGENT_TAG: () => LETTA_CODE_SUBAGENT_TAG,
392
+ LETTA_CODE_ORIGIN_TAG: () => LETTA_CODE_ORIGIN_TAG,
393
+ GIT_MEMORY_ENABLED_TAG: () => GIT_MEMORY_ENABLED_TAG
394
+ });
395
+ function buildCreatedAgentTags(options = {}) {
396
+ const tags = [LETTA_CODE_ORIGIN_TAG];
397
+ if (options.isSubagent) {
398
+ tags.push(LETTA_CODE_SUBAGENT_TAG);
399
+ }
400
+ if (options.enableMemfs) {
401
+ tags.push(GIT_MEMORY_ENABLED_TAG);
402
+ }
403
+ if (options.tags && Array.isArray(options.tags)) {
404
+ tags.push(...options.tags);
405
+ }
406
+ return Array.from(new Set(tags));
407
+ }
408
+ function isStatelessSubagentTags(tags) {
409
+ return Boolean(tags?.includes(LETTA_CODE_SUBAGENT_TAG) && !tags.includes(GIT_MEMORY_ENABLED_TAG));
410
+ }
411
+ function isMemfsOptOutCreateTags(tags) {
412
+ return Array.isArray(tags) && tags.includes(MEMFS_DISABLED_TAG);
413
+ }
414
+ var LETTA_CODE_ORIGIN_TAG = "origin:letta-code", ONBOARDING_ORIGIN_TAG = "origin:onboarding", LETTA_CODE_SUBAGENT_TAG = "role:subagent", GIT_MEMORY_ENABLED_TAG = "git-memory-enabled", MEMFS_DISABLED_TAG = "memfs:disabled";
415
+
383
416
  // node_modules/@letta-ai/letta-client/internal/tslib.mjs
384
417
  function __classPrivateFieldSet(receiver, state, value, kind, f) {
385
418
  if (kind === "m")
@@ -6044,7 +6077,7 @@ var package_default;
6044
6077
  var init_package = __esm(() => {
6045
6078
  package_default = {
6046
6079
  name: "@letta-ai/letta-code",
6047
- version: "0.34.35",
6080
+ version: "0.34.36",
6048
6081
  lettaStartupLogProtocol: 1,
6049
6082
  lettaConfigDirProtocol: 1,
6050
6083
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
@@ -6322,7 +6355,7 @@ var init_package = __esm(() => {
6322
6355
  },
6323
6356
  lettaLifecycleProtocol: 1,
6324
6357
  lettaRuntimeGraphProtocol: 1,
6325
- lettaRuntimeGraphSha512: "sha512-Vf/ArTuf+qMWFMs3VWpjgnuKmlisBRBM3uNzl9ayDETGs7KjLixvXe8tnzAZvsFwfwiTrIVYjIqy2mlKpUX+Eg==",
6358
+ lettaRuntimeGraphSha512: "sha512-tPPjQGBLt5+vA9W+Mv4yvHjYDOzBSNPnlU8rxk0g42xKGYf9HCuWpRA2cR5XvB6ETJFjJmcjMOcHZmQVjiwNuA==",
6326
6359
  patchedDependencies: {
6327
6360
  "@earendil-works/pi-ai@0.99.1": "patches/@earendil-works%2Fpi-ai@0.99.1.patch"
6328
6361
  }
@@ -99024,35 +99057,6 @@ var init_backend_mode = __esm(() => {
99024
99057
  init_paths();
99025
99058
  });
99026
99059
 
99027
- // src/agent/agent-tags.ts
99028
- var exports_agent_tags = {};
99029
- __export(exports_agent_tags, {
99030
- isMemfsOptOutCreateTags: () => isMemfsOptOutCreateTags,
99031
- buildCreatedAgentTags: () => buildCreatedAgentTags,
99032
- ONBOARDING_ORIGIN_TAG: () => ONBOARDING_ORIGIN_TAG,
99033
- MEMFS_DISABLED_TAG: () => MEMFS_DISABLED_TAG,
99034
- LETTA_CODE_SUBAGENT_TAG: () => LETTA_CODE_SUBAGENT_TAG,
99035
- LETTA_CODE_ORIGIN_TAG: () => LETTA_CODE_ORIGIN_TAG,
99036
- GIT_MEMORY_ENABLED_TAG: () => GIT_MEMORY_ENABLED_TAG
99037
- });
99038
- function buildCreatedAgentTags(options = {}) {
99039
- const tags = [LETTA_CODE_ORIGIN_TAG];
99040
- if (options.isSubagent) {
99041
- tags.push(LETTA_CODE_SUBAGENT_TAG);
99042
- }
99043
- if (options.enableMemfs) {
99044
- tags.push(GIT_MEMORY_ENABLED_TAG);
99045
- }
99046
- if (options.tags && Array.isArray(options.tags)) {
99047
- tags.push(...options.tags);
99048
- }
99049
- return Array.from(new Set(tags));
99050
- }
99051
- function isMemfsOptOutCreateTags(tags) {
99052
- return Array.isArray(tags) && tags.includes(MEMFS_DISABLED_TAG);
99053
- }
99054
- var LETTA_CODE_ORIGIN_TAG = "origin:letta-code", ONBOARDING_ORIGIN_TAG = "origin:onboarding", LETTA_CODE_SUBAGENT_TAG = "role:subagent", GIT_MEMORY_ENABLED_TAG = "git-memory-enabled", MEMFS_DISABLED_TAG = "memfs:disabled";
99055
-
99056
99060
  // src/utils/directory-limits.ts
99057
99061
  function parsePositiveIntEnv(value, fallback, min, max) {
99058
99062
  if (!value || value.trim() === "") {
@@ -109111,6 +109115,12 @@ async function applyMemfsFlags(agentId, memfsFlag, options) {
109111
109115
  const { settingsManager: settingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), exports_settings_manager));
109112
109116
  const { getBackend: getBackend2 } = await Promise.resolve().then(() => (init_backend2(), exports_backend));
109113
109117
  const backend = getBackend2();
109118
+ const { GIT_MEMORY_ENABLED_TAG: GIT_MEMORY_ENABLED_TAG2, isStatelessSubagentTags: isStatelessSubagentTags2 } = await Promise.resolve().then(() => exports_agent_tags);
109119
+ const tags = options?.agentTags ?? (await backend.retrieveAgent(agentId, { include: ["agent.tags"] })).tags;
109120
+ if (isStatelessSubagentTags2(tags)) {
109121
+ debugLog("memfs", `Not enabling MemFS for stateless subagent ${agentId}`);
109122
+ return { action: "unchanged" };
109123
+ }
109114
109124
  if (backend.capabilities.localMemfs) {
109115
109125
  const memoryDir = getScopedMemoryFilesystemRoot(agentId);
109116
109126
  const { initializeLocalMemoryRepo: initializeLocalMemoryRepo2 } = await Promise.resolve().then(() => (init_memory_git(), exports_memory_git));
@@ -109133,7 +109143,6 @@ async function applyMemfsFlags(agentId, memfsFlag, options) {
109133
109143
  throw new Error(await getMemfsSyncUnavailableMessage());
109134
109144
  }
109135
109145
  const localMemfsEnabled = settingsManager2.isMemfsEnabled(agentId);
109136
- const { GIT_MEMORY_ENABLED_TAG: GIT_MEMORY_ENABLED_TAG2 } = await Promise.resolve().then(() => exports_agent_tags);
109137
109146
  const shouldAutoEnableFromTag = !memfsFlag && !localMemfsEnabled && Boolean(options?.agentTags?.includes(GIT_MEMORY_ENABLED_TAG2));
109138
109147
  const enabling = Boolean(memfsFlag || shouldAutoEnableFromTag);
109139
109148
  if (enabling) {
@@ -109217,6 +109226,7 @@ async function enableMemfsIfCloud(agentId, options = {}) {
109217
109226
  var MEMORY_FS_ROOT = ".letta", MEMORY_FS_AGENTS_DIR = "agents", MEMORY_FS_MEMORY_DIR = "memory", MEMORY_SYSTEM_DIR = "system", MEMORY_TREE_MAX_LINES, MEMORY_TREE_MAX_CHARS, MEMORY_TREE_MAX_CHILDREN_PER_DIR;
109218
109227
  var init_memory_filesystem2 = __esm(() => {
109219
109228
  init_paths();
109229
+ init_debug();
109220
109230
  init_directory_limits();
109221
109231
  init_settings_path();
109222
109232
  init_context();
@@ -110796,11 +110806,15 @@ class LocalStore {
110796
110806
  listAgents(body) {
110797
110807
  this.refreshLoadedAgentRecordsFromStorage();
110798
110808
  const bodyRecord = body ?? {};
110809
+ const name = optionalString3(bodyRecord.name);
110799
110810
  const queryText = optionalString3(bodyRecord.query_text)?.toLowerCase();
110800
110811
  const tags = isStringArray2(bodyRecord.tags) ? bodyRecord.tags : [];
110801
110812
  const after = optionalString3(bodyRecord.after);
110802
110813
  const limit4 = typeof bodyRecord.limit === "number" ? bodyRecord.limit : 20;
110803
110814
  let agents = [...this.agents.values()].filter((agent) => !isHiddenLocalAgentRecord(agent)).map((agent) => this.projectAgent(agent));
110815
+ if (name) {
110816
+ agents = agents.filter((agent) => agent.name === name);
110817
+ }
110804
110818
  if (tags.length > 0) {
110805
110819
  agents = agents.filter((agent) => tags.every((tag) => agent.tags?.includes(tag)));
110806
110820
  }
@@ -110811,11 +110825,7 @@ class LocalStore {
110811
110825
  return haystack.includes(queryText);
110812
110826
  });
110813
110827
  }
110814
- agents.sort((a, b) => {
110815
- const aDate = a.last_run_completion ?? "";
110816
- const bDate = b.last_run_completion ?? "";
110817
- return bDate.localeCompare(aDate);
110818
- });
110828
+ agents.sort((a, b) => (b.last_run_completion ?? "").localeCompare(a.last_run_completion ?? ""));
110819
110829
  if (after) {
110820
110830
  const afterIndex = agents.findIndex((agent) => agent.id === after);
110821
110831
  if (afterIndex >= 0)
@@ -211680,7 +211690,7 @@ var init_mcp_client = __esm(() => {
211680
211690
  init_streamableHttp();
211681
211691
  DEFAULT_CLIENT_INFO = {
211682
211692
  name: "letta-code",
211683
- version: "0.34.35"
211693
+ version: "0.34.36"
211684
211694
  };
211685
211695
  });
211686
211696
 
@@ -213668,7 +213678,6 @@ function createSharedReminderState() {
213668
213678
  lastNotifiedPermissionMode: null,
213669
213679
  turnCount: 0,
213670
213680
  pendingReflectionTrigger: false,
213671
- pendingMemoryGitSyncReminders: [],
213672
213681
  pendingCommandIoReminders: [],
213673
213682
  pendingToolsetChangeReminders: [],
213674
213683
  hasNotifiedLowDiskSpace: false
@@ -213704,9 +213713,6 @@ function pushBounded(items3, entry) {
213704
213713
  function enqueueCommandIoReminder(state2, reminder) {
213705
213714
  pushBounded(state2.pendingCommandIoReminders, reminder);
213706
213715
  }
213707
- function enqueueMemoryGitSyncReminder(state2, reminder) {
213708
- pushBounded(state2.pendingMemoryGitSyncReminders, reminder);
213709
- }
213710
213716
  function enqueueToolsetChangeReminder(state2, reminder) {
213711
213717
  pushBounded(state2.pendingToolsetChangeReminders, reminder);
213712
213718
  }
@@ -215641,7 +215647,7 @@ Before executing the command, please follow these steps:
215641
215647
 
215642
215648
  Usage notes:
215643
215649
  - The command and description arguments are required.
215644
- - You can specify an optional timeout in milliseconds (up to 600000ms / 10 minutes). If not specified, commands will timeout after 120000ms (2 minutes).
215650
+ - Commands run until they exit unless you pass a timeout in milliseconds. Foreground timeouts have a maximum of 600000ms.
215645
215651
  - Write a clear, concise user-facing description of what this command does. This description may be shown directly in chat as part of a status row like \`Running command: <description>\` or \`Ran command: <description>\`. Describe the command's purpose, not its shell syntax. For simple commands, keep it brief (5-10 words). For complex commands (piped commands, obscure flags, or anything hard to understand at a glance), add enough context to clarify what it does.
215646
215652
  - If the output exceeds 30000 characters, you receive only the first 2000 characters plus the path of a file containing the full output. A failed command whose output exceeds 10000 characters returns a head-and-tail excerpt plus that file path. Read or search that file when you need the rest instead of re-running the command.
215647
215653
  - Ordinary commands wait briefly for a result, then automatically continue in the background if they are still running. You will receive a task ID and one completion notification, so do not predict command duration, set \`run_in_background\` merely because a command may be slow, or poll for completion.
@@ -215744,7 +215750,7 @@ Important:
215744
215750
  var init_Bash = () => {};
215745
215751
 
215746
215752
  // src/tools/descriptions/CodeMode.md
215747
- var CodeMode_default = "Run a JavaScript async function body that calls tools and returns only the results you need.\n\nCall built-in and server-side tools with `await tools.<name>(args)` and MCP tools with `await tools.mcp__server__tool(args)`; `listTools()` and `describeTool(name)` list tools and their schemas. Failed calls throw. `args` holds optional input, `process.env` holds the variables a Bash command sees plus the agent's secrets, and `console.log()` output is captured.\n\nReturns `{ result, output }`. Like Bash, a script still running after a brief wait continues in the background and its result arrives as a task notification; `run_in_background: true` returns the task ID immediately. TaskStop aborts it.\n\nNested calls follow permission rules; calls that need approval fail, so call those tools directly. This is not a security sandbox.\n\nExample:\n\n const texts = await Promise.all(args.paths.map(file_path => tools.Read({ file_path })));\n return texts.map((text, i) => ({ path: args.paths[i], lines: text.split('\\n').length }));\n";
215753
+ var CodeMode_default = "Run a JavaScript async function body that calls tools and returns only the results you need.\n\nCall built-in and server-side tools with `await tools.<name>(args)` and MCP tools with `await tools.mcp__server__tool(args)`; `listTools()` and `describeTool(name)` list tools and their schemas. Failed calls throw. `args` holds optional input, `process.env` holds the variables a Bash command sees plus the agent's secrets, and `console.log()` output is captured.\n\n`await decide(state, questions, options?)` makes one typed model decision. `questions` is an object of id → `{ type, instructions, criteria }`: `choice` takes a criteria map of id → description, `score` a criteria array, `noul` no criteria. It resolves to `{ answers }` keyed by question id (`.choice`, `.score`, or `.noul` in 0–1), or `null` after an invalid answer.\n\nReturns `{ result, output }`. Like Bash, a script still running after a brief wait continues in the background and its result arrives as a task notification; `run_in_background: true` returns the task ID immediately. TaskStop aborts it.\n\nNested calls follow permission rules; calls that need approval fail, so call those tools directly. This is not a security sandbox.\n\nExample:\n\n const texts = await Promise.all(args.paths.map(file_path => tools.Read({ file_path })));\n return texts.map((text, i) => ({ path: args.paths[i], lines: text.split('\\n').length }));\n";
215748
215754
  var init_CodeMode = () => {};
215749
215755
 
215750
215756
  // src/tools/descriptions/Edit.md
@@ -215848,7 +215854,7 @@ Do not use this tool for root memory files (already loaded) or skills/ (use Skil
215848
215854
  var init_Memory = () => {};
215849
215855
 
215850
215856
  // src/tools/descriptions/Monitor.md
215851
- var Monitor_default = 'Start a background monitor that streams events from a long-running script. Each stdout line is an event — you keep working and notifications arrive in the chat. Events arrive on their own schedule and are not replies from the user, even if one lands while you\'re waiting for the user to answer a question.\n\nPick by how many notifications you need:\n- **One** ("tell me when the server is ready / the build finishes") → use **Bash with `run_in_background`** and a command that exits when the condition is true, e.g. `until grep -q "Ready in" dev.log; do sleep 0.5; done`. You get a single completion notification when it exits.\n- **One per occurrence, indefinitely** ("tell me every time an ERROR line appears") → Monitor with an unbounded command (`tail -f`, `inotifywait -m`, `while true`).\n- **One per occurrence, until a known end** ("emit each CI step result, stop when the run completes") → Monitor with a command that emits lines and then exits.\n\nYour script\'s stdout is the event stream. Each line becomes a notification. Exit ends the watch.\n # Each matching log line is an event\n tail -f /var/log/app.log | grep --line-buffered "ERROR"\n # Each file change is an event\n inotifywait -m --format \'%e %f\' /watched/dir\n # Node script that emits events as they arrive (e.g. WebSocket listener)\n node watch-for-events.js\n\nFor open github.com pull requests, use WatchPR instead. It covers the whole PR surface and keeps watching after CI finishes. Use Monitor only when WatchPR does not apply.\n\n**Don\'t use an unbounded command for a single notification.** `tail -f`, `inotifywait -m`, and `while true` never exit on their own, so the monitor stays armed until timeout even after the event has fired. For "tell me when X is ready," use Bash `run_in_background` with an `until` loop instead (one notification, ends in seconds). Note that `tail -f log | grep -m 1 ...` does *not* fix this: if the log goes quiet after the match, `tail` never receives SIGPIPE and the pipeline hangs anyway.\n\n- Every pipe stage must flush per line or matches sit in its buffer unseen: `grep` needs `--line-buffered`, `awk` needs `fflush()`. `head` cannot flush at all — `| head -N` delivers nothing until N matches accumulate, then ends the stream.\n- In poll loops, handle transient failures (`curl ... || true`) — one failed request shouldn\'t kill the monitor.\n- Poll intervals: 30s+ for remote APIs (rate limits), 0.5–1s for local checks.\n- Write a specific `description` — it appears in every notification ("errors in deploy.log" not "watching logs").\n- Only stdout is the event stream. Stderr goes to the output file (readable via Read) but does not trigger notifications — for a command you run directly (e.g. `python train.py 2>&1 | grep --line-buffered ...`), merge stderr with `2>&1` so its failures reach your filter. (No effect on `tail -f` of an existing log — that file only contains what its writer redirected.)\n\n**Coverage — silence is not success.** When watching a job or process for an outcome, your filter must match every terminal state, not just the happy path. A monitor that greps only for the success marker stays silent through a crashloop, a hung process, or an unexpected exit — and silence looks identical to "still running." Before arming, ask: *if this process crashed right now, would my filter emit anything?* If not, widen it.\n # Wrong — silent on crash, hang, or any non-success exit\n tail -f run.log | grep --line-buffered "elapsed_steps="\n # Right — one alternation covering progress + the failure signatures you\'d act on\n tail -f run.log | grep -E --line-buffered "elapsed_steps=|Traceback|Error|FAILED|assert|Killed|OOM"\n\nFor poll loops checking job state, emit on every terminal status (`succeeded|failed|cancelled|timeout`), not just success. If you cannot confidently enumerate the failure signatures, broaden the grep alternation rather than narrow it — some extra noise is better than missing a crashloop.\n\n**Output volume**: Every stdout line is a conversation message, so the filter should be selective — but selective means "the lines you\'d act on," not "only good news." Never pipe raw logs; filter to exactly the success and failure signals you care about. Monitors that produce too many events are automatically stopped; restart with a tighter filter if this happens.\n\nStdout lines within 200ms are batched into a single notification, so multiline output from a single event groups naturally.\n\nThe script runs in the same shell environment as Bash. Exit ends the watch (exit code is reported). Timeout → killed. Set `persistent: true` for session-length watches (log tails, long-lived event streams). In managed Cloud sandboxes, every monitor stops at the per-sandbox cap configured by LETTA_MONITOR_MAX_TIMEOUT_MS (default 1 hour, clamped to 1 hour through 7 days), including persistent monitors; elsewhere persistent monitors run until TaskStop or session end. Call Monitor again to re-arm it if you still need to watch. For much longer Cloud waits, schedule a Wake or cron (which does not keep the sandbox running between turns) and combine it with shorter monitors. Use TaskStop to cancel early.\n\nWhen someone is waiting on the result, set `backup_after_seconds` (e.g. 900). If the monitor goes that long without an event, a durable Wake brings you back to check the real state, so a queued, hung, or dead watch can\'t leave them waiting in silence.\n\n**ws source** — open a WebSocket and stream each incoming text frame as an event. No shell, no polling: the server pushes, you get notified.\n ws: {url: \'wss://events.example.com/stream\', protocols: [\'v1\']},\n description: \'deploy events\',\n\nEach text frame becomes one notification (multiline frames stay as one event). Binary frames are reported as `[binary frame, N bytes]` rather than passed through. Socket close ends the watch with the close code surfaced; errors are surfaced before close. Same rate limiting as bash — a firehose will be suppressed and eventually stopped, so subscribe to a filtered feed where one exists.\n\nPrefer this over `command: \'websocat wss://…\'` — it avoids the extra process and line-buffering pitfalls. Use bash when you need to transform or filter frames with shell tools before they become events.\n\n**script source** — a JavaScript watch loop with the same globals as CodeMode, plus `emit(text)` (one event) and `sleep(ms)`. Returning or throwing ends the watch.\n';
215857
+ var Monitor_default = 'Start a background monitor that streams events from a long-running script. Each stdout line is an event — you keep working and notifications arrive in the chat. Events arrive on their own schedule and are not replies from the user, even if one lands while you\'re waiting for the user to answer a question.\n\nPick by how many notifications you need:\n- **One** ("tell me when the server is ready / the build finishes") → use **Bash with `run_in_background`** and a command that exits when the condition is true, e.g. `until grep -q "Ready in" dev.log; do sleep 0.5; done`. You get a single completion notification when it exits.\n- **One per occurrence, indefinitely** ("tell me every time an ERROR line appears") → Monitor with an unbounded command (`tail -f`, `inotifywait -m`, `while true`).\n- **One per occurrence, until a known end** ("emit each CI step result, stop when the run completes") → Monitor with a command that emits lines and then exits.\n\nYour script\'s stdout is the event stream. Each line becomes a notification. Exit ends the watch.\n # Each matching log line is an event\n tail -f /var/log/app.log | grep --line-buffered "ERROR"\n # Each file change is an event\n inotifywait -m --format \'%e %f\' /watched/dir\n # Node script that emits events as they arrive (e.g. WebSocket listener)\n node watch-for-events.js\n\nFor open github.com pull requests, use WatchPR instead. It covers the whole PR surface and keeps watching after CI finishes. Use Monitor only when WatchPR does not apply.\n\n**Don\'t use an unbounded command for a single notification.** `tail -f`, `inotifywait -m`, and `while true` never exit on their own, so the monitor stays armed until timeout even after the event has fired. For "tell me when X is ready," use Bash `run_in_background` with an `until` loop instead (one notification, ends in seconds). Note that `tail -f log | grep -m 1 ...` does *not* fix this: if the log goes quiet after the match, `tail` never receives SIGPIPE and the pipeline hangs anyway.\n\n- Every pipe stage must flush per line or matches sit in its buffer unseen: `grep` needs `--line-buffered`, `awk` needs `fflush()`. `head` cannot flush at all — `| head -N` delivers nothing until N matches accumulate, then ends the stream.\n- In poll loops, handle transient failures (`curl ... || true`) — one failed request shouldn\'t kill the monitor.\n- Poll intervals: 30s+ for remote APIs (rate limits), 0.5–1s for local checks.\n- Write a specific `description` — it appears in every notification ("errors in deploy.log" not "watching logs").\n- Only stdout is the event stream. Stderr goes to the output file (readable via Read) but does not trigger notifications — for a command you run directly (e.g. `python train.py 2>&1 | grep --line-buffered ...`), merge stderr with `2>&1` so its failures reach your filter. (No effect on `tail -f` of an existing log — that file only contains what its writer redirected.)\n\n**Coverage — silence is not success.** When watching a job or process for an outcome, your filter must match every terminal state, not just the happy path. A monitor that greps only for the success marker stays silent through a crashloop, a hung process, or an unexpected exit — and silence looks identical to "still running." Before arming, ask: *if this process crashed right now, would my filter emit anything?* If not, widen it.\n # Wrong — silent on crash, hang, or any non-success exit\n tail -f run.log | grep --line-buffered "elapsed_steps="\n # Right — one alternation covering progress + the failure signatures you\'d act on\n tail -f run.log | grep -E --line-buffered "elapsed_steps=|Traceback|Error|FAILED|assert|Killed|OOM"\n\nFor poll loops checking job state, emit on every terminal status (`succeeded|failed|cancelled|timeout`), not just success. If you cannot confidently enumerate the failure signatures, broaden the grep alternation rather than narrow it — some extra noise is better than missing a crashloop.\n\n**Output volume**: Every stdout line is a conversation message, so the filter should be selective — but selective means "the lines you\'d act on," not "only good news." Never pipe raw logs; filter to exactly the success and failure signals you care about. Monitors that produce too many events are automatically stopped; restart with a tighter filter if this happens.\n\nStdout lines within 200ms are batched into a single notification, so multiline output from a single event groups naturally.\n\nThe script runs in the same shell environment as Bash. Exit ends the watch (exit code is reported). Timeout → killed. Set `persistent: true` for session-length watches (log tails, long-lived event streams). In managed Cloud sandboxes, every monitor stops at the per-sandbox cap configured by LETTA_MONITOR_MAX_TIMEOUT_MS (default 1 hour, clamped to 1 hour through 7 days), including persistent monitors; elsewhere persistent monitors run until TaskStop or session end. Call Monitor again to re-arm it if you still need to watch. For much longer Cloud waits, schedule a Wake or cron (which does not keep the sandbox running between turns) and combine it with shorter monitors. Use TaskStop to cancel early.\n\nWhen someone is waiting on the result, set `backup_after_seconds` (e.g. 900). If the monitor goes that long without an event, a durable Wake brings you back to check the real state, so a queued, hung, or dead watch can\'t leave them waiting in silence.\n\n**ws source** — open a WebSocket and stream each incoming text frame as an event. No shell, no polling: the server pushes, you get notified.\n ws: {url: \'wss://events.example.com/stream\', protocols: [\'v1\']},\n description: \'deploy events\',\n\nEach text frame becomes one notification (multiline frames stay as one event). Binary frames are reported as `[binary frame, N bytes]` rather than passed through. Socket close ends the watch with the close code surfaced; errors are surfaced before close. Same rate limiting as bash — a firehose will be suppressed and eventually stopped, so subscribe to a filtered feed where one exists.\n\nPrefer this over `command: \'websocat wss://…\'` — it avoids the extra process and line-buffering pitfalls. Use bash when you need to transform or filter frames with shell tools before they become events.\n\n**script source** — a JavaScript watch loop with the same globals as CodeMode (including `decide()`), plus `emit(text)` (one event) and `sleep(ms)`. Tool calls and `decide()` share one rate limit; `decide()` spends money on every call, so call it only when an observed change needs judgment, not on every poll. Returning or throwing ends the watch.\n';
215852
215858
  var init_Monitor = () => {};
215853
215859
 
215854
215860
  // src/tools/descriptions/Read.md
@@ -221930,9 +221936,13 @@ var exports_bash = {};
221930
221936
  __export(exports_bash, {
221931
221937
  spawnCommand: () => spawnCommand,
221932
221938
  getBackgroundLauncher: () => getBackgroundLauncher,
221933
- bash: () => bash
221939
+ bash: () => bash,
221940
+ __setShellProcessStarterForTests: () => __setShellProcessStarterForTests
221934
221941
  });
221935
221942
  import { rmSync as rmSync13 } from "node:fs";
221943
+ function __setShellProcessStarterForTests(starter) {
221944
+ shellProcessStarter = starter ?? startShellProcess;
221945
+ }
221936
221946
  function rebuildCachedLauncher(command, secretEnv) {
221937
221947
  if (!cachedWorkingLauncher)
221938
221948
  return null;
@@ -222078,7 +222088,7 @@ async function bash(args) {
222078
222088
  validateRequiredParams(args, ["command"], "Bash");
222079
222089
  const {
222080
222090
  command,
222081
- timeout = 120000,
222091
+ timeout,
222082
222092
  description,
222083
222093
  run_in_background = false,
222084
222094
  signal,
@@ -222156,8 +222166,8 @@ async function bash(args) {
222156
222166
  let bgProcess;
222157
222167
  let outputWriteFailed = false;
222158
222168
  const foregroundOutput = { stdout: "", stderr: "" };
222159
- const effectiveTimeout = run_in_background ? timeout > 0 ? timeout : 0 : Math.min(Math.max(timeout, 1), 600000);
222160
- const runningProcess = startShellProcess(sandboxed.launcher, {
222169
+ const effectiveTimeout = run_in_background ? timeout !== undefined && timeout > 0 ? timeout : 0 : timeout === undefined ? 0 : Math.min(Math.max(timeout, 1), 600000);
222170
+ const runningProcess = shellProcessStarter(sandboxed.launcher, {
222161
222171
  cwd: userCwd,
222162
222172
  env: sandboxed.env,
222163
222173
  timeoutMs: effectiveTimeout,
@@ -222225,11 +222235,11 @@ async function bash(args) {
222225
222235
  }, (error5) => {
222226
222236
  flushStreamScrubbers();
222227
222237
  const err = error5;
222228
- const message = sanitizeOutput(err.killed ? `Command timed out after ${timeout}ms` : err.message);
222238
+ const message = sanitizeOutput(err.killed ? `Command timed out after ${effectiveTimeout}ms` : err.message);
222229
222239
  bgProcess.status = "failed";
222230
222240
  appendBackgroundProcessOutput(bgProcess, "stderr", message);
222231
222241
  appendToOutputFile(outputFile, err.killed ? `
222232
- [timeout after ${timeout}ms]
222242
+ [timeout after ${effectiveTimeout}ms]
222233
222243
  ` : `
222234
222244
  [error] ${message}
222235
222245
  `);
@@ -222324,7 +222334,7 @@ Output file: ${outputFile}`
222324
222334
  status: "success"
222325
222335
  };
222326
222336
  }
222327
- var cachedWorkingLauncher = null, DEFAULT_FOREGROUND_YIELD_MS = 1e4, NOTIFICATION_TAIL_LINES = 50;
222337
+ var cachedWorkingLauncher = null, DEFAULT_FOREGROUND_YIELD_MS = 1e4, shellProcessStarter, NOTIFICATION_TAIL_LINES = 50;
222328
222338
  var init_bash = __esm(async () => {
222329
222339
  init_constants2();
222330
222340
  init_runtime_context();
@@ -222342,6 +222352,7 @@ var init_bash = __esm(async () => {
222342
222352
  init_worktree_ownership(),
222343
222353
  init_shell_runner()
222344
222354
  ]);
222355
+ shellProcessStarter = startShellProcess;
222345
222356
  });
222346
222357
 
222347
222358
  // src/tools/client-tool-serialization.ts
@@ -235685,7 +235696,8 @@ async function executeScript(spawner, options, meta2, extraGlobals) {
235685
235696
  tools,
235686
235697
  listTools,
235687
235698
  describeTool,
235688
- ...spawner ? { agent, decide, parallel, pipeline: pipeline7, phase, log: log3 } : {},
235699
+ decide,
235700
+ ...spawner ? { agent, parallel, pipeline: pipeline7, phase, log: log3 } : {},
235689
235701
  args: options.args,
235690
235702
  console: { log: log3, warn: log3, error: log3, info: log3 },
235691
235703
  process: Object.freeze({ env: Object.freeze({ ...options.env }) }),
@@ -242308,10 +242320,10 @@ var init_monitor_event_stream = __esm(() => {
242308
242320
  });
242309
242321
 
242310
242322
  // src/tools/impl/monitor-script.ts
242311
- function rateLimitToolCalls(call2, signal) {
242323
+ function createCallLimiter(signal) {
242312
242324
  let tokens = TOOL_CALL_BURST;
242313
242325
  let refilledAt = Date.now();
242314
- return (toolName2, toolArgs) => {
242326
+ return (call2) => {
242315
242327
  if (signal.aborted)
242316
242328
  return new Promise(() => {});
242317
242329
  const now2 = Date.now();
@@ -242321,10 +242333,10 @@ function rateLimitToolCalls(call2, signal) {
242321
242333
  refilledAt += refill * TOOL_CALL_REFILL_MS;
242322
242334
  }
242323
242335
  if (tokens === 0) {
242324
- throw new Error(`Monitor script exceeded ${1000 / TOOL_CALL_REFILL_MS} tool calls per second; add sleep() between polls.`);
242336
+ throw new Error(`Monitor script exceeded ${1000 / TOOL_CALL_REFILL_MS} calls per second; add sleep() between polls.`);
242325
242337
  }
242326
242338
  tokens -= 1;
242327
- return call2(toolName2, toolArgs);
242339
+ return call2();
242328
242340
  };
242329
242341
  }
242330
242342
  function abortableSleep3(ms, signal) {
@@ -242467,16 +242479,25 @@ async function startScriptMonitor(args, hooks) {
242467
242479
  const stopped = new Promise((_resolve, reject2) => {
242468
242480
  controller.signal.addEventListener("abort", () => reject2(controller.signal.reason), { once: true });
242469
242481
  });
242482
+ const limit4 = createCallLimiter(controller.signal);
242470
242483
  const run = executeToolScript({
242471
242484
  ...toolScope,
242472
- callTool: rateLimitToolCalls(toolScope.callTool, controller.signal),
242485
+ callTool: (toolName2, toolArgs) => limit4(() => toolScope.callTool(toolName2, toolArgs)),
242473
242486
  maxTotalToolCalls: Number.POSITIVE_INFINITY,
242474
242487
  script: args.script,
242475
242488
  args: args.args,
242476
242489
  signal: new AbortController().signal,
242477
242490
  globals: {
242478
242491
  emit,
242479
- sleep: (ms) => abortableSleep3(ms, controller.signal)
242492
+ sleep: (ms) => abortableSleep3(ms, controller.signal),
242493
+ decide: (state2, questions, options) => {
242494
+ if (options !== undefined && (!options || typeof options !== "object" || Array.isArray(options))) {
242495
+ throw new Error("decide() options must be an object.");
242496
+ }
242497
+ const pending2 = limit4(() => submitWorkflowDecision({ ...options, state: state2, questions }, controller.signal));
242498
+ pending2.catch(() => {});
242499
+ return pending2;
242500
+ }
242480
242501
  },
242481
242502
  onProgress: (event) => {
242482
242503
  if (event.kind === "log")
@@ -242517,6 +242538,7 @@ var init_monitor_script = __esm(() => {
242517
242538
  init_script_tool_scope();
242518
242539
  init_secret_substitution();
242519
242540
  init_tool_context_retention();
242541
+ init_decide();
242520
242542
  init_workflow_engine();
242521
242543
  init_message_queue_bridge();
242522
242544
  init_task_notifications();
@@ -245048,7 +245070,7 @@ class CodexAppServerClient {
245048
245070
  await this.request("initialize", {
245049
245071
  clientInfo: {
245050
245072
  name: "letta-code",
245051
- version: "0.34.35"
245073
+ version: "0.34.36"
245052
245074
  },
245053
245075
  capabilities: null
245054
245076
  });
@@ -246262,16 +246284,6 @@ var init_catalog = __esm(() => {
246262
246284
  "listen"
246263
246285
  ]
246264
246286
  },
246265
- {
246266
- id: "memory-git-sync",
246267
- description: "Queued post-turn memory git sync status",
246268
- modes: [
246269
- "interactive",
246270
- "headless-one-shot",
246271
- "headless-bidirectional",
246272
- "listen"
246273
- ]
246274
- },
246275
246287
  {
246276
246288
  id: "command-io",
246277
246289
  description: "Recent slash command input/output context",
@@ -246514,14 +246526,6 @@ async function buildPermissionModeReminder(context3) {
246514
246526
 
246515
246527
  `;
246516
246528
  }
246517
- async function buildMemoryGitSyncReminder(context3) {
246518
- if (context3.state.pendingMemoryGitSyncReminders.length === 0) {
246519
- return null;
246520
- }
246521
- return context3.state.pendingMemoryGitSyncReminders.splice(0).map((reminder) => reminder.text).join(`
246522
-
246523
- `);
246524
- }
246525
246529
  function escapeXml2(value) {
246526
246530
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
246527
246531
  }
@@ -246687,7 +246691,6 @@ var init_engine3 = __esm(() => {
246687
246691
  "mcp-servers-info": buildMcpServersInfoReminder,
246688
246692
  "session-context": buildSessionContextReminder,
246689
246693
  "permission-mode": buildPermissionModeReminder,
246690
- "memory-git-sync": buildMemoryGitSyncReminder,
246691
246694
  "command-io": buildCommandIoReminder,
246692
246695
  "toolset-change": buildToolsetChangeReminder,
246693
246696
  "disk-space": buildDiskSpaceReminder
@@ -255545,7 +255548,7 @@ var init_Bash2 = __esm(() => {
255545
255548
  },
255546
255549
  timeout: {
255547
255550
  type: "number",
255548
- description: "Optional timeout in milliseconds (max 600000)"
255551
+ description: "Commands run until they exit unless you pass a timeout in milliseconds. Foreground timeouts have a maximum of 600000."
255549
255552
  },
255550
255553
  description: {
255551
255554
  type: "string",
@@ -259090,6 +259093,7 @@ var init_startup_ingress = __esm(() => {
259090
259093
  });
259091
259094
 
259092
259095
  // src/websocket/listener/connection.ts
259096
+ import WebSocket7 from "ws";
259093
259097
  function toListenerConnection(connectionId) {
259094
259098
  return { type: "ToConnection", connectionId };
259095
259099
  }
@@ -259109,8 +259113,11 @@ function refreshLegacySingleConnection(runtime) {
259109
259113
  const only = live.find(({ id: id2 }) => id2 === runtime.connectionId) ?? (live.length === 1 ? live[0] : null);
259110
259114
  runtime.transport = runtime.processTransport ?? only?.writer ?? null;
259111
259115
  runtime.streamTransport = only?.streamWriter ?? null;
259112
- runtime.socket = only ? socketForTransport(only.writer) : null;
259113
- runtime.streamSocket = only?.streamWriter ? socketForTransport(only.streamWriter) : null;
259116
+ const pending2 = (socket) => socket != null && socket.readyState <= WebSocket7.OPEN && ![...runtime.connections.values()].some(({ writer, streamWriter }) => writer === socket || streamWriter === socket);
259117
+ if (only || !pending2(runtime.socket))
259118
+ runtime.socket = only ? socketForTransport(only.writer) : null;
259119
+ if (only || !pending2(runtime.streamSocket))
259120
+ runtime.streamSocket = only?.streamWriter ? socketForTransport(only.streamWriter) : null;
259114
259121
  }
259115
259122
  function createConnectionRequestKey(connectionId, requestId) {
259116
259123
  return JSON.stringify([connectionId, requestId]);
@@ -441999,9 +442006,13 @@ async function syncMemfsForAgent(agentId) {
441999
442006
  const agent = await getBackend2().retrieveAgent(agentId, {
442000
442007
  include: ["agent.tags"]
442001
442008
  });
442002
- const { GIT_MEMORY_ENABLED_TAG: GIT_MEMORY_ENABLED_TAG2 } = await Promise.resolve().then(() => exports_agent_tags);
442009
+ const { GIT_MEMORY_ENABLED_TAG: GIT_MEMORY_ENABLED_TAG2, isStatelessSubagentTags: isStatelessSubagentTags2 } = await Promise.resolve().then(() => exports_agent_tags);
442003
442010
  const { applyMemfsFlags: applyMemfsFlags2, isLettaCloud: isLettaCloud2 } = await Promise.resolve().then(() => (init_memory_filesystem2(), exports_memory_filesystem));
442004
442011
  if (!agent.tags?.includes(GIT_MEMORY_ENABLED_TAG2)) {
442012
+ if (isStatelessSubagentTags2(agent.tags)) {
442013
+ debugLog("memfs-sync", `Agent ${agentId} is a subagent, skipping repair`);
442014
+ return;
442015
+ }
442005
442016
  if (!await isLettaCloud2()) {
442006
442017
  debugLog("memfs-sync", `Agent ${agentId} does not have memfs tag (self-hosted), skipping`);
442007
442018
  return;
@@ -445335,7 +445346,7 @@ var init_control_inputs = __esm(async () => {
445335
445346
  // src/websocket/terminal-handler.ts
445336
445347
  import { existsSync as existsSync58 } from "node:fs";
445337
445348
  import * as os6 from "node:os";
445338
- import WebSocket7 from "ws";
445349
+ import WebSocket8 from "ws";
445339
445350
  function getTerminalKey(connectionId, terminalId) {
445340
445351
  return JSON.stringify([connectionId, terminalId]);
445341
445352
  }
@@ -445356,7 +445367,7 @@ function getDefaultShell() {
445356
445367
  return "/bin/sh";
445357
445368
  }
445358
445369
  function sendTerminalMessage(socket, message2) {
445359
- if (socket.readyState === WebSocket7.OPEN) {
445370
+ if (socket.readyState === WebSocket8.OPEN) {
445360
445371
  socket.send(JSON.stringify(message2));
445361
445372
  }
445362
445373
  }
@@ -453909,7 +453920,7 @@ async function runPostTurnMemorySync(params, dependencies4 = {}) {
453909
453920
  const repairInProgress = (result3.status === "conflict" || result3.status === "invalid") && await repairMemory({ ...params, result: result3 });
453910
453921
  const reminder = repairInProgress ? formatMemoryRepairInProgressReminder(result3) : formatMemoryPostTurnSyncReminder(result3);
453911
453922
  if (reminder) {
453912
- params.enqueueReminder?.(reminder);
453923
+ debugWarn("memfs-git", reminder);
453913
453924
  await params.emitWarning?.(reminder);
453914
453925
  }
453915
453926
  } finally {
@@ -453923,7 +453934,7 @@ async function runPostTurnMemorySync(params, dependencies4 = {}) {
453923
453934
  try {
453924
453935
  const repositorySyncResult = await syncAttachedRepositories(params.agentId);
453925
453936
  for (const reminder of formatAttachedRepositoriesPostTurnSyncReminders(repositorySyncResult)) {
453926
- params.enqueueReminder?.(reminder);
453937
+ debugWarn("memfs-git", reminder);
453927
453938
  await params.emitWarning?.(reminder);
453928
453939
  }
453929
453940
  } catch (error5) {
@@ -454009,9 +454020,6 @@ async function runListenerTurnCleanup(params) {
454009
454020
  affected_paths: ["*"],
454010
454021
  timestamp: Date.now()
454011
454022
  }, { agent_id: agentId, conversation_id: conversationId }, TO_SUBSCRIBERS);
454012
- },
454013
- enqueueReminder: (text3) => {
454014
- enqueueMemoryGitSyncReminder(runtime.reminderState, { text: text3 });
454015
454023
  }
454016
454024
  });
454017
454025
  }
@@ -461160,7 +461168,7 @@ var init_turn = __esm(async () => {
461160
461168
  });
461161
461169
 
461162
461170
  // src/websocket/listener/connection-lifecycle.ts
461163
- import WebSocket8 from "ws";
461171
+ import WebSocket9 from "ws";
461164
461172
  function createConnectionTurnProcessor(runtime) {
461165
461173
  return async (queuedTurn, dequeuedBatch) => {
461166
461174
  const scopedRuntime = getOrCreateScopedRuntime(runtime, queuedTurn.agentId, queuedTurn.conversationId);
@@ -461265,7 +461273,7 @@ function closeListenerRuntimeConnections(runtime, suppressCallbacks) {
461265
461273
  for (const socket of socketsToClose) {
461266
461274
  if (suppressCallbacks) {
461267
461275
  socket.removeAllListeners();
461268
- if (socket.readyState !== WebSocket8.CLOSED && typeof socket.on === "function" && typeof socket.once === "function" && typeof socket.removeListener === "function") {
461276
+ if (socket.readyState !== WebSocket9.CLOSED && typeof socket.on === "function" && typeof socket.once === "function" && typeof socket.removeListener === "function") {
461269
461277
  const ignoreError = () => {};
461270
461278
  socket.on("error", ignoreError);
461271
461279
  socket.once("close", () => {
@@ -461273,7 +461281,7 @@ function closeListenerRuntimeConnections(runtime, suppressCallbacks) {
461273
461281
  });
461274
461282
  }
461275
461283
  }
461276
- if (socket.readyState === WebSocket8.OPEN || socket.readyState === WebSocket8.CONNECTING) {
461284
+ if (socket.readyState === WebSocket9.OPEN || socket.readyState === WebSocket9.CONNECTING) {
461277
461285
  socket.close();
461278
461286
  }
461279
461287
  }
@@ -462311,12 +462319,12 @@ var init_file_commands = __esm(async () => {
462311
462319
  });
462312
462320
 
462313
462321
  // src/websocket/listener/handshake-deadline.ts
462314
- import WebSocket9 from "ws";
462322
+ import WebSocket10 from "ws";
462315
462323
  function createListenerSocket(url2, apiKey) {
462316
462324
  const options = getListenerSocketOptions(apiKey);
462317
- const socket = new WebSocket9(url2.toString(), options);
462325
+ const socket = new WebSocket10(url2.toString(), options);
462318
462326
  const timer = setTimeout(() => {
462319
- if (socket.readyState === WebSocket9.CONNECTING)
462327
+ if (socket.readyState === WebSocket10.CONNECTING)
462320
462328
  socket.terminate();
462321
462329
  }, options.handshakeTimeout);
462322
462330
  timer.unref?.();
@@ -465695,7 +465703,7 @@ var init_input_frame_router = __esm(async () => {
465695
465703
  });
465696
465704
 
465697
465705
  // src/websocket/listener/split-stream-lifecycle.ts
465698
- import { WebSocket as WebSocket10 } from "ws";
465706
+ import { WebSocket as WebSocket11 } from "ws";
465699
465707
  function createListenerPairIdentity(runtime) {
465700
465708
  runtime.nextConnectionAttempt += 1;
465701
465709
  return {
@@ -465717,7 +465725,7 @@ function parseListenerReadyMessage(data) {
465717
465725
  return null;
465718
465726
  }
465719
465727
  function terminateSocketIfOpenOrConnecting(socket) {
465720
- if (socket && (socket.readyState === WebSocket10.OPEN || socket.readyState === WebSocket10.CONNECTING)) {
465728
+ if (socket && (socket.readyState === WebSocket11.OPEN || socket.readyState === WebSocket11.CONNECTING)) {
465721
465729
  socket.terminate();
465722
465730
  }
465723
465731
  }
@@ -465727,6 +465735,13 @@ function isCurrentSocketPair(runtime, controlSocket, streamSocket) {
465727
465735
  function shouldHandleControlSocketClose(runtime, controlSocket, connectionId) {
465728
465736
  return runtime === getActiveRuntime() && (runtime.socket === controlSocket || runtime.connections.get(connectionId)?.writer === controlSocket);
465729
465737
  }
465738
+ function trackReconnectInFlight(runtime, reconnect) {
465739
+ reconnectsInFlight.add(runtime);
465740
+ return reconnect.finally(() => reconnectsInFlight.delete(runtime));
465741
+ }
465742
+ function shouldRecoverOrphanedControlClose(runtime) {
465743
+ return runtime === getActiveRuntime() && !runtime.intentionallyClosed && (!runtime.socket || runtime.socket.readyState === WebSocket11.CLOSED) && ![...runtime.connections.values()].some(({ writer }) => isListenerTransportOpen(writer)) && !runtime.reconnectTimeout && !reconnectsInFlight.has(runtime);
465744
+ }
465730
465745
  function terminateCurrentSocketPair(runtime, controlSocket, streamSocket) {
465731
465746
  if (!isCurrentSocketPair(runtime, controlSocket, streamSocket)) {
465732
465747
  return;
@@ -465756,7 +465771,7 @@ function getStreamOpenTimeoutMs() {
465756
465771
  return LISTENER_STREAM_OPEN_TIMEOUT_MS;
465757
465772
  }
465758
465773
  async function waitForPairedSocketAcceptance(socket, identity2) {
465759
- if (socket.readyState === WebSocket10.CLOSING || socket.readyState === WebSocket10.CLOSED) {
465774
+ if (socket.readyState === WebSocket11.CLOSING || socket.readyState === WebSocket11.CLOSED) {
465760
465775
  return { status: "closed" };
465761
465776
  }
465762
465777
  const timeoutMs = getStreamOpenTimeoutMs();
@@ -465796,10 +465811,10 @@ async function waitForPairedSocketAcceptance(socket, identity2) {
465796
465811
  });
465797
465812
  }
465798
465813
  async function waitForStreamSocketOpen(streamSocket) {
465799
- if (streamSocket.readyState === WebSocket10.OPEN) {
465814
+ if (streamSocket.readyState === WebSocket11.OPEN) {
465800
465815
  return { status: "open", transport: streamSocket };
465801
465816
  }
465802
- if (streamSocket.readyState === WebSocket10.CLOSING || streamSocket.readyState === WebSocket10.CLOSED) {
465817
+ if (streamSocket.readyState === WebSocket11.CLOSING || streamSocket.readyState === WebSocket11.CLOSED) {
465803
465818
  return { status: "closed" };
465804
465819
  }
465805
465820
  const timeoutMs = getStreamOpenTimeoutMs();
@@ -465950,11 +465965,13 @@ function handleListenerSocketOpenFailure({
465950
465965
  }
465951
465966
  terminateCurrentSocketPair(runtime, controlSocket, streamSocket);
465952
465967
  }
465953
- var MAX_CLOSE_REASON_LENGTH = 120;
465968
+ var reconnectsInFlight, MAX_CLOSE_REASON_LENGTH = 120;
465954
465969
  var init_split_stream_lifecycle = __esm(async () => {
465955
465970
  init_debug();
465956
465971
  init_constants3();
465972
+ init_transport2();
465957
465973
  await init_runtime6();
465974
+ reconnectsInFlight = new WeakSet;
465958
465975
  });
465959
465976
 
465960
465977
  // src/websocket/listener/turn-finished-ack.ts
@@ -467685,9 +467702,9 @@ var init_restored_queue_pump = __esm(async () => {
467685
467702
  });
467686
467703
 
467687
467704
  // src/websocket/listener/socket-send.ts
467688
- import WebSocket11 from "ws";
467705
+ import WebSocket12 from "ws";
467689
467706
  function safeSocketSend(socket, payload, errorType, context3) {
467690
- if (socket.readyState !== WebSocket11.OPEN)
467707
+ if (socket.readyState !== WebSocket12.OPEN)
467691
467708
  return false;
467692
467709
  try {
467693
467710
  const serialized = typeof payload === "string" ? payload : JSON.stringify(payload);
@@ -467986,7 +468003,7 @@ var init_sync_replay = __esm(async () => {
467986
468003
  });
467987
468004
 
467988
468005
  // src/websocket/listener/lifecycle.ts
467989
- import WebSocket12 from "ws";
468006
+ import WebSocket13 from "ws";
467990
468007
  function createRuntime() {
467991
468008
  const bootWorkingDirectory = getCurrentWorkingDirectory();
467992
468009
  return {
@@ -468256,7 +468273,7 @@ async function attachOpenListenerSocket(runtime, socket, opts, options = {}) {
468256
468273
  abortStartupIngress();
468257
468274
  return;
468258
468275
  }
468259
- const streamTransport = streamSocket?.readyState === WebSocket12.OPEN ? streamSocket : null;
468276
+ const streamTransport = streamSocket?.readyState === WebSocket13.OPEN ? streamSocket : null;
468260
468277
  await startConnectedListenerRuntime(runtime, transport3, opts, processQueuedTurn, {
468261
468278
  startHeartbeat: options.startHeartbeat ?? false,
468262
468279
  startCronScheduler: options.startCronScheduler ?? true,
@@ -468494,7 +468511,7 @@ async function connectWithRetry(runtime, opts, attempt2 = 0, startTime = Date.no
468494
468511
  socket.on("close", (code2, reason) => {
468495
468512
  lifecycleLog(formatListenerSocketClose("control", code2, reason));
468496
468513
  abortStartupIngress();
468497
- if (!shouldHandleControlSocketClose(runtime, socket, opts.connectionId)) {
468514
+ if (!shouldHandleControlSocketClose(runtime, socket, opts.connectionId) && !shouldRecoverOrphanedControlClose(runtime)) {
468498
468515
  return;
468499
468516
  }
468500
468517
  safeEmitWsEvent("recv", "lifecycle", {
@@ -468527,7 +468544,7 @@ async function connectWithRetry(runtime, opts, attempt2 = 0, startTime = Date.no
468527
468544
  streamSocket.removeAllListeners("message");
468528
468545
  streamSocket.removeAllListeners("open");
468529
468546
  streamSocket.removeAllListeners("close");
468530
- if (streamSocket.readyState === WebSocket12.OPEN || streamSocket.readyState === WebSocket12.CONNECTING) {
468547
+ if (streamSocket.readyState === WebSocket13.OPEN || streamSocket.readyState === WebSocket13.CONNECTING) {
468531
468548
  streamSocket.close();
468532
468549
  }
468533
468550
  }
@@ -468554,7 +468571,7 @@ async function connectWithRetry(runtime, opts, attempt2 = 0, startTime = Date.no
468554
468571
  const nextAttempt = runtime.hasSuccessfulConnection ? 0 : attempt2 + 1;
468555
468572
  const nextStartTime = runtime.hasSuccessfulConnection ? Date.now() : startTime;
468556
468573
  runtime.hasSuccessfulConnection = false;
468557
- connectWithRetry(runtime, opts, nextAttempt, nextStartTime).catch((error5) => {
468574
+ trackReconnectInFlight(runtime, connectWithRetry(runtime, opts, nextAttempt, nextStartTime)).catch((error5) => {
468558
468575
  opts.onError(error5 instanceof Error ? error5 : new Error(String(error5)));
468559
468576
  });
468560
468577
  });
@@ -470933,7 +470950,7 @@ __export(exports_app_server, {
470933
470950
  });
470934
470951
  import { createServer as createServer6 } from "node:http";
470935
470952
  import { hostname as hostname5 } from "node:os";
470936
- import WebSocket13, { WebSocketServer } from "ws";
470953
+ import WebSocket14, { WebSocketServer } from "ws";
470937
470954
  function getRequiredAddressInfo(server2) {
470938
470955
  const address = server2.address();
470939
470956
  if (!address || typeof address === "string") {
@@ -470949,12 +470966,12 @@ function getWebSocketUrl(baseUrl, path47) {
470949
470966
  function closeSocket(socket, code2 = 1001, reason = "closing") {
470950
470967
  if (!socket)
470951
470968
  return;
470952
- if (socket.readyState === WebSocket13.OPEN || socket.readyState === WebSocket13.CONNECTING) {
470969
+ if (socket.readyState === WebSocket14.OPEN || socket.readyState === WebSocket14.CONNECTING) {
470953
470970
  socket.close(code2, reason);
470954
470971
  }
470955
470972
  }
470956
470973
  function terminateSocket(socket) {
470957
- if (!socket || socket.readyState === WebSocket13.CLOSED)
470974
+ if (!socket || socket.readyState === WebSocket14.CLOSED)
470958
470975
  return;
470959
470976
  socket.terminate();
470960
470977
  }
@@ -471141,7 +471158,7 @@ async function startAppServer(options = {}) {
471141
471158
  client.terminate();
471142
471159
  continue;
471143
471160
  }
471144
- if (client.readyState === WebSocket13.OPEN) {
471161
+ if (client.readyState === WebSocket14.OPEN) {
471145
471162
  const sentAt = Date.now();
471146
471163
  client.ping();
471147
471164
  watchdog?.recordPing(sentAt);
@@ -484690,7 +484707,7 @@ async function handleChannelsSlashCommand(runtime, args) {
484690
484707
 
484691
484708
  // src/channels/gateway-local.ts
484692
484709
  import { createHash as createHash21 } from "node:crypto";
484693
- import WebSocket14 from "ws";
484710
+ import WebSocket15 from "ws";
484694
484711
  async function executeChannelServiceCommand(command) {
484695
484712
  if (!isDetachedChannelsCommand(command)) {
484696
484713
  throw new Error(`Unsupported ChannelGateway command: ${command.type}`);
@@ -484761,7 +484778,7 @@ async function startLocalChannelGateway(options) {
484761
484778
  await settingsManager.initialize();
484762
484779
  const client = createAppServerClient({
484763
484780
  url: options.appServerUrl,
484764
- WebSocket: WebSocket14
484781
+ WebSocket: WebSocket15
484765
484782
  });
484766
484783
  await client.connect();
484767
484784
  let serverInfo;
@@ -486449,9 +486466,10 @@ function resolveHeadlessMemfsPolicy(options) {
486449
486466
  const isFreshStatelessSubagent = options.isSubagentRole && options.newAgentRequested;
486450
486467
  return {
486451
486468
  isFreshStatelessSubagent,
486452
- isStatelessSession: options.statelessRequested || isFreshStatelessSubagent
486469
+ isStatelessSession: options.statelessRequested || isFreshStatelessSubagent || isStatelessSubagentTags(options.agentTags)
486453
486470
  };
486454
486471
  }
486472
+ var init_headless_memfs_policy = () => {};
486455
486473
 
486456
486474
  // src/headless-mod-adapter.ts
486457
486475
  import { join as join106 } from "node:path";
@@ -487235,18 +487253,17 @@ In headless mode, use:
487235
487253
  const skillSourcesRaw = values4["skill-sources"];
487236
487254
  const memfsFlag = values4.memfs;
487237
487255
  const statelessFlag = values4.stateless;
487238
- const isSubagentRole = process.env.LETTA_CODE_AGENT_ROLE === "subagent";
487239
- const memfsPolicy = resolveHeadlessMemfsPolicy({
487256
+ const isSubagent = process.env.LETTA_CODE_AGENT_ROLE === "subagent";
487257
+ const memfsPolicyFor = (agentTags) => resolveHeadlessMemfsPolicy({
487240
487258
  statelessRequested: Boolean(statelessFlag),
487241
- isSubagentRole,
487242
- newAgentRequested: Boolean(forceNew)
487243
- });
487244
- const { isFreshStatelessSubagent } = memfsPolicy;
487245
- const isStatelessSession = Boolean(ephemeralFlag) || memfsPolicy.isStatelessSession;
487246
- if (isStatelessSession && backend3.capabilities.localMemfs) {
487247
- const { disableLocalBackendMemfsForProcess: disableLocalBackendMemfsForProcess2 } = await Promise.resolve().then(() => (init_paths(), exports_paths));
487248
- disableLocalBackendMemfsForProcess2();
487249
- }
487259
+ isSubagentRole: isSubagent,
487260
+ newAgentRequested: Boolean(forceNew),
487261
+ agentTags
487262
+ });
487263
+ const memfsPolicy = memfsPolicyFor();
487264
+ let isStatelessSession = Boolean(ephemeralFlag) || memfsPolicy.isStatelessSession;
487265
+ if (isStatelessSession && backend3.capabilities.localMemfs)
487266
+ disableLocalBackendMemfsForProcess();
487250
487267
  const memfsStartupRaw = values4["memfs-startup"];
487251
487268
  const memfsStartupPolicy = memfsStartupRaw === "background" || memfsStartupRaw === "skip" ? memfsStartupRaw : "blocking";
487252
487269
  const requestedMemoryPromptMode = memfsFlag ? "memfs" : undefined;
@@ -487437,7 +487454,7 @@ Use --new-agent to create a local agent, or pass --agent <local-agent-id> to cho
487437
487454
  if (!agent && forceNew) {
487438
487455
  const { isLettaCloud: isLettaCloud2 } = await Promise.resolve().then(() => (init_memory_filesystem2(), exports_memory_filesystem));
487439
487456
  const willAutoEnableMemfs = backend3.capabilities.remoteMemfs && shouldAutoEnableMemfsForNewAgent && await isLettaCloud2();
487440
- const effectiveMemoryMode = backend3.capabilities.localMemfs ? isFreshStatelessSubagent ? "standard" : "local-memfs" : requestedMemoryPromptMode ?? (willAutoEnableMemfs ? "memfs" : undefined);
487457
+ const effectiveMemoryMode = backend3.capabilities.localMemfs ? memfsPolicy.isFreshStatelessSubagent ? "standard" : "local-memfs" : requestedMemoryPromptMode ?? (willAutoEnableMemfs ? "memfs" : undefined);
487441
487458
  const personalityOptions = personality ? await buildCreateAgentOptionsForPersonality({
487442
487459
  personalityId: personality,
487443
487460
  model,
@@ -487534,7 +487551,9 @@ Current session AGENT_ID=${process.env.AGENT_ID}; --backend local switches to a
487534
487551
  let conversationId;
487535
487552
  let conversationOpenReason = "startup";
487536
487553
  let effectiveReflectionSettings;
487537
- const isSubagent = process.env.LETTA_CODE_AGENT_ROLE === "subagent";
487554
+ isStatelessSession ||= memfsPolicyFor(agent.tags).isStatelessSession;
487555
+ if (isStatelessSession && backend3.capabilities.localMemfs)
487556
+ disableLocalBackendMemfsForProcess();
487538
487557
  let startupMemfsFlag = autoEnableMemfsForFreshAgent ? true : memfsFlag;
487539
487558
  if (!isStatelessSession && !isDetached && backend3.capabilities.remoteMemfs && !autoEnableMemfsForFreshAgent) {
487540
487559
  const { hydrateMemfsSettingFromAgent: hydrateMemfsSettingFromAgent2, isLettaCloud: isLettaCloud2 } = await Promise.resolve().then(() => (init_memory_filesystem2(), exports_memory_filesystem));
@@ -489877,13 +489896,7 @@ async function runBidirectionalMode(agent, conversationId, effectiveModel, _outp
489877
489896
  conversationId,
489878
489897
  agentId: agent.id,
489879
489898
  isEnabled: (id2) => settingsManager.isMemfsEnabled(id2),
489880
- debugLabel: "Post-turn headless memory sync",
489881
- enqueueReminder: (text3) => {
489882
- enqueueMemoryGitSyncReminder(sharedReminderState, { text: text3 });
489883
- },
489884
- emitWarning: (text3) => {
489885
- debugWarn("memfs-git", text3);
489886
- }
489899
+ debugLabel: "Post-turn headless memory sync"
489887
489900
  });
489888
489901
  turnInProgress = false;
489889
489902
  blockedEmittedThisTurn = false;
@@ -489908,6 +489921,7 @@ var LLM_API_ERROR_MAX_RETRIES2 = 3, EMPTY_RESPONSE_MAX_RETRIES2 = 2, HEADLESS_ST
489908
489921
  var init_headless = __esm(async () => {
489909
489922
  init_error();
489910
489923
  init_preloaded_skills();
489924
+ init_paths();
489911
489925
  init_constants2();
489912
489926
  init_headless_message_sender();
489913
489927
  init_headless_response_state();
@@ -489941,6 +489955,7 @@ var init_headless = __esm(async () => {
489941
489955
  init_headless_cloud_send();
489942
489956
  init_headless_environment_response();
489943
489957
  init_headless_listener_launch();
489958
+ init_headless_memfs_policy();
489944
489959
  init_headless_reflection_settings();
489945
489960
  init_diff_preview();
489946
489961
  init_mcp_runtime();
@@ -532155,12 +532170,7 @@ ${SYSTEM_REMINDER_CLOSE}
532155
532170
  await runPostTurnMemorySync({
532156
532171
  conversationId,
532157
532172
  agentId,
532158
- isEnabled: isActiveMemfsEnabled,
532159
- enqueueReminder: (text3) => {
532160
- enqueueMemoryGitSyncReminder(sharedReminderStateRef.current, {
532161
- text: text3
532162
- });
532163
- }
532173
+ isEnabled: isActiveMemfsEnabled
532164
532174
  });
532165
532175
  clearPlaceholdersInText(msg);
532166
532176
  return { submitted: true };
@@ -537017,8 +537027,9 @@ Error: ${message2}`);
537017
537027
  skipPromptUpdate: shouldCreateNew
537018
537028
  })) : Promise.resolve().then(() => {
537019
537029
  if (backend3.capabilities.localMemfs) {
537020
- settingsManager.setMemfsEnabled(agentId2, true);
537021
- return { action: "enabled" };
537030
+ const enabled = !isStatelessSubagentTags(agentTags);
537031
+ settingsManager.setMemfsEnabled(agentId2, enabled);
537032
+ return { action: enabled ? "enabled" : "unchanged" };
537022
537033
  }
537023
537034
  if (memfsFlag) {
537024
537035
  throw new Error("MemFS is not supported by the active backend.");
@@ -539725,4 +539736,4 @@ function registerBunOAuthFlows() {
539725
539736
  registerBunOAuthFlows();
539726
539737
  await init_src5().then(() => exports_src2);
539727
539738
 
539728
- //# debugId=EAB82AF451FE476464756E2164756E21
539739
+ //# debugId=69C4BA5DCB08F6E364756E2164756E21