@letta-ai/letta-code 0.24.2 → 0.24.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/letta.js CHANGED
@@ -3269,7 +3269,7 @@ var package_default;
3269
3269
  var init_package = __esm(() => {
3270
3270
  package_default = {
3271
3271
  name: "@letta-ai/letta-code",
3272
- version: "0.24.2",
3272
+ version: "0.24.3",
3273
3273
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
3274
3274
  type: "module",
3275
3275
  bin: {
@@ -51442,7 +51442,8 @@ var init_readOnlyShell = __esm(() => {
51442
51442
  "sleep"
51443
51443
  ]);
51444
51444
  SAFE_LETTA_COMMANDS = {
51445
- memfs: new Set(["status", "help", "backups", "export"]),
51445
+ memory: new Set(["status", "help", "backups", "export", "tokens"]),
51446
+ memfs: new Set(["status", "help", "backups", "export", "tokens"]),
51446
51447
  agents: new Set(["list", "help"]),
51447
51448
  messages: new Set(["search", "list", "help"]),
51448
51449
  blocks: new Set(["list", "help"])
@@ -83492,46 +83493,58 @@ var init_memoryReminder = __esm(() => {
83492
83493
  };
83493
83494
  });
83494
83495
 
83495
- // src/cli/helpers/systemPromptWarning.ts
83496
+ // src/utils/systemPromptSize.ts
83496
83497
  import { existsSync as existsSync26, readdirSync as readdirSync8, readFileSync as readFileSync19 } from "node:fs";
83497
83498
  import { join as join29 } from "node:path";
83498
83499
  function estimateSystemTokens(text) {
83499
- return Math.ceil(Buffer.byteLength(text, "utf8") / STARTUP_SYSTEM_PROMPT_ESTIMATED_BYTES_PER_TOKEN);
83500
+ return Math.ceil(Buffer.byteLength(text, "utf8") / SYSTEM_PROMPT_BYTES_PER_TOKEN);
83500
83501
  }
83501
- function estimateSystemPromptTokensFromMemoryDir(memoryDir) {
83502
- const systemDir = join29(memoryDir, "system");
83503
- if (!existsSync26(systemDir)) {
83504
- return 0;
83502
+ function normalizePath2(value) {
83503
+ return value.replaceAll("\\", "/");
83504
+ }
83505
+ function walkMarkdownFiles(dir) {
83506
+ if (!existsSync26(dir)) {
83507
+ return [];
83505
83508
  }
83506
- const walkMarkdownFiles = (dir) => {
83507
- if (!existsSync26(dir)) {
83508
- return [];
83509
+ const out = [];
83510
+ const entries = readdirSync8(dir, { withFileTypes: true });
83511
+ for (const entry of entries) {
83512
+ if (entry.name.startsWith(".")) {
83513
+ continue;
83509
83514
  }
83510
- const out = [];
83511
- const entries = readdirSync8(dir, { withFileTypes: true });
83512
- for (const entry of entries) {
83513
- if (entry.name.startsWith(".")) {
83514
- continue;
83515
- }
83516
- const full = join29(dir, entry.name);
83517
- if (entry.isDirectory()) {
83518
- if (entry.name === ".git") {
83519
- continue;
83520
- }
83521
- out.push(...walkMarkdownFiles(full));
83522
- continue;
83523
- }
83524
- if (entry.isFile() && entry.name.endsWith(".md")) {
83525
- out.push(full);
83526
- }
83515
+ const full = join29(dir, entry.name);
83516
+ if (entry.isDirectory()) {
83517
+ out.push(...walkMarkdownFiles(full));
83518
+ continue;
83527
83519
  }
83528
- return out;
83529
- };
83530
- return walkMarkdownFiles(systemDir).sort().reduce((sum, filePath) => {
83520
+ if (entry.isFile() && entry.name.endsWith(".md")) {
83521
+ out.push(full);
83522
+ }
83523
+ }
83524
+ return out;
83525
+ }
83526
+ function estimateSystemPromptSize(memoryDir) {
83527
+ const systemDir = join29(memoryDir, "system");
83528
+ if (!existsSync26(systemDir)) {
83529
+ return { total: 0, files: [] };
83530
+ }
83531
+ const files = walkMarkdownFiles(systemDir).sort();
83532
+ const rows = [];
83533
+ for (const filePath of files) {
83531
83534
  const text = readFileSync19(filePath, "utf8");
83532
- return sum + estimateSystemTokens(text);
83533
- }, 0);
83535
+ const rel = normalizePath2(filePath.slice(memoryDir.length + 1));
83536
+ rows.push({ path: rel, tokens: estimateSystemTokens(text) });
83537
+ }
83538
+ const total = rows.reduce((sum, row) => sum + row.tokens, 0);
83539
+ return { total, files: rows };
83540
+ }
83541
+ function estimateSystemPromptTokensFromMemoryDir(memoryDir) {
83542
+ return estimateSystemPromptSize(memoryDir).total;
83534
83543
  }
83544
+ var SYSTEM_PROMPT_BYTES_PER_TOKEN = 4;
83545
+ var init_systemPromptSize = () => {};
83546
+
83547
+ // src/cli/helpers/systemPromptWarning.ts
83535
83548
  function setSystemPromptDoctorState(agentId, estimatedTokens) {
83536
83549
  const nextState = {
83537
83550
  estimated_tokens: estimatedTokens,
@@ -83574,11 +83587,12 @@ function buildStartupSystemPromptWarning(agentState) {
83574
83587
  }
83575
83588
  return null;
83576
83589
  }
83577
- var STARTUP_SYSTEM_PROMPT_WARNING_THRESHOLD_TOKENS = 30000, STARTUP_SYSTEM_PROMPT_ESTIMATED_BYTES_PER_TOKEN = 4, systemPromptDoctorStateByAgent;
83590
+ var STARTUP_SYSTEM_PROMPT_WARNING_THRESHOLD_TOKENS = 30000, systemPromptDoctorStateByAgent;
83578
83591
  var init_systemPromptWarning = __esm(() => {
83579
83592
  init_memoryFilesystem();
83580
83593
  init_settings_manager();
83581
83594
  init_debug();
83595
+ init_systemPromptSize();
83582
83596
  systemPromptDoctorStateByAgent = new Map;
83583
83597
  });
83584
83598
 
@@ -87183,10 +87197,132 @@ var init_reflectionTranscript = __esm(() => {
87183
87197
  init_directoryLimits();
87184
87198
  });
87185
87199
 
87200
+ // src/utils/tuiPerf.ts
87201
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync20 } from "node:fs";
87202
+ import { dirname as dirname14 } from "node:path";
87203
+ function ensureExitHook() {
87204
+ if (tuiPerfExitHookRegistered) {
87205
+ return;
87206
+ }
87207
+ tuiPerfExitHookRegistered = true;
87208
+ process.once("beforeExit", () => {
87209
+ flushTuiPerfTelemetry();
87210
+ });
87211
+ }
87212
+ function scheduleTuiPerfFlush() {
87213
+ if (tuiPerfFlushTimer) {
87214
+ return;
87215
+ }
87216
+ tuiPerfFlushTimer = setTimeout(() => {
87217
+ tuiPerfFlushTimer = null;
87218
+ flushTuiPerfTelemetry();
87219
+ }, TUI_PERF_FLUSH_INTERVAL_MS);
87220
+ const timerWithUnref = tuiPerfFlushTimer;
87221
+ timerWithUnref.unref?.();
87222
+ }
87223
+ function recordTuiPerf(key, sample) {
87224
+ if (!TUI_PERF_ENABLED || !TUI_PERF_FILE) {
87225
+ return;
87226
+ }
87227
+ ensureExitHook();
87228
+ if (tuiPerfWindowStartedAt === 0) {
87229
+ tuiPerfWindowStartedAt = Date.now();
87230
+ }
87231
+ const bytes = sample?.bytes ?? 0;
87232
+ const ms = sample?.ms ?? 0;
87233
+ const bucket = tuiPerfBuckets.get(key) ?? {
87234
+ count: 0,
87235
+ bytes: 0,
87236
+ ms: 0,
87237
+ maxBytes: 0,
87238
+ maxMs: 0
87239
+ };
87240
+ bucket.count += 1;
87241
+ bucket.bytes += bytes;
87242
+ bucket.ms += ms;
87243
+ bucket.maxBytes = Math.max(bucket.maxBytes, bytes);
87244
+ bucket.maxMs = Math.max(bucket.maxMs, ms);
87245
+ tuiPerfBuckets.set(key, bucket);
87246
+ scheduleTuiPerfFlush();
87247
+ }
87248
+ function recordTuiJsonPayload(key, value) {
87249
+ if (!TUI_PERF_ENABLED || !TUI_PERF_FILE) {
87250
+ return;
87251
+ }
87252
+ try {
87253
+ recordTuiPerf(key, { bytes: Buffer.byteLength(JSON.stringify(value)) });
87254
+ } catch {
87255
+ recordTuiPerf(key);
87256
+ }
87257
+ }
87258
+ function flushTuiPerfTelemetry() {
87259
+ if (tuiPerfBuckets.size === 0) {
87260
+ tuiPerfWindowStartedAt = 0;
87261
+ return;
87262
+ }
87263
+ const filePath = TUI_PERF_FILE;
87264
+ if (!filePath) {
87265
+ tuiPerfBuckets.clear();
87266
+ tuiPerfWindowStartedAt = 0;
87267
+ return;
87268
+ }
87269
+ const windowMs = Math.max(1, Date.now() - tuiPerfWindowStartedAt);
87270
+ const totals = {
87271
+ count: 0,
87272
+ bytes: 0,
87273
+ ms: 0,
87274
+ maxBytes: 0,
87275
+ maxMs: 0
87276
+ };
87277
+ const buckets = {};
87278
+ for (const [key, bucket] of [...tuiPerfBuckets.entries()].sort(([a], [b]) => a.localeCompare(b))) {
87279
+ totals.count += bucket.count;
87280
+ totals.bytes += bucket.bytes;
87281
+ totals.ms += bucket.ms;
87282
+ totals.maxBytes = Math.max(totals.maxBytes, bucket.maxBytes);
87283
+ totals.maxMs = Math.max(totals.maxMs, bucket.maxMs);
87284
+ buckets[key] = {
87285
+ ...bucket,
87286
+ avg_bytes: bucket.count > 0 ? bucket.bytes / bucket.count : 0,
87287
+ avg_ms: bucket.count > 0 ? bucket.ms / bucket.count : 0
87288
+ };
87289
+ }
87290
+ try {
87291
+ const dir = dirname14(filePath);
87292
+ if (tuiPerfFileDirEnsured !== dir) {
87293
+ mkdirSync20(dir, { recursive: true });
87294
+ tuiPerfFileDirEnsured = dir;
87295
+ }
87296
+ appendFileSync3(filePath, `${JSON.stringify({
87297
+ ts: new Date().toISOString(),
87298
+ event: "tui_activity",
87299
+ window_ms: windowMs,
87300
+ totals,
87301
+ buckets
87302
+ })}
87303
+ `, { encoding: "utf8" });
87304
+ } catch (error) {
87305
+ if (!tuiPerfWarningEmitted) {
87306
+ tuiPerfWarningEmitted = true;
87307
+ console.error(`[TUI Perf] Failed to write LETTA_TUI_PERF_FILE=${filePath}`, error);
87308
+ }
87309
+ } finally {
87310
+ tuiPerfBuckets.clear();
87311
+ tuiPerfWindowStartedAt = 0;
87312
+ }
87313
+ }
87314
+ var TUI_PERF_FLUSH_INTERVAL_MS = 1000, TUI_PERF_ENV_VALUES, TUI_PERF_ENABLED, TUI_PERF_FILE, tuiPerfBuckets, tuiPerfFlushTimer = null, tuiPerfWindowStartedAt = 0, tuiPerfFileDirEnsured = null, tuiPerfWarningEmitted = false, tuiPerfExitHookRegistered = false;
87315
+ var init_tuiPerf = __esm(() => {
87316
+ TUI_PERF_ENV_VALUES = new Set(["1", "true", "yes"]);
87317
+ TUI_PERF_ENABLED = TUI_PERF_ENV_VALUES.has((process.env.LETTA_TUI_PERF ?? "").toLowerCase());
87318
+ TUI_PERF_FILE = process.env.LETTA_TUI_PERF_FILE?.trim() || null;
87319
+ tuiPerfBuckets = new Map;
87320
+ });
87321
+
87186
87322
  // src/cli/helpers/chunkLog.ts
87187
87323
  import {
87188
87324
  existsSync as existsSync28,
87189
- mkdirSync as mkdirSync20,
87325
+ mkdirSync as mkdirSync21,
87190
87326
  readdirSync as readdirSync9,
87191
87327
  unlinkSync as unlinkSync7,
87192
87328
  writeFileSync as writeFileSync16
@@ -87287,7 +87423,7 @@ class ChunkLog {
87287
87423
  return;
87288
87424
  try {
87289
87425
  if (!existsSync28(this.agentDir)) {
87290
- mkdirSync20(this.agentDir, { recursive: true });
87426
+ mkdirSync21(this.agentDir, { recursive: true });
87291
87427
  }
87292
87428
  this.dirCreated = true;
87293
87429
  } catch (e) {
@@ -87526,6 +87662,7 @@ async function drainStream(stream2, buffers, refresh, abortSignal, onFirstMessag
87526
87662
  }
87527
87663
  try {
87528
87664
  for await (const chunk of stream2) {
87665
+ recordTuiJsonPayload(`stream_chunk:${chunk.message_type ?? "unknown"}`, chunk);
87529
87666
  if ((buffers.abortGeneration || 0) !== startAbortGen) {
87530
87667
  stopReason = "cancelled";
87531
87668
  queueMicrotask(refresh);
@@ -87581,6 +87718,7 @@ async function drainStream(stream2, buffers, refresh, abortSignal, onFirstMessag
87581
87718
  shouldAccumulate = shouldOutputChunk;
87582
87719
  }
87583
87720
  if (shouldAccumulate) {
87721
+ recordTuiJsonPayload(`stream_accumulate:${chunk.message_type ?? "unknown"}`, chunk);
87584
87722
  onChunk(buffers, chunk, contextTracker);
87585
87723
  queueMicrotask(refresh);
87586
87724
  }
@@ -87663,6 +87801,7 @@ async function drainStream(stream2, buffers, refresh, abortSignal, onFirstMessag
87663
87801
  }
87664
87802
  async function drainStreamWithResume(stream2, buffers, refresh, abortSignal, onFirstMessage, onChunkProcessed, contextTracker, seenSeqIdThreshold) {
87665
87803
  const overallStartTime = performance.now();
87804
+ recordTuiPerf("stream_lifecycle:start");
87666
87805
  const streamRequestContext = getStreamRequestContext(stream2);
87667
87806
  const streamOtid = streamRequestContext?.otid ?? null;
87668
87807
  let _client;
@@ -87785,6 +87924,9 @@ async function drainStreamWithResume(stream2, buffers, refresh, abortSignal, onF
87785
87924
  markCurrentLineAsFinished(buffers);
87786
87925
  }
87787
87926
  result.apiDurationMs = performance.now() - overallStartTime;
87927
+ recordTuiPerf(`stream_lifecycle:end:${result.stopReason}`, {
87928
+ ms: result.apiDurationMs
87929
+ });
87788
87930
  return result;
87789
87931
  }
87790
87932
  var FALLBACK_RUN_DISCOVERY_TIMEOUT_MS = 5000;
@@ -87795,6 +87937,7 @@ var init_stream = __esm(async () => {
87795
87937
  init_debug();
87796
87938
  init_streamAbortRelay();
87797
87939
  init_timing();
87940
+ init_tuiPerf();
87798
87941
  init_chunkLog();
87799
87942
  await __promiseAll([
87800
87943
  init_message(),
@@ -88893,22 +89036,12 @@ function emitToolExecutionFinishedEvents(socket, runtime, params) {
88893
89036
  }
88894
89037
  function createToolExecutionOutputEmitter(socket, runtime, params) {
88895
89038
  const outputByToolCallId = new Map;
88896
- return (toolCallId, chunk, isStderr = false) => {
88897
- if (!toolCallId || chunk.length === 0) {
89039
+ const emitToolOutput = (toolCallId, outputState) => {
89040
+ if (!outputState.dirty) {
88898
89041
  return;
88899
89042
  }
88900
- const existing = outputByToolCallId.get(toolCallId);
88901
- const outputState = existing ?? {
88902
- messageId: `message-tool-return-stream-${toolCallId}`,
88903
- stdout: "",
88904
- stderr: ""
88905
- };
88906
- if (isStderr) {
88907
- outputState.stderr = appendStreamingOutputWithCap(outputState.stderr, chunk);
88908
- } else {
88909
- outputState.stdout = appendStreamingOutputWithCap(outputState.stdout, chunk);
88910
- }
88911
- outputByToolCallId.set(toolCallId, outputState);
89043
+ outputState.dirty = false;
89044
+ outputState.lastEmittedAt = Date.now();
88912
89045
  const stdout = normalizeStreamingOutputLines(outputState.stdout);
88913
89046
  const stderr = normalizeStreamingOutputLines(outputState.stderr);
88914
89047
  const toolReturn = [stdout?.join(`
@@ -88938,6 +89071,52 @@ function createToolExecutionOutputEmitter(socket, runtime, params) {
88938
89071
  conversation_id: params.conversationId
88939
89072
  });
88940
89073
  };
89074
+ const flushToolOutput = (toolCallId, outputState) => {
89075
+ if (outputState.timer) {
89076
+ clearTimeout(outputState.timer);
89077
+ outputState.timer = null;
89078
+ }
89079
+ emitToolOutput(toolCallId, outputState);
89080
+ };
89081
+ const emitter = (toolCallId, chunk, isStderr = false) => {
89082
+ if (!toolCallId || chunk.length === 0) {
89083
+ return;
89084
+ }
89085
+ const existing = outputByToolCallId.get(toolCallId);
89086
+ const outputState = existing ?? {
89087
+ messageId: `message-tool-return-stream-${toolCallId}`,
89088
+ stdout: "",
89089
+ stderr: "",
89090
+ dirty: false,
89091
+ lastEmittedAt: 0,
89092
+ timer: null
89093
+ };
89094
+ if (isStderr) {
89095
+ outputState.stderr = appendStreamingOutputWithCap(outputState.stderr, chunk);
89096
+ } else {
89097
+ outputState.stdout = appendStreamingOutputWithCap(outputState.stdout, chunk);
89098
+ }
89099
+ outputByToolCallId.set(toolCallId, outputState);
89100
+ outputState.dirty = true;
89101
+ const now = Date.now();
89102
+ const elapsed = now - outputState.lastEmittedAt;
89103
+ if (elapsed >= STREAMING_TOOL_OUTPUT_EMIT_INTERVAL_MS) {
89104
+ flushToolOutput(toolCallId, outputState);
89105
+ return;
89106
+ }
89107
+ if (!outputState.timer) {
89108
+ outputState.timer = setTimeout(() => {
89109
+ outputState.timer = null;
89110
+ emitToolOutput(toolCallId, outputState);
89111
+ }, STREAMING_TOOL_OUTPUT_EMIT_INTERVAL_MS - elapsed);
89112
+ }
89113
+ };
89114
+ emitter.flush = () => {
89115
+ for (const [toolCallId, outputState] of outputByToolCallId.entries()) {
89116
+ flushToolOutput(toolCallId, outputState);
89117
+ }
89118
+ };
89119
+ return emitter;
88941
89120
  }
88942
89121
  function getInterruptApprovalsForEmission(runtime, params) {
88943
89122
  if (params.lastExecutionResults && params.lastExecutionResults.length > 0) {
@@ -89055,7 +89234,7 @@ function stashRecoveredApprovalInterrupts(runtime, recovered) {
89055
89234
  clearRecoveredApprovalState(runtime);
89056
89235
  return true;
89057
89236
  }
89058
- var INTERRUPT_TOOL_RETURN_MAX_CHARS, STREAMING_TOOL_OUTPUT_MAX_CHARS;
89237
+ var INTERRUPT_TOOL_RETURN_MAX_CHARS, STREAMING_TOOL_OUTPUT_MAX_CHARS, STREAMING_TOOL_OUTPUT_EMIT_INTERVAL_MS = 100;
89059
89238
  var init_interrupts = __esm(async () => {
89060
89239
  init_approval_result_normalization();
89061
89240
  init_constants();
@@ -89892,16 +90071,21 @@ async function resolveRecoveredApprovalResponse(runtime, socket, response, proce
89892
90071
  runtime.currentToolsetPreference = preparedToolContext.toolsetPreference;
89893
90072
  runtime.currentLoadedTools = preparedToolContext.preparedToolContext.loadedToolNames;
89894
90073
  try {
89895
- const approvalResults = await executeApprovalBatch(decisions, undefined, {
89896
- abortSignal: recoveryAbortController.signal,
89897
- onStreamingOutput: emitToolExecutionOutput,
89898
- toolContextId: preparedToolContext.preparedToolContext.contextId,
89899
- workingDirectory,
89900
- parentScope: recovered.agentId && recovered.conversationId ? {
89901
- agentId: recovered.agentId,
89902
- conversationId: recovered.conversationId
89903
- } : undefined
89904
- });
90074
+ let approvalResults;
90075
+ try {
90076
+ approvalResults = await executeApprovalBatch(decisions, undefined, {
90077
+ abortSignal: recoveryAbortController.signal,
90078
+ onStreamingOutput: emitToolExecutionOutput,
90079
+ toolContextId: preparedToolContext.preparedToolContext.contextId,
90080
+ workingDirectory,
90081
+ parentScope: recovered.agentId && recovered.conversationId ? {
90082
+ agentId: recovered.agentId,
90083
+ conversationId: recovered.conversationId
90084
+ } : undefined
90085
+ });
90086
+ } finally {
90087
+ emitToolExecutionOutput.flush();
90088
+ }
89905
90089
  emitToolExecutionFinishedEvents(socket, runtime, {
89906
90090
  approvals: approvalResults,
89907
90091
  runId: runtime.activeRunId ?? undefined,
@@ -89915,7 +90099,8 @@ async function resolveRecoveredApprovalResponse(runtime, socket, response, proce
89915
90099
  const continuationMessages = [
89916
90100
  {
89917
90101
  type: "approval",
89918
- approvals: approvalResults
90102
+ approvals: approvalResults,
90103
+ otid: crypto.randomUUID()
89919
90104
  }
89920
90105
  ];
89921
90106
  let continuationBatchId = `batch-recovered-${crypto.randomUUID()}`;
@@ -90309,6 +90494,18 @@ async function sendApprovalContinuationWithRetry(conversationId, messages, opts,
90309
90494
  retryAfterMs
90310
90495
  });
90311
90496
  transientRetries = attempt;
90497
+ const retryMessage = getRetryStatusMessage(errorDetail);
90498
+ if (retryMessage) {
90499
+ emitRetryDelta(socket, runtime, {
90500
+ message: retryMessage,
90501
+ reason: "error",
90502
+ attempt,
90503
+ maxAttempts: LLM_API_ERROR_MAX_RETRIES,
90504
+ delayMs,
90505
+ agentId: runtime.agentId ?? undefined,
90506
+ conversationId
90507
+ });
90508
+ }
90312
90509
  await new Promise((resolve27) => setTimeout(resolve27, delayMs));
90313
90510
  if (abortSignal?.aborted) {
90314
90511
  throw new Error("Cancelled by user");
@@ -90354,6 +90551,15 @@ async function sendApprovalContinuationWithRetry(conversationId, messages, opts,
90354
90551
  category: "conversation_busy",
90355
90552
  attempt: conversationBusyRetries
90356
90553
  });
90554
+ emitRetryDelta(socket, runtime, {
90555
+ message: "Conversation is busy, waiting and retrying…",
90556
+ reason: "error",
90557
+ attempt: conversationBusyRetries,
90558
+ maxAttempts: MAX_CONVERSATION_BUSY_RETRIES,
90559
+ delayMs: retryDelayMs,
90560
+ agentId: runtime.agentId ?? undefined,
90561
+ conversationId
90562
+ });
90357
90563
  await new Promise((resolve27) => setTimeout(resolve27, retryDelayMs));
90358
90564
  if (abortSignal?.aborted) {
90359
90565
  throw new Error("Cancelled by user");
@@ -92710,14 +92916,19 @@ async function handleApprovalStop(params) {
92710
92916
  }));
92711
92917
  }
92712
92918
  };
92713
- const executionResults = await executeApprovalBatch(decisions, undefined, {
92714
- toolContextId: turnToolContextId ?? undefined,
92715
- abortSignal: abortController.signal,
92716
- onStreamingOutput: emitToolExecutionOutput,
92717
- workingDirectory: turnWorkingDirectory,
92718
- parentScope: agentId && conversationId ? { agentId, conversationId } : undefined,
92719
- onFileWrite
92720
- });
92919
+ let executionResults;
92920
+ try {
92921
+ executionResults = await executeApprovalBatch(decisions, undefined, {
92922
+ toolContextId: turnToolContextId ?? undefined,
92923
+ abortSignal: abortController.signal,
92924
+ onStreamingOutput: emitToolExecutionOutput,
92925
+ workingDirectory: turnWorkingDirectory,
92926
+ parentScope: agentId && conversationId ? { agentId, conversationId } : undefined,
92927
+ onFileWrite
92928
+ });
92929
+ } finally {
92930
+ emitToolExecutionOutput.flush();
92931
+ }
92721
92932
  const persistedExecutionResults = normalizeExecutionResultsForInterruptParity(runtime, executionResults, lastExecutingToolCallIds);
92722
92933
  validateApprovalResultIds(decisions.map((decision) => ({
92723
92934
  approval: {
@@ -92738,7 +92949,8 @@ async function handleApprovalStop(params) {
92738
92949
  const nextInput = [
92739
92950
  {
92740
92951
  type: "approval",
92741
- approvals: persistedExecutionResults
92952
+ approvals: persistedExecutionResults,
92953
+ otid: crypto.randomUUID()
92742
92954
  }
92743
92955
  ];
92744
92956
  let continuationBatchId = dequeuedBatchId;
@@ -93898,7 +94110,115 @@ var init_commands = __esm(async () => {
93898
94110
  });
93899
94111
 
93900
94112
  // src/websocket/listener/protocol-outbound.ts
94113
+ import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync22 } from "node:fs";
94114
+ import { dirname as dirname15 } from "node:path";
94115
+ import { performance as performance2 } from "node:perf_hooks";
93901
94116
  import WebSocket3 from "ws";
94117
+ function getProtocolPerfKey(message) {
94118
+ if (message.type === "stream_delta" && "delta" in message) {
94119
+ const delta = message.delta;
94120
+ return `${message.type}:${String(delta.message_type ?? "unknown")}`;
94121
+ }
94122
+ return message.type;
94123
+ }
94124
+ function scheduleProtocolPerfFlush() {
94125
+ if (protocolPerfFlushTimer) {
94126
+ return;
94127
+ }
94128
+ protocolPerfFlushTimer = setTimeout(() => {
94129
+ protocolPerfFlushTimer = null;
94130
+ flushProtocolPerfTelemetry();
94131
+ }, PROTOCOL_PERF_FLUSH_INTERVAL_MS);
94132
+ const timerWithUnref = protocolPerfFlushTimer;
94133
+ timerWithUnref.unref?.();
94134
+ }
94135
+ function recordProtocolPerfTelemetry(key, sample) {
94136
+ if (protocolPerfWindowStartedAt === 0) {
94137
+ protocolPerfWindowStartedAt = Date.now();
94138
+ }
94139
+ const bucket = protocolPerfBuckets.get(key) ?? {
94140
+ count: 0,
94141
+ bytes: 0,
94142
+ stringifyMs: 0,
94143
+ sendMs: 0,
94144
+ maxBufferedBefore: 0,
94145
+ maxBufferedAfter: 0
94146
+ };
94147
+ bucket.count += 1;
94148
+ bucket.bytes += sample.bytes;
94149
+ bucket.stringifyMs += sample.stringifyMs;
94150
+ bucket.sendMs += sample.sendMs;
94151
+ bucket.maxBufferedBefore = Math.max(bucket.maxBufferedBefore, sample.bufferedBefore);
94152
+ bucket.maxBufferedAfter = Math.max(bucket.maxBufferedAfter, sample.bufferedAfter);
94153
+ protocolPerfBuckets.set(key, bucket);
94154
+ scheduleProtocolPerfFlush();
94155
+ }
94156
+ function writeProtocolPerfFile(record, fallbackLine) {
94157
+ const filePath = PROTOCOL_PERF_FILE;
94158
+ if (!filePath) {
94159
+ console.error(fallbackLine);
94160
+ return;
94161
+ }
94162
+ try {
94163
+ const dir = dirname15(filePath);
94164
+ if (protocolPerfFileDirEnsured !== dir) {
94165
+ mkdirSync22(dir, { recursive: true });
94166
+ protocolPerfFileDirEnsured = dir;
94167
+ }
94168
+ appendFileSync4(filePath, `${JSON.stringify(record)}
94169
+ `, {
94170
+ encoding: "utf8"
94171
+ });
94172
+ } catch (error) {
94173
+ if (!protocolPerfFileWarningEmitted) {
94174
+ protocolPerfFileWarningEmitted = true;
94175
+ console.error(`[Listen Perf] Failed to write LETTA_LISTENER_PERF_FILE=${filePath}`, error);
94176
+ }
94177
+ console.error(fallbackLine);
94178
+ }
94179
+ }
94180
+ function flushProtocolPerfTelemetry() {
94181
+ if (protocolPerfBuckets.size === 0) {
94182
+ protocolPerfWindowStartedAt = 0;
94183
+ return;
94184
+ }
94185
+ const windowMs = Math.max(1, Date.now() - protocolPerfWindowStartedAt);
94186
+ const totals = {
94187
+ count: 0,
94188
+ bytes: 0,
94189
+ stringifyMs: 0,
94190
+ sendMs: 0,
94191
+ maxBufferedBefore: 0,
94192
+ maxBufferedAfter: 0
94193
+ };
94194
+ const buckets = {};
94195
+ const parts = [...protocolPerfBuckets.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([key, bucket]) => {
94196
+ totals.count += bucket.count;
94197
+ totals.bytes += bucket.bytes;
94198
+ totals.stringifyMs += bucket.stringifyMs;
94199
+ totals.sendMs += bucket.sendMs;
94200
+ totals.maxBufferedBefore = Math.max(totals.maxBufferedBefore, bucket.maxBufferedBefore);
94201
+ totals.maxBufferedAfter = Math.max(totals.maxBufferedAfter, bucket.maxBufferedAfter);
94202
+ buckets[key] = {
94203
+ ...bucket,
94204
+ avg_bytes: bucket.count > 0 ? bucket.bytes / bucket.count : 0,
94205
+ avg_stringify_ms: bucket.count > 0 ? bucket.stringifyMs / bucket.count : 0,
94206
+ avg_send_ms: bucket.count > 0 ? bucket.sendMs / bucket.count : 0
94207
+ };
94208
+ const stringifyMs = bucket.stringifyMs.toFixed(2);
94209
+ const sendMs = bucket.sendMs.toFixed(2);
94210
+ return `${key}{count=${bucket.count},bytes=${bucket.bytes},stringify_ms=${stringifyMs},send_ms=${sendMs},max_buffered_before=${bucket.maxBufferedBefore},max_buffered_after=${bucket.maxBufferedAfter}}`;
94211
+ });
94212
+ writeProtocolPerfFile({
94213
+ ts: new Date().toISOString(),
94214
+ event: "protocol_emit",
94215
+ window_ms: windowMs,
94216
+ totals,
94217
+ buckets
94218
+ }, `[Listen Perf] protocol_emit window_ms=${windowMs} ${parts.join(" ")}`);
94219
+ protocolPerfBuckets.clear();
94220
+ protocolPerfWindowStartedAt = 0;
94221
+ }
93902
94222
  function getCachedDeviceGitContext(cwd2) {
93903
94223
  const now = Date.now();
93904
94224
  const cached = gitContextCache.get(cwd2);
@@ -94105,8 +94425,24 @@ function emitProtocolV2Message(socket, runtime, message, scope) {
94105
94425
  emitted_at: new Date().toISOString(),
94106
94426
  idempotency_key: `${message.type}:${eventSeq}:${crypto.randomUUID()}`
94107
94427
  };
94428
+ const perfEnabled = PROTOCOL_PERF_ENABLED;
94429
+ const stringifyStartedAt = perfEnabled ? performance2.now() : 0;
94430
+ let payload;
94108
94431
  try {
94109
- socket.send(JSON.stringify(outbound));
94432
+ payload = JSON.stringify(outbound);
94433
+ const stringifyMs = perfEnabled ? performance2.now() - stringifyStartedAt : 0;
94434
+ const bufferedBefore = perfEnabled ? socket.bufferedAmount : 0;
94435
+ const sendStartedAt = perfEnabled ? performance2.now() : 0;
94436
+ socket.send(payload);
94437
+ if (perfEnabled) {
94438
+ recordProtocolPerfTelemetry(getProtocolPerfKey(message), {
94439
+ bytes: Buffer.byteLength(payload),
94440
+ stringifyMs,
94441
+ sendMs: performance2.now() - sendStartedAt,
94442
+ bufferedBefore,
94443
+ bufferedAfter: socket.bufferedAmount
94444
+ });
94445
+ }
94110
94446
  } catch (error) {
94111
94447
  console.error(`[Listen V2] Failed to emit ${message.type} (seq=${eventSeq})`, error);
94112
94448
  safeEmitWsEvent("send", "lifecycle", {
@@ -94340,7 +94676,7 @@ function emitStreamDelta(socket, runtime, delta, scope, subagentId) {
94340
94676
  };
94341
94677
  emitProtocolV2Message(socket, runtime, message, scope);
94342
94678
  }
94343
- var GIT_CONTEXT_CACHE_TTL_MS = 15000, MAX_GIT_CONTEXT_CACHE_ENTRIES = 64, gitContextCache;
94679
+ var GIT_CONTEXT_CACHE_TTL_MS = 15000, MAX_GIT_CONTEXT_CACHE_ENTRIES = 64, PROTOCOL_PERF_FLUSH_INTERVAL_MS = 1000, PROTOCOL_PERF_ENV_VALUES, PROTOCOL_PERF_ENABLED, PROTOCOL_PERF_FILE, protocolPerfBuckets, protocolPerfFlushTimer = null, protocolPerfWindowStartedAt = 0, protocolPerfFileDirEnsured = null, protocolPerfFileWarningEmitted = false, gitContextCache;
94344
94680
  var init_protocol_outbound = __esm(async () => {
94345
94681
  init_memoryFilesystem();
94346
94682
  init_gitContext();
@@ -94356,6 +94692,10 @@ var init_protocol_outbound = __esm(async () => {
94356
94692
  init_permissionMode();
94357
94693
  init_runtime4();
94358
94694
  await init_commands();
94695
+ PROTOCOL_PERF_ENV_VALUES = new Set(["1", "true", "yes"]);
94696
+ PROTOCOL_PERF_ENABLED = PROTOCOL_PERF_ENV_VALUES.has((process.env.LETTA_LISTENER_PERF ?? "").toLowerCase());
94697
+ PROTOCOL_PERF_FILE = process.env.LETTA_LISTENER_PERF_FILE?.trim() || null;
94698
+ protocolPerfBuckets = new Map;
94359
94699
  gitContextCache = new Map;
94360
94700
  });
94361
94701
 
@@ -97555,7 +97895,7 @@ async function handleSkillCommand(parsed, socket) {
97555
97895
  const {
97556
97896
  existsSync: existsSync30,
97557
97897
  lstatSync: lstatSync2,
97558
- mkdirSync: mkdirSync21,
97898
+ mkdirSync: mkdirSync23,
97559
97899
  rmdirSync,
97560
97900
  symlinkSync,
97561
97901
  unlinkSync: unlinkSync8
@@ -97586,7 +97926,7 @@ async function handleSkillCommand(parsed, socket) {
97586
97926
  }
97587
97927
  const linkName = basename13(parsed.skill_path);
97588
97928
  const linkPath = join34(globalSkillsDir, linkName);
97589
- mkdirSync21(globalSkillsDir, { recursive: true });
97929
+ mkdirSync23(globalSkillsDir, { recursive: true });
97590
97930
  if (existsSync30(linkPath)) {
97591
97931
  const stat7 = lstatSync2(linkPath);
97592
97932
  if (stat7.isSymbolicLink()) {
@@ -99786,9 +100126,9 @@ __export(exports_debug2, {
99786
100126
  debugLog: () => debugLog3
99787
100127
  });
99788
100128
  import {
99789
- appendFileSync as appendFileSync3,
100129
+ appendFileSync as appendFileSync5,
99790
100130
  existsSync as existsSync31,
99791
- mkdirSync as mkdirSync22,
100131
+ mkdirSync as mkdirSync24,
99792
100132
  readdirSync as readdirSync11,
99793
100133
  readFileSync as readFileSync22,
99794
100134
  unlinkSync as unlinkSync8
@@ -99809,7 +100149,7 @@ function printDebugLine2(line, level = "log") {
99809
100149
  const debugFile = getDebugFile2();
99810
100150
  if (debugFile) {
99811
100151
  try {
99812
- appendFileSync3(debugFile, line, { encoding: "utf8" });
100152
+ appendFileSync5(debugFile, line, { encoding: "utf8" });
99813
100153
  return;
99814
100154
  } catch {}
99815
100155
  }
@@ -99835,7 +100175,7 @@ class DebugLogFile2 {
99835
100175
  return;
99836
100176
  this.ensureDir();
99837
100177
  try {
99838
- appendFileSync3(this.logPath, line, { encoding: "utf8" });
100178
+ appendFileSync5(this.logPath, line, { encoding: "utf8" });
99839
100179
  } catch {}
99840
100180
  }
99841
100181
  getTail(maxLines = DEFAULT_TAIL_LINES2) {
@@ -99858,7 +100198,7 @@ class DebugLogFile2 {
99858
100198
  return;
99859
100199
  try {
99860
100200
  if (!existsSync31(this.agentDir)) {
99861
- mkdirSync22(this.agentDir, { recursive: true });
100201
+ mkdirSync24(this.agentDir, { recursive: true });
99862
100202
  }
99863
100203
  this.dirCreated = true;
99864
100204
  } catch {}
@@ -99928,10 +100268,10 @@ __export(exports_skills2, {
99928
100268
  });
99929
100269
  import { existsSync as existsSync32 } from "node:fs";
99930
100270
  import { readdir as readdir8, readFile as readFile13, realpath as realpath4, stat as stat7 } from "node:fs/promises";
99931
- import { dirname as dirname14, join as join38 } from "node:path";
100271
+ import { dirname as dirname16, join as join38 } from "node:path";
99932
100272
  import { fileURLToPath as fileURLToPath9 } from "node:url";
99933
100273
  function getBundledSkillsPath2() {
99934
- const thisDir = dirname14(fileURLToPath9(import.meta.url));
100274
+ const thisDir = dirname16(fileURLToPath9(import.meta.url));
99935
100275
  if (thisDir.includes("src/agent") || thisDir.includes("src\\agent")) {
99936
100276
  return join38(thisDir, "../skills/builtin");
99937
100277
  }
@@ -100125,16 +100465,16 @@ import {
100125
100465
  existsSync as existsSync33,
100126
100466
  readFileSync as fsReadFileSync2,
100127
100467
  writeFileSync as fsWriteFileSync2,
100128
- mkdirSync as mkdirSync23
100468
+ mkdirSync as mkdirSync25
100129
100469
  } from "node:fs";
100130
- import { dirname as dirname15 } from "node:path";
100470
+ import { dirname as dirname17 } from "node:path";
100131
100471
  async function readFile14(path26) {
100132
100472
  return fsReadFileSync2(path26, { encoding: "utf-8" });
100133
100473
  }
100134
100474
  async function writeFile12(path26, content) {
100135
- const dir = dirname15(path26);
100475
+ const dir = dirname17(path26);
100136
100476
  if (!existsSync33(dir)) {
100137
- mkdirSync23(dir, { recursive: true });
100477
+ mkdirSync25(dir, { recursive: true });
100138
100478
  }
100139
100479
  fsWriteFileSync2(path26, content, { encoding: "utf-8", flush: true });
100140
100480
  }
@@ -100142,7 +100482,7 @@ function exists2(path26) {
100142
100482
  return existsSync33(path26);
100143
100483
  }
100144
100484
  async function mkdir9(path26, options) {
100145
- mkdirSync23(path26, options);
100485
+ mkdirSync25(path26, options);
100146
100486
  }
100147
100487
  async function readJsonFile(path26) {
100148
100488
  const text = await readFile14(path26);
@@ -100275,7 +100615,7 @@ var exports_bootstrap_tools = {};
100275
100615
  __export(exports_bootstrap_tools, {
100276
100616
  bootstrapBaseToolsIfNeeded: () => bootstrapBaseToolsIfNeeded
100277
100617
  });
100278
- import { existsSync as existsSync34, mkdirSync as mkdirSync24, writeFileSync as writeFileSync17 } from "node:fs";
100618
+ import { existsSync as existsSync34, mkdirSync as mkdirSync26, writeFileSync as writeFileSync17 } from "node:fs";
100279
100619
  import { homedir as homedir27 } from "node:os";
100280
100620
  import { join as join39 } from "node:path";
100281
100621
  async function bootstrapBaseToolsIfNeeded() {
@@ -100285,7 +100625,7 @@ async function bootstrapBaseToolsIfNeeded() {
100285
100625
  try {
100286
100626
  const success = await addBaseToolsToServer();
100287
100627
  if (success) {
100288
- mkdirSync24(join39(homedir27(), ".letta"), { recursive: true });
100628
+ mkdirSync26(join39(homedir27(), ".letta"), { recursive: true });
100289
100629
  writeFileSync17(MARKER_PATH, new Date().toISOString(), "utf-8");
100290
100630
  }
100291
100631
  } catch (err) {
@@ -101989,7 +102329,7 @@ __export(exports_import, {
101989
102329
  });
101990
102330
  import { createReadStream } from "node:fs";
101991
102331
  import { chmod, mkdir as mkdir10, readFile as readFile15, writeFile as writeFile13 } from "node:fs/promises";
101992
- import { dirname as dirname16, resolve as resolve30 } from "node:path";
102332
+ import { dirname as dirname18, resolve as resolve30 } from "node:path";
101993
102333
  async function importAgentFromFile(options) {
101994
102334
  const client = await getClient();
101995
102335
  const resolvedPath = resolve30(options.filePath);
@@ -102048,7 +102388,7 @@ async function writeSkillFiles(skillDir, files) {
102048
102388
  }
102049
102389
  async function writeSkillFile(skillDir, filePath, content) {
102050
102390
  const fullPath = resolve30(skillDir, filePath);
102051
- await mkdir10(dirname16(fullPath), { recursive: true });
102391
+ await mkdir10(dirname18(fullPath), { recursive: true });
102052
102392
  await writeFile13(fullPath, content, "utf-8");
102053
102393
  const isScript = filePath.startsWith("scripts/") || content.trimStart().startsWith("#!");
102054
102394
  if (isScript) {
@@ -124703,7 +125043,7 @@ html.dark .agent-name { color: var(--text-dim); }
124703
125043
  var init_plan_viewer_template = () => {};
124704
125044
 
124705
125045
  // src/web/generate-plan-viewer.ts
124706
- import { chmodSync as chmodSync2, existsSync as existsSync37, mkdirSync as mkdirSync27, writeFileSync as writeFileSync20 } from "node:fs";
125046
+ import { chmodSync as chmodSync2, existsSync as existsSync37, mkdirSync as mkdirSync29, writeFileSync as writeFileSync20 } from "node:fs";
124707
125047
  import { homedir as homedir31 } from "node:os";
124708
125048
  import { join as join44 } from "node:path";
124709
125049
  async function generateAndOpenPlanViewer(planContent, planFilePath, options) {
@@ -124716,7 +125056,7 @@ async function generateAndOpenPlanViewer(planContent, planFilePath, options) {
124716
125056
  const jsonPayload = JSON.stringify(data).replace(/</g, "\\u003c");
124717
125057
  const html = plan_viewer_template_default.replace("<!--LETTA_PLAN_DATA_PLACEHOLDER-->", () => jsonPayload);
124718
125058
  if (!existsSync37(VIEWERS_DIR)) {
124719
- mkdirSync27(VIEWERS_DIR, { recursive: true, mode: 448 });
125059
+ mkdirSync29(VIEWERS_DIR, { recursive: true, mode: 448 });
124720
125060
  }
124721
125061
  try {
124722
125062
  chmodSync2(VIEWERS_DIR, 448);
@@ -128026,12 +128366,12 @@ __export(exports_terminalKeybindingInstaller, {
128026
128366
  import {
128027
128367
  copyFileSync,
128028
128368
  existsSync as existsSync39,
128029
- mkdirSync as mkdirSync28,
128369
+ mkdirSync as mkdirSync30,
128030
128370
  readFileSync as readFileSync25,
128031
128371
  writeFileSync as writeFileSync21
128032
128372
  } from "node:fs";
128033
128373
  import { homedir as homedir32, platform as platform6 } from "node:os";
128034
- import { dirname as dirname17, join as join46 } from "node:path";
128374
+ import { dirname as dirname19, join as join46 } from "node:path";
128035
128375
  function detectTerminalType() {
128036
128376
  if (process.env.CURSOR_TRACE_ID || process.env.CURSOR_CHANNEL) {
128037
128377
  return "cursor";
@@ -128122,9 +128462,9 @@ function installKeybinding(keybindingsPath) {
128122
128462
  if (keybindingExists(keybindingsPath)) {
128123
128463
  return { success: true, alreadyExists: true };
128124
128464
  }
128125
- const parentDir = dirname17(keybindingsPath);
128465
+ const parentDir = dirname19(keybindingsPath);
128126
128466
  if (!existsSync39(parentDir)) {
128127
- mkdirSync28(parentDir, { recursive: true });
128467
+ mkdirSync30(parentDir, { recursive: true });
128128
128468
  }
128129
128469
  let keybindings = [];
128130
128470
  let backupPath = null;
@@ -128281,9 +128621,9 @@ return config`);
128281
128621
  ${WEZTERM_DELETE_FIX}
128282
128622
  `;
128283
128623
  }
128284
- const parentDir = dirname17(configPath);
128624
+ const parentDir = dirname19(configPath);
128285
128625
  if (!existsSync39(parentDir)) {
128286
- mkdirSync28(parentDir, { recursive: true });
128626
+ mkdirSync30(parentDir, { recursive: true });
128287
128627
  }
128288
128628
  writeFileSync21(configPath, content, { encoding: "utf-8" });
128289
128629
  return {
@@ -128881,7 +129221,7 @@ __export(exports_custom, {
128881
129221
  });
128882
129222
  import { existsSync as existsSync40 } from "node:fs";
128883
129223
  import { readdir as readdir10, readFile as readFile16 } from "node:fs/promises";
128884
- import { basename as basename14, dirname as dirname18, join as join47 } from "node:path";
129224
+ import { basename as basename14, dirname as dirname20, join as join47 } from "node:path";
128885
129225
  async function getCustomCommands() {
128886
129226
  if (cachedCommands !== null) {
128887
129227
  return cachedCommands;
@@ -128942,7 +129282,7 @@ async function parseCommandFile(filePath, rootPath, source2) {
128942
129282
  const content = await readFile16(filePath, "utf-8");
128943
129283
  const { frontmatter, body } = parseFrontmatter(content);
128944
129284
  const id = basename14(filePath, ".md");
128945
- const relativePath = dirname18(filePath).slice(rootPath.length);
129285
+ const relativePath = dirname20(filePath).slice(rootPath.length);
128946
129286
  const namespace = relativePath.replace(/^[/\\]/, "") || undefined;
128947
129287
  let description = getStringField(frontmatter, "description");
128948
129288
  if (!description) {
@@ -134102,14 +134442,14 @@ var init_InputRich = __esm(async () => {
134102
134442
  import { execFileSync as execFileSync4 } from "node:child_process";
134103
134443
  import {
134104
134444
  existsSync as existsSync41,
134105
- mkdirSync as mkdirSync29,
134445
+ mkdirSync as mkdirSync31,
134106
134446
  mkdtempSync,
134107
134447
  readFileSync as readFileSync26,
134108
134448
  rmSync as rmSync4,
134109
134449
  writeFileSync as writeFileSync22
134110
134450
  } from "node:fs";
134111
134451
  import { tmpdir as tmpdir6 } from "node:os";
134112
- import { dirname as dirname19, join as join49 } from "node:path";
134452
+ import { dirname as dirname21, join as join49 } from "node:path";
134113
134453
  function runCommand(command, args, cwd2, input) {
134114
134454
  try {
134115
134455
  return execFileSync4(command, args, {
@@ -134356,8 +134696,8 @@ function runGit3(args, cwd2) {
134356
134696
  }
134357
134697
  function writeWorkflow(repoDir, workflowPath, content) {
134358
134698
  const absolutePath = join49(repoDir, workflowPath);
134359
- if (!existsSync41(dirname19(absolutePath))) {
134360
- mkdirSync29(dirname19(absolutePath), { recursive: true });
134699
+ if (!existsSync41(dirname21(absolutePath))) {
134700
+ mkdirSync31(dirname21(absolutePath), { recursive: true });
134361
134701
  }
134362
134702
  const next = `${content.trimEnd()}
134363
134703
  `;
@@ -138599,7 +138939,7 @@ __export(exports_generate_memory_viewer, {
138599
138939
  generateAndOpenMemoryViewer: () => generateAndOpenMemoryViewer
138600
138940
  });
138601
138941
  import { execFile as execFileCb3 } from "node:child_process";
138602
- import { chmodSync as chmodSync3, existsSync as existsSync42, mkdirSync as mkdirSync30, writeFileSync as writeFileSync23 } from "node:fs";
138942
+ import { chmodSync as chmodSync3, existsSync as existsSync42, mkdirSync as mkdirSync32, writeFileSync as writeFileSync23 } from "node:fs";
138603
138943
  import { homedir as homedir34 } from "node:os";
138604
138944
  import { join as join50 } from "node:path";
138605
138945
  import { promisify as promisify13 } from "node:util";
@@ -138920,7 +139260,7 @@ async function generateAndOpenMemoryViewer(agentId, options) {
138920
139260
  const jsonPayload = JSON.stringify(data).replace(/</g, "\\u003c");
138921
139261
  const html = memory_viewer_template_default.replace("<!--LETTA_DATA_PLACEHOLDER-->", () => jsonPayload);
138922
139262
  if (!existsSync42(VIEWERS_DIR2)) {
138923
- mkdirSync30(VIEWERS_DIR2, { recursive: true, mode: 448 });
139263
+ mkdirSync32(VIEWERS_DIR2, { recursive: true, mode: 448 });
138924
139264
  }
138925
139265
  try {
138926
139266
  chmodSync3(VIEWERS_DIR2, 448);
@@ -142730,7 +143070,7 @@ function formatDuration3(ms) {
142730
143070
  }
142731
143071
  return `${(ms / 1000).toFixed(1)}s`;
142732
143072
  }
142733
- function formatNumber(n) {
143073
+ function formatNumber2(n) {
142734
143074
  return n.toLocaleString();
142735
143075
  }
142736
143076
  function formatUsageStats({
@@ -142752,7 +143092,7 @@ function formatUsageStats({
142752
143092
  const monthlyCredits = Math.round(balance.monthly_credit_balance);
142753
143093
  const purchasedCredits = Math.round(balance.purchased_credit_balance);
142754
143094
  const toDollars = (credits) => (credits / 1000).toFixed(2);
142755
- outputLines.push(`Plan: [${balance.billing_tier}]`, buildAppUrl("/settings/organization/usage"), "", `Available credits: ◎${formatNumber(totalCredits)} ($${toDollars(totalCredits)})`, `Monthly credits: ◎${formatNumber(monthlyCredits)} ($${toDollars(monthlyCredits)})`, `Purchased credits: ◎${formatNumber(purchasedCredits)} ($${toDollars(purchasedCredits)})`);
143095
+ outputLines.push(`Plan: [${balance.billing_tier}]`, buildAppUrl("/settings/organization/usage"), "", `Available credits: ◎${formatNumber2(totalCredits)} ($${toDollars(totalCredits)})`, `Monthly credits: ◎${formatNumber2(monthlyCredits)} ($${toDollars(monthlyCredits)})`, `Purchased credits: ◎${formatNumber2(purchasedCredits)} ($${toDollars(purchasedCredits)})`);
142756
143096
  }
142757
143097
  return outputLines.join(`
142758
143098
  `);
@@ -150023,6 +150363,7 @@ function App2({
150023
150363
  streamingRefreshTimeoutRef.current = setTimeout(() => {
150024
150364
  streamingRefreshTimeoutRef.current = null;
150025
150365
  if (!buffersRef.current.interrupted) {
150366
+ recordTuiPerf("ui_refresh:tool_output");
150026
150367
  refreshDerived();
150027
150368
  }
150028
150369
  }, 100);
@@ -150035,6 +150376,9 @@ function App2({
150035
150376
  };
150036
150377
  }, []);
150037
150378
  const updateStreamingOutput = import_react104.useCallback((toolCallId, chunk, isStderr = false) => {
150379
+ recordTuiPerf(`tool_output:${isStderr ? "stderr" : "stdout"}`, {
150380
+ bytes: Buffer.byteLength(chunk)
150381
+ });
150038
150382
  const lineId = buffersRef.current.toolCallIdToLineId.get(toolCallId);
150039
150383
  if (!lineId)
150040
150384
  return;
@@ -150055,6 +150399,7 @@ function App2({
150055
150399
  setTimeout(() => {
150056
150400
  buffersRef.current.pendingRefresh = false;
150057
150401
  if (!buffersRef.current.interrupted && (buffersRef.current.commitGeneration || 0) === capturedGeneration) {
150402
+ recordTuiPerf("ui_refresh:stream");
150058
150403
  refreshDerived();
150059
150404
  }
150060
150405
  }, 16);
@@ -157568,6 +157913,7 @@ var init_App2 = __esm(async () => {
157568
157913
  init_telemetry();
157569
157914
  init_toolset_labels();
157570
157915
  init_debug();
157916
+ init_tuiPerf();
157571
157917
  init_version();
157572
157918
  init_mcp();
157573
157919
  init_profile();
@@ -157748,12 +158094,12 @@ __export(exports_terminalKeybindingInstaller2, {
157748
158094
  import {
157749
158095
  copyFileSync as copyFileSync2,
157750
158096
  existsSync as existsSync46,
157751
- mkdirSync as mkdirSync31,
158097
+ mkdirSync as mkdirSync33,
157752
158098
  readFileSync as readFileSync29,
157753
158099
  writeFileSync as writeFileSync25
157754
158100
  } from "node:fs";
157755
158101
  import { homedir as homedir38, platform as platform7 } from "node:os";
157756
- import { dirname as dirname20, join as join54 } from "node:path";
158102
+ import { dirname as dirname22, join as join54 } from "node:path";
157757
158103
  function detectTerminalType2() {
157758
158104
  if (process.env.CURSOR_TRACE_ID || process.env.CURSOR_CHANNEL) {
157759
158105
  return "cursor";
@@ -157844,9 +158190,9 @@ function installKeybinding2(keybindingsPath) {
157844
158190
  if (keybindingExists2(keybindingsPath)) {
157845
158191
  return { success: true, alreadyExists: true };
157846
158192
  }
157847
- const parentDir = dirname20(keybindingsPath);
158193
+ const parentDir = dirname22(keybindingsPath);
157848
158194
  if (!existsSync46(parentDir)) {
157849
- mkdirSync31(parentDir, { recursive: true });
158195
+ mkdirSync33(parentDir, { recursive: true });
157850
158196
  }
157851
158197
  let keybindings = [];
157852
158198
  let backupPath = null;
@@ -158003,9 +158349,9 @@ return config`);
158003
158349
  ${WEZTERM_DELETE_FIX2}
158004
158350
  `;
158005
158351
  }
158006
- const parentDir = dirname20(configPath);
158352
+ const parentDir = dirname22(configPath);
158007
158353
  if (!existsSync46(parentDir)) {
158008
- mkdirSync31(parentDir, { recursive: true });
158354
+ mkdirSync33(parentDir, { recursive: true });
158009
158355
  }
158010
158356
  writeFileSync25(configPath, content, { encoding: "utf-8" });
158011
158357
  return {
@@ -158573,7 +158919,7 @@ __export(exports_import2, {
158573
158919
  });
158574
158920
  import { createReadStream as createReadStream2 } from "node:fs";
158575
158921
  import { chmod as chmod2, mkdir as mkdir11, readFile as readFile19, writeFile as writeFile14 } from "node:fs/promises";
158576
- import { dirname as dirname21, resolve as resolve35 } from "node:path";
158922
+ import { dirname as dirname23, resolve as resolve35 } from "node:path";
158577
158923
  async function importAgentFromFile2(options) {
158578
158924
  const client = await getClient();
158579
158925
  const resolvedPath = resolve35(options.filePath);
@@ -158632,7 +158978,7 @@ async function writeSkillFiles2(skillDir, files) {
158632
158978
  }
158633
158979
  async function writeSkillFile2(skillDir, filePath, content) {
158634
158980
  const fullPath = resolve35(skillDir, filePath);
158635
- await mkdir11(dirname21(fullPath), { recursive: true });
158981
+ await mkdir11(dirname23(fullPath), { recursive: true });
158636
158982
  await writeFile14(fullPath, content, "utf-8");
158637
158983
  const isScript = filePath.startsWith("scripts/") || content.trimStart().startsWith("#!");
158638
158984
  if (isScript) {
@@ -158742,7 +159088,7 @@ __export(exports_memoryFilesystem2, {
158742
159088
  MEMORY_FS_MEMORY_DIR: () => MEMORY_FS_MEMORY_DIR2,
158743
159089
  MEMORY_FS_AGENTS_DIR: () => MEMORY_FS_AGENTS_DIR2
158744
159090
  });
158745
- import { existsSync as existsSync47, mkdirSync as mkdirSync32 } from "node:fs";
159091
+ import { existsSync as existsSync47, mkdirSync as mkdirSync34 } from "node:fs";
158746
159092
  import { homedir as homedir40 } from "node:os";
158747
159093
  import { join as join56, resolve as resolve36 } from "node:path";
158748
159094
  function getMemoryFilesystemRoot2(agentId, homeDir = homedir40()) {
@@ -158778,10 +159124,10 @@ function ensureMemoryFilesystemDirs2(agentId, homeDir = homedir40()) {
158778
159124
  const root = getMemoryFilesystemRoot2(agentId, homeDir);
158779
159125
  const systemDir = getMemorySystemDir2(agentId, homeDir);
158780
159126
  if (!existsSync47(root)) {
158781
- mkdirSync32(root, { recursive: true });
159127
+ mkdirSync34(root, { recursive: true });
158782
159128
  }
158783
159129
  if (!existsSync47(systemDir)) {
158784
- mkdirSync32(systemDir, { recursive: true });
159130
+ mkdirSync34(systemDir, { recursive: true });
158785
159131
  }
158786
159132
  }
158787
159133
  async function isMemfsEnabledOnServer2(agentId) {
@@ -164102,51 +164448,141 @@ async function runListenSubcommand(argv) {
164102
164448
  }
164103
164449
  }
164104
164450
 
164105
- // src/cli/subcommands/memfs.ts
164451
+ // src/cli/subcommands/memory.ts
164106
164452
  init_memoryGit();
164107
- import { cpSync, existsSync as existsSync30, mkdirSync as mkdirSync21, rmSync as rmSync3, statSync as statSync8 } from "node:fs";
164453
+ import { cpSync, existsSync as existsSync30, mkdirSync as mkdirSync23, rmSync as rmSync3, statSync as statSync8 } from "node:fs";
164108
164454
  import { readdir as readdir7 } from "node:fs/promises";
164109
164455
  import { homedir as homedir23 } from "node:os";
164110
164456
  import { join as join34 } from "node:path";
164111
164457
  import { parseArgs as parseArgs8 } from "node:util";
164458
+
164459
+ // src/cli/subcommands/memoryTokens.ts
164460
+ init_systemPromptSize();
164461
+ var DEFAULT_TOP = 20;
164462
+ var USAGE_EXIT = 64;
164463
+ var IO_EXIT = 65;
164464
+ function parsePositiveInt(raw, fallback) {
164465
+ if (raw === undefined)
164466
+ return fallback;
164467
+ if (!/^\d+$/.test(raw))
164468
+ return null;
164469
+ return Number.parseInt(raw, 10);
164470
+ }
164471
+ function formatNumber(value) {
164472
+ return value.toLocaleString("en-US");
164473
+ }
164474
+ function printText(total, files, top, quiet) {
164475
+ console.log("System prompt token estimate");
164476
+ console.log(` Total: ${formatNumber(total)} tokens`);
164477
+ if (quiet || top <= 0 || files.length === 0) {
164478
+ return;
164479
+ }
164480
+ const ranked = [...files].sort((a, b) => b.tokens - a.tokens);
164481
+ const limited = ranked.slice(0, top);
164482
+ console.log("");
164483
+ console.log("Top files:");
164484
+ console.log(` ${"tokens".padStart(8)} path`);
164485
+ for (const row of limited) {
164486
+ console.log(` ${formatNumber(row.tokens).padStart(8)} ${row.path}`);
164487
+ }
164488
+ }
164489
+ function printJson(total, files) {
164490
+ console.log(JSON.stringify({
164491
+ total_tokens: total,
164492
+ files
164493
+ }, null, 2));
164494
+ }
164495
+ function resolveMemoryDir3(options) {
164496
+ if (options.memoryDir)
164497
+ return options.memoryDir;
164498
+ if (process.env.MEMORY_DIR)
164499
+ return process.env.MEMORY_DIR;
164500
+ if (options.agentMemoryDir)
164501
+ return options.agentMemoryDir;
164502
+ return null;
164503
+ }
164504
+ async function runMemoryTokensAction(options) {
164505
+ const format2 = options.format ?? "text";
164506
+ if (format2 !== "text" && format2 !== "json") {
164507
+ console.error(`Invalid --format: ${format2} (expected text or json)`);
164508
+ return USAGE_EXIT;
164509
+ }
164510
+ const top = parsePositiveInt(options.top, DEFAULT_TOP);
164511
+ if (top === null) {
164512
+ console.error(`Invalid --top: ${options.top} (expected non-negative integer)`);
164513
+ return USAGE_EXIT;
164514
+ }
164515
+ const memoryDir = resolveMemoryDir3(options);
164516
+ if (!memoryDir) {
164517
+ console.error("Missing memory dir. Set --memory-dir, --agent, $MEMORY_DIR, or $LETTA_AGENT_ID.");
164518
+ return USAGE_EXIT;
164519
+ }
164520
+ let estimate;
164521
+ try {
164522
+ estimate = estimateSystemPromptSize(memoryDir);
164523
+ } catch (error) {
164524
+ const message = error instanceof Error ? error.message : String(error);
164525
+ console.error(`Failed to read memory dir: ${message}`);
164526
+ return IO_EXIT;
164527
+ }
164528
+ const { total, files } = estimate;
164529
+ if (format2 === "json") {
164530
+ printJson(total, files);
164531
+ } else {
164532
+ printText(total, files, top, options.quiet);
164533
+ }
164534
+ return 0;
164535
+ }
164536
+
164537
+ // src/cli/subcommands/memory.ts
164112
164538
  function printUsage5() {
164113
164539
  console.log(`
164114
164540
  Usage:
164115
- letta memfs status [--agent <id>]
164116
- letta memfs diff [--agent <id>]
164117
- letta memfs backup [--agent <id>]
164118
- letta memfs backups [--agent <id>]
164119
- letta memfs restore --from <backup> --force [--agent <id>]
164120
- letta memfs export --agent <id> --out <dir>
164121
- letta memfs pull [--agent <id>]
164541
+ letta memory status [--agent <id>]
164542
+ letta memory diff [--agent <id>]
164543
+ letta memory backup [--agent <id>]
164544
+ letta memory backups [--agent <id>]
164545
+ letta memory restore --from <backup> --force [--agent <id>]
164546
+ letta memory export --agent <id> --out <dir>
164547
+ letta memory pull [--agent <id>]
164548
+ letta memory tokens [--memory-dir <path>] [--agent <id>] [--top <N>]
164549
+ [--format text|json] [--quiet]
164122
164550
 
164123
164551
  Notes:
164124
- - Requires agent id via --agent or LETTA_AGENT_ID.
164125
- - Output is JSON only.
164552
+ - Most actions require agent id via --agent or LETTA_AGENT_ID and output JSON.
164553
+ - \`tokens\` additionally accepts --memory-dir or $MEMORY_DIR; reports the
164554
+ estimated token size of system/. Policy (whether a size is concerning) is
164555
+ up to the caller.
164126
164556
  - Memory is git-backed. Use git commands for commit/push.
164127
164557
 
164128
164558
  Examples:
164129
- LETTA_AGENT_ID=agent-123 letta memfs status
164130
- letta memfs pull --agent agent-123
164131
- letta memfs backup --agent agent-123
164132
- letta memfs export --agent agent-123 --out /tmp/letta-memfs-agent-123
164559
+ LETTA_AGENT_ID=agent-123 letta memory status
164560
+ letta memory pull --agent agent-123
164561
+ letta memory backup --agent agent-123
164562
+ letta memory export --agent agent-123 --out /tmp/letta-memory-agent-123
164563
+ letta memory tokens
164564
+ letta memory tokens --memory-dir ~/.letta/agents/agent-123/memory --format json
164133
164565
  `.trim());
164134
164566
  }
164135
164567
  function getAgentId5(agentFromArgs, agentIdFromArgs) {
164136
164568
  return agentFromArgs || agentIdFromArgs || process.env.LETTA_AGENT_ID || "";
164137
164569
  }
164138
- var MEMFS_OPTIONS = {
164570
+ var MEMORY_OPTIONS = {
164139
164571
  help: { type: "boolean", short: "h" },
164140
164572
  agent: { type: "string" },
164141
164573
  "agent-id": { type: "string" },
164142
164574
  from: { type: "string" },
164143
164575
  force: { type: "boolean" },
164144
- out: { type: "string" }
164576
+ out: { type: "string" },
164577
+ "memory-dir": { type: "string" },
164578
+ top: { type: "string" },
164579
+ format: { type: "string" },
164580
+ quiet: { type: "boolean" }
164145
164581
  };
164146
- function parseMemfsArgs(argv) {
164582
+ function parseMemoryArgs(argv) {
164147
164583
  return parseArgs8({
164148
164584
  args: argv,
164149
- options: MEMFS_OPTIONS,
164585
+ options: MEMORY_OPTIONS,
164150
164586
  strict: true,
164151
164587
  allowPositionals: true
164152
164588
  });
@@ -164198,10 +164634,10 @@ function resolveBackupPath(agentId, from) {
164198
164634
  }
164199
164635
  return join34(getAgentRoot(agentId), from);
164200
164636
  }
164201
- async function runMemfsSubcommand(argv) {
164637
+ async function runMemorySubcommand(argv) {
164202
164638
  let parsed;
164203
164639
  try {
164204
- parsed = parseMemfsArgs(argv);
164640
+ parsed = parseMemoryArgs(argv);
164205
164641
  } catch (error) {
164206
164642
  const message = error instanceof Error ? error.message : String(error);
164207
164643
  console.error(`Error: ${message}`);
@@ -164214,6 +164650,15 @@ async function runMemfsSubcommand(argv) {
164214
164650
  return 0;
164215
164651
  }
164216
164652
  const agentId = getAgentId5(parsed.values.agent, parsed.values["agent-id"]);
164653
+ if (action === "tokens") {
164654
+ return runMemoryTokensAction({
164655
+ memoryDir: parsed.values["memory-dir"],
164656
+ agentMemoryDir: agentId ? getMemoryRoot(agentId) : undefined,
164657
+ top: parsed.values.top,
164658
+ format: parsed.values.format,
164659
+ quiet: Boolean(parsed.values.quiet)
164660
+ });
164661
+ }
164217
164662
  if (!agentId) {
164218
164663
  console.error("Missing agent id. Set LETTA_AGENT_ID or pass --agent/--agent-id.");
164219
164664
  return 1;
@@ -164326,7 +164771,7 @@ async function runMemfsSubcommand(argv) {
164326
164771
  return 1;
164327
164772
  }
164328
164773
  } else {
164329
- mkdirSync21(out, { recursive: true });
164774
+ mkdirSync23(out, { recursive: true });
164330
164775
  }
164331
164776
  cpSync(root, out, { recursive: true });
164332
164777
  console.log(JSON.stringify({ exportedFrom: root, exportedTo: out, agentId }, null, 2));
@@ -164685,8 +165130,9 @@ async function runSubcommand(argv) {
164685
165130
  return null;
164686
165131
  }
164687
165132
  switch (command) {
165133
+ case "memory":
164688
165134
  case "memfs":
164689
- return runMemfsSubcommand(rest);
165135
+ return runMemorySubcommand(rest);
164690
165136
  case "agents":
164691
165137
  return runAgentsSubcommand(rest);
164692
165138
  case "messages":
@@ -166855,7 +167301,7 @@ USAGE
166855
167301
 
166856
167302
  # maintenance
166857
167303
  letta update Manually check for updates and install if available
166858
- letta memfs ... Memory filesystem subcommands (JSON-only)
167304
+ letta memory ... Memory filesystem subcommands
166859
167305
  letta agents ... Agents subcommands (JSON-only)
166860
167306
  letta messages ... Messages subcommands (JSON-only)
166861
167307
  letta blocks ... Blocks subcommands (JSON-only)
@@ -166864,14 +167310,16 @@ USAGE
166864
167310
  OPTIONS
166865
167311
  ${renderCliOptionsHelp()}
166866
167312
 
166867
- SUBCOMMANDS (JSON-only)
166868
- letta memfs status --agent <id>
166869
- letta memfs diff --agent <id>
166870
- letta memfs resolve --agent <id> --resolutions '<JSON>'
166871
- letta memfs backup --agent <id>
166872
- letta memfs backups --agent <id>
166873
- letta memfs restore --agent <id> --from <backup> --force
166874
- letta memfs export --agent <id> --out <dir>
167313
+ SUBCOMMANDS
167314
+ letta memory status --agent <id>
167315
+ letta memory diff --agent <id>
167316
+ letta memory resolve --agent <id> --resolutions '<JSON>'
167317
+ letta memory backup --agent <id>
167318
+ letta memory backups --agent <id>
167319
+ letta memory restore --agent <id> --from <backup> --force
167320
+ letta memory export --agent <id> --out <dir>
167321
+ letta memory pull --agent <id>
167322
+ letta memory tokens [--memory-dir <path>] [--agent <id>] [--format text|json]
166875
167323
  letta agents list [--query <text> | --name <name> | --tags <tags>]
166876
167324
  letta messages search --query <text> [--all-agents]
166877
167325
  letta messages list [--agent <id>]
@@ -168205,4 +168653,4 @@ Error during initialization: ${message}`);
168205
168653
  }
168206
168654
  main();
168207
168655
 
168208
- //# debugId=876580101EC255F264756E2164756E21
168656
+ //# debugId=22CE2EBE31776A7764756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letta-ai/letta-code",
3
- "version": "0.24.2",
3
+ "version": "0.24.3",
4
4
  "description": "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,7 +30,7 @@ This is the recommended flow:
30
30
 
31
31
  1. **Export the source agent's memfs to a temp directory**
32
32
  ```bash
33
- letta memfs export --agent <source-agent-id> --out /tmp/letta-memfs-<source-agent-id>
33
+ letta memory export --agent <source-agent-id> --out /tmp/letta-memory-<source-agent-id>
34
34
  ```
35
35
 
36
36
  2. **Copy the files you want into your own memfs**
@@ -39,13 +39,16 @@ This is the recommended flow:
39
39
 
40
40
  Example:
41
41
  ```bash
42
- cp -r /tmp/letta-memfs-agent-abc123/system/project ~/.letta/agents/$LETTA_AGENT_ID/memory/system/
43
- cp /tmp/letta-memfs-agent-abc123/notes.md ~/.letta/agents/$LETTA_AGENT_ID/memory/
42
+ cp -r /tmp/letta-memory-agent-abc123/system/project ~/.letta/agents/$LETTA_AGENT_ID/memory/system/
43
+ cp /tmp/letta-memory-agent-abc123/notes.md ~/.letta/agents/$LETTA_AGENT_ID/memory/
44
44
  ```
45
45
 
46
- 3. **Sync to API**
46
+ 3. **Commit and push the memory repo**
47
47
  ```bash
48
- letta memfs sync --agent $LETTA_AGENT_ID
48
+ cd ~/.letta/agents/$LETTA_AGENT_ID/memory
49
+ git add system/project notes.md
50
+ git commit -m "Import memory from source agent"
51
+ git push
49
52
  ```
50
53
 
51
54
  This gives you full control over what you bring across and keeps everything consistent with memfs.
@@ -107,15 +110,18 @@ Scenario: You're a new agent and want to inherit memory from an existing agent "
107
110
 
108
111
  2. **Export their memfs:**
109
112
  ```bash
110
- letta memfs export --agent agent-abc123 --out /tmp/letta-memfs-agent-abc123
113
+ letta memory export --agent agent-abc123 --out /tmp/letta-memory-agent-abc123
111
114
  ```
112
115
 
113
116
  3. **Copy the relevant files into your memfs:**
114
117
  ```bash
115
- cp -r /tmp/letta-memfs-agent-abc123/system/project ~/.letta/agents/$LETTA_AGENT_ID/memory/system/
118
+ cp -r /tmp/letta-memory-agent-abc123/system/project ~/.letta/agents/$LETTA_AGENT_ID/memory/system/
116
119
  ```
117
120
 
118
- 4. **Sync:**
121
+ 4. **Commit and push:**
119
122
  ```bash
120
- letta memfs sync --agent $LETTA_AGENT_ID
123
+ cd ~/.letta/agents/$LETTA_AGENT_ID/memory
124
+ git add system/project
125
+ git commit -m "Import project memory"
126
+ git push
121
127
  ```