@letta-ai/letta-code 0.19.5 → 0.19.7

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
@@ -344,7 +344,7 @@ var init_values = __esm(() => {
344
344
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
345
345
 
346
346
  // node_modules/@letta-ai/letta-client/version.mjs
347
- var VERSION = "1.7.12";
347
+ var VERSION = "1.8.0";
348
348
 
349
349
  // node_modules/@letta-ai/letta-client/internal/detect-platform.mjs
350
350
  function getDetectedPlatform() {
@@ -3240,7 +3240,7 @@ var package_default;
3240
3240
  var init_package = __esm(() => {
3241
3241
  package_default = {
3242
3242
  name: "@letta-ai/letta-code",
3243
- version: "0.19.5",
3243
+ version: "0.19.7",
3244
3244
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
3245
3245
  type: "module",
3246
3246
  bin: {
@@ -3273,7 +3273,7 @@ var init_package = __esm(() => {
3273
3273
  access: "public"
3274
3274
  },
3275
3275
  dependencies: {
3276
- "@letta-ai/letta-client": "^1.7.12",
3276
+ "@letta-ai/letta-client": "1.8.0",
3277
3277
  glob: "^13.0.0",
3278
3278
  "highlight.js": "^11.11.1",
3279
3279
  "ink-link": "^5.0.0",
@@ -6970,7 +6970,12 @@ var init_models2 = __esm(() => {
6970
6970
  label: "Auto",
6971
6971
  description: "Automatically select the best model",
6972
6972
  isFeatured: true,
6973
- free: true
6973
+ free: true,
6974
+ updateArgs: {
6975
+ context_window: 140000,
6976
+ max_output_tokens: 28000,
6977
+ parallel_tool_calls: true
6978
+ }
6974
6979
  },
6975
6980
  {
6976
6981
  id: "auto-fast",
@@ -6978,7 +6983,12 @@ var init_models2 = __esm(() => {
6978
6983
  label: "Auto Fast",
6979
6984
  description: "Automatically select the best fast model",
6980
6985
  isFeatured: true,
6981
- free: true
6986
+ free: true,
6987
+ updateArgs: {
6988
+ context_window: 140000,
6989
+ max_output_tokens: 28000,
6990
+ parallel_tool_calls: true
6991
+ }
6982
6992
  },
6983
6993
  {
6984
6994
  id: "sonnet",
@@ -8431,12 +8441,24 @@ var init_models2 = __esm(() => {
8431
8441
  parallel_tool_calls: true
8432
8442
  }
8433
8443
  },
8444
+ {
8445
+ id: "minimax-m2.7",
8446
+ handle: "minimax/MiniMax-M2.7",
8447
+ label: "MiniMax 2.7",
8448
+ description: "MiniMax's latest coding model",
8449
+ isFeatured: true,
8450
+ free: true,
8451
+ updateArgs: {
8452
+ context_window: 160000,
8453
+ max_output_tokens: 64000,
8454
+ parallel_tool_calls: true
8455
+ }
8456
+ },
8434
8457
  {
8435
8458
  id: "minimax-m2.5",
8436
8459
  handle: "minimax/MiniMax-M2.5",
8437
8460
  label: "MiniMax 2.5",
8438
- description: "MiniMax's latest coding model",
8439
- isFeatured: true,
8461
+ description: "MiniMax's latest coding model (legacy)",
8440
8462
  free: true,
8441
8463
  updateArgs: {
8442
8464
  context_window: 160000,
@@ -39806,9 +39828,483 @@ var init_build4 = __esm(async () => {
39806
39828
  build_default2 = Spinner;
39807
39829
  });
39808
39830
 
39809
- // src/cli/helpers/planName.ts
39831
+ // src/cli/helpers/fileIndex.ts
39832
+ import { createHash as createHash2 } from "node:crypto";
39833
+ import {
39834
+ existsSync as existsSync9,
39835
+ mkdirSync as mkdirSync7,
39836
+ readdirSync as readdirSync4,
39837
+ readFileSync as readFileSync6,
39838
+ statSync as statSync2,
39839
+ writeFileSync as writeFileSync4
39840
+ } from "node:fs";
39810
39841
  import { homedir as homedir7 } from "node:os";
39811
- import { join as join8 } from "node:path";
39842
+ import { join as join8, normalize as normalize2, relative as relative2, sep as sep2 } from "node:path";
39843
+ function normalizeParent2(relativePath) {
39844
+ if (relativePath.length === 0) {
39845
+ return "";
39846
+ }
39847
+ const lastSepIndex = relativePath.lastIndexOf(sep2);
39848
+ return lastSepIndex === -1 ? "" : relativePath.slice(0, lastSepIndex);
39849
+ }
39850
+ function hashValue2(input) {
39851
+ return createHash2("sha256").update(input).digest("hex");
39852
+ }
39853
+ function lowerBound2(sorted, target) {
39854
+ let low = 0;
39855
+ let high = sorted.length;
39856
+ while (low < high) {
39857
+ const mid = low + high >> 1;
39858
+ if ((sorted[mid] ?? "") < target) {
39859
+ low = mid + 1;
39860
+ } else {
39861
+ high = mid;
39862
+ }
39863
+ }
39864
+ return low;
39865
+ }
39866
+ function findPrefixRange2(sorted, prefix) {
39867
+ const start = lowerBound2(sorted, prefix);
39868
+ let end = start;
39869
+ while (end < sorted.length) {
39870
+ const candidate = sorted[end];
39871
+ if (!candidate?.startsWith(prefix)) {
39872
+ break;
39873
+ }
39874
+ end++;
39875
+ }
39876
+ return [start, end];
39877
+ }
39878
+ function preparePreviousIndexData2(cache5) {
39879
+ const entryPaths = cache5.entries.map((entry) => entry.path);
39880
+ const merkleKeys = Object.keys(cache5.merkle).sort();
39881
+ const stats = { ...cache5.stats };
39882
+ const statsKeys = Object.keys(stats).sort();
39883
+ return {
39884
+ entries: cache5.entries,
39885
+ entryPaths,
39886
+ merkle: cache5.merkle,
39887
+ merkleKeys,
39888
+ stats,
39889
+ statsKeys
39890
+ };
39891
+ }
39892
+ function appendSubtreeEntries2(targetEntries, previous, path3) {
39893
+ if (path3 === "") {
39894
+ for (const e of previous.entries)
39895
+ targetEntries.push(e);
39896
+ return;
39897
+ }
39898
+ const { entryPaths, entries: previousEntries } = previous;
39899
+ const prefix = `${path3}/`;
39900
+ const [start, end] = findPrefixRange2(entryPaths, prefix);
39901
+ for (let i = start;i < end; i++) {
39902
+ const entry = previousEntries[i];
39903
+ if (entry !== undefined)
39904
+ targetEntries.push(entry);
39905
+ }
39906
+ }
39907
+ function copyMerkleSubtree2(previous, path3, target) {
39908
+ if (path3 !== "" && previous.merkle[path3]) {
39909
+ target[path3] = previous.merkle[path3];
39910
+ }
39911
+ const prefix = path3 === "" ? "" : `${path3}/`;
39912
+ const [start, end] = prefix === "" ? [0, previous.merkleKeys.length] : findPrefixRange2(previous.merkleKeys, prefix);
39913
+ for (let i = start;i < end; i++) {
39914
+ const key = previous.merkleKeys[i];
39915
+ if (key === undefined)
39916
+ continue;
39917
+ target[key] = previous.merkle[key] ?? "";
39918
+ }
39919
+ }
39920
+ function copyStatsSubtree2(previous, path3, target) {
39921
+ if (path3 !== "" && previous.stats[path3]) {
39922
+ target[path3] = previous.stats[path3];
39923
+ }
39924
+ const prefix = path3 === "" ? "" : `${path3}/`;
39925
+ const [start, end] = prefix === "" ? [0, previous.statsKeys.length] : findPrefixRange2(previous.statsKeys, prefix);
39926
+ for (let i = start;i < end; i++) {
39927
+ const key = previous.statsKeys[i];
39928
+ if (key === undefined)
39929
+ continue;
39930
+ const val = previous.stats[key];
39931
+ if (val !== undefined)
39932
+ target[key] = val;
39933
+ }
39934
+ }
39935
+ function collectPreviousChildNames2(previous, path3) {
39936
+ const names = new Set;
39937
+ const prefix = path3 === "" ? "" : `${path3}/`;
39938
+ const [start, end] = prefix === "" ? [0, previous.statsKeys.length] : findPrefixRange2(previous.statsKeys, prefix);
39939
+ for (let i = start;i < end; i++) {
39940
+ const key = previous.statsKeys[i];
39941
+ if (!key) {
39942
+ continue;
39943
+ }
39944
+ const remainder = key.slice(prefix.length);
39945
+ const slashIndex = remainder.indexOf("/");
39946
+ const childName = slashIndex === -1 ? remainder : remainder.slice(0, slashIndex);
39947
+ if (childName.length > 0) {
39948
+ names.add(childName);
39949
+ }
39950
+ }
39951
+ return names;
39952
+ }
39953
+ function statsMatch2(prev, current) {
39954
+ if (prev.type === "dir" && !current.isDirectory()) {
39955
+ return false;
39956
+ }
39957
+ if (prev.type === "file" && !current.isFile()) {
39958
+ return false;
39959
+ }
39960
+ if (prev.mtimeMs !== current.mtimeMs || prev.ino !== (current.ino ?? 0)) {
39961
+ return false;
39962
+ }
39963
+ if (prev.type === "file") {
39964
+ return typeof prev.size === "number" ? prev.size === current.size : true;
39965
+ }
39966
+ return true;
39967
+ }
39968
+ function shouldReuseDirectory2(previous, path3, stats, childNames, childStats) {
39969
+ if (!previous) {
39970
+ return false;
39971
+ }
39972
+ const previousStats = previous.stats[path3];
39973
+ if (!previousStats || previousStats.type !== "dir") {
39974
+ return false;
39975
+ }
39976
+ if (previousStats.mtimeMs !== stats.mtimeMs || previousStats.ino !== stats.ino) {
39977
+ return false;
39978
+ }
39979
+ const previousChildNames = collectPreviousChildNames2(previous, path3);
39980
+ const seen = new Set;
39981
+ for (const childName of childNames) {
39982
+ const childPath = path3 === "" ? childName : `${path3}/${childName}`;
39983
+ const prevStats = previous.stats[childPath];
39984
+ const currentStats = childStats.get(childName);
39985
+ if (!prevStats || !currentStats) {
39986
+ return false;
39987
+ }
39988
+ if (!statsMatch2(prevStats, currentStats)) {
39989
+ return false;
39990
+ }
39991
+ seen.add(childName);
39992
+ }
39993
+ if (seen.size !== previousChildNames.size) {
39994
+ return false;
39995
+ }
39996
+ for (const name of previousChildNames) {
39997
+ if (!seen.has(name)) {
39998
+ return false;
39999
+ }
40000
+ }
40001
+ return true;
40002
+ }
40003
+ async function buildDirectory2(dir, relativePath, entries, merkle, statsMap, previous, depth, context2) {
40004
+ let dirStats;
40005
+ try {
40006
+ dirStats = statSync2(dir);
40007
+ } catch {
40008
+ const unreadableHash = hashValue2("__unreadable__");
40009
+ merkle[relativePath] = unreadableHash;
40010
+ return unreadableHash;
40011
+ }
40012
+ const currentStats = {
40013
+ type: "dir",
40014
+ mtimeMs: dirStats.mtimeMs,
40015
+ ino: dirStats.ino ?? 0
40016
+ };
40017
+ let dirEntries;
40018
+ try {
40019
+ dirEntries = readdirSync4(dir);
40020
+ } catch {
40021
+ const unreadableHash = hashValue2("__unreadable__");
40022
+ merkle[relativePath] = unreadableHash;
40023
+ return unreadableHash;
40024
+ }
40025
+ const childNames = [];
40026
+ const childStatsMap = new Map;
40027
+ for (const entry of dirEntries) {
40028
+ const entryRelPath = relativePath === "" ? entry : `${relativePath}/${entry}`;
40029
+ if (shouldExcludeEntry(entry, entryRelPath)) {
40030
+ continue;
40031
+ }
40032
+ try {
40033
+ const childStat = statSync2(join8(dir, entry));
40034
+ childNames.push(entry);
40035
+ childStatsMap.set(entry, childStat);
40036
+ } catch {}
40037
+ }
40038
+ if (previous !== undefined && shouldReuseDirectory2(previous, relativePath, currentStats, childNames, childStatsMap)) {
40039
+ copyStatsSubtree2(previous, relativePath, statsMap);
40040
+ appendSubtreeEntries2(entries, previous, relativePath);
40041
+ copyMerkleSubtree2(previous, relativePath, merkle);
40042
+ return previous.merkle[relativePath] ?? hashValue2("__reused__");
40043
+ }
40044
+ statsMap[relativePath] = currentStats;
40045
+ if (depth >= MAX_INDEX_DEPTH2 || context2.newEntryCount >= MAX_CACHE_ENTRIES2) {
40046
+ context2.truncated = true;
40047
+ const truncatedHash = hashValue2("__truncated__");
40048
+ merkle[relativePath] = truncatedHash;
40049
+ return truncatedHash;
40050
+ }
40051
+ const childHashes = [];
40052
+ for (const entry of childNames) {
40053
+ if (context2.newEntryCount >= MAX_CACHE_ENTRIES2) {
40054
+ context2.truncated = true;
40055
+ break;
40056
+ }
40057
+ if (context2.newEntryCount > 0 && context2.newEntryCount % 500 === 0) {
40058
+ await new Promise((resolve2) => setImmediate(resolve2));
40059
+ }
40060
+ const entryStat = childStatsMap.get(entry);
40061
+ if (!entryStat) {
40062
+ continue;
40063
+ }
40064
+ const fullPath = join8(dir, entry);
40065
+ const entryPath = relative2(process.cwd(), fullPath);
40066
+ if (!entryPath) {
40067
+ continue;
40068
+ }
40069
+ if (entryStat.isDirectory()) {
40070
+ entries.push({
40071
+ path: entryPath,
40072
+ type: "dir",
40073
+ lowerPath: entryPath.toLowerCase(),
40074
+ parent: normalizeParent2(entryPath)
40075
+ });
40076
+ context2.newEntryCount++;
40077
+ const childHash = await buildDirectory2(fullPath, entryPath, entries, merkle, statsMap, previous, depth + 1, context2);
40078
+ childHashes.push(`dir:${entry}:${childHash}`);
40079
+ } else {
40080
+ const fileHash = hashValue2(`${entryPath}:${entryStat.size}:${entryStat.mtimeMs}:${entryStat.ino ?? 0}`);
40081
+ statsMap[entryPath] = {
40082
+ type: "file",
40083
+ mtimeMs: entryStat.mtimeMs,
40084
+ ino: entryStat.ino ?? 0,
40085
+ size: entryStat.size
40086
+ };
40087
+ merkle[entryPath] = fileHash;
40088
+ entries.push({
40089
+ path: entryPath,
40090
+ type: "file",
40091
+ lowerPath: entryPath.toLowerCase(),
40092
+ parent: normalizeParent2(entryPath)
40093
+ });
40094
+ context2.newEntryCount++;
40095
+ childHashes.push(`file:${entry}:${fileHash}`);
40096
+ }
40097
+ }
40098
+ const dirHash = hashValue2(childHashes.sort().join("|"));
40099
+ merkle[relativePath] = dirHash;
40100
+ return dirHash;
40101
+ }
40102
+ async function buildIndex2(previous) {
40103
+ const entries = [];
40104
+ const merkle = {};
40105
+ const statsMap = {};
40106
+ const context2 = { newEntryCount: 0, truncated: false };
40107
+ const rootHash = await buildDirectory2(process.cwd(), "", entries, merkle, statsMap, previous, 0, context2);
40108
+ entries.sort((a, b) => a.path.localeCompare(b.path));
40109
+ const seen = new Set;
40110
+ const deduped = entries.filter((e) => {
40111
+ if (seen.has(e.path))
40112
+ return false;
40113
+ seen.add(e.path);
40114
+ return true;
40115
+ });
40116
+ return {
40117
+ entries: deduped,
40118
+ merkle,
40119
+ stats: statsMap,
40120
+ rootHash,
40121
+ truncated: context2.truncated
40122
+ };
40123
+ }
40124
+ function sanitizeWorkspacePath2(workspacePath) {
40125
+ const normalizedPath = normalize2(workspacePath);
40126
+ const strippedPath = normalizedPath.replace(/^[/\\]+/, "");
40127
+ const sanitized = strippedPath.replace(/[/\\:]/g, "_").replace(/\s+/g, "_");
40128
+ return sanitized.length === 0 ? "workspace" : sanitized;
40129
+ }
40130
+ function getProjectStorageDir2() {
40131
+ const homeDir = homedir7();
40132
+ const sanitizedWorkspace = sanitizeWorkspacePath2(process.cwd());
40133
+ return join8(homeDir, ".letta", "projects", sanitizedWorkspace);
40134
+ }
40135
+ function ensureProjectStorageDir2() {
40136
+ const storageDir = getProjectStorageDir2();
40137
+ if (!existsSync9(storageDir)) {
40138
+ mkdirSync7(storageDir, { recursive: true });
40139
+ }
40140
+ return storageDir;
40141
+ }
40142
+ function getProjectIndexPath2() {
40143
+ return join8(getProjectStorageDir2(), PROJECT_INDEX_FILENAME2);
40144
+ }
40145
+ function loadCachedIndex2() {
40146
+ const indexPath = getProjectIndexPath2();
40147
+ if (!existsSync9(indexPath)) {
40148
+ return null;
40149
+ }
40150
+ try {
40151
+ const content = readFileSync6(indexPath, "utf-8");
40152
+ const parsed = JSON.parse(content);
40153
+ if (parsed?.metadata && typeof parsed.metadata.rootHash === "string" && Array.isArray(parsed.entries) && parsed.merkle && typeof parsed.merkle === "object") {
40154
+ const merkle = {};
40155
+ for (const [key, value] of Object.entries(parsed.merkle)) {
40156
+ if (typeof value === "string") {
40157
+ merkle[key] = value;
40158
+ }
40159
+ }
40160
+ const stats = {};
40161
+ if (parsed.stats && typeof parsed.stats === "object") {
40162
+ for (const [path3, rawStats] of Object.entries(parsed.stats)) {
40163
+ const sv = rawStats;
40164
+ if (sv && typeof sv.mtimeMs === "number" && typeof sv.ino === "number" && (sv.type === "file" || sv.type === "dir")) {
40165
+ stats[path3] = {
40166
+ type: sv.type,
40167
+ mtimeMs: sv.mtimeMs,
40168
+ ino: sv.ino
40169
+ };
40170
+ }
40171
+ }
40172
+ }
40173
+ return {
40174
+ metadata: {
40175
+ rootHash: parsed.metadata.rootHash
40176
+ },
40177
+ entries: parsed.entries,
40178
+ merkle,
40179
+ stats
40180
+ };
40181
+ }
40182
+ } catch {}
40183
+ return null;
40184
+ }
40185
+ function cacheProjectIndex2(result) {
40186
+ try {
40187
+ const storageDir = ensureProjectStorageDir2();
40188
+ const indexPath = join8(storageDir, PROJECT_INDEX_FILENAME2);
40189
+ const payload = {
40190
+ metadata: {
40191
+ rootHash: result.rootHash
40192
+ },
40193
+ entries: result.entries,
40194
+ merkle: result.merkle,
40195
+ stats: result.stats
40196
+ };
40197
+ writeFileSync4(indexPath, JSON.stringify(payload, null, 2), "utf-8");
40198
+ } catch {}
40199
+ }
40200
+ function buildCachedEntries2(entries, stats) {
40201
+ const sorted = [...entries].sort((a, b) => {
40202
+ if (a.type === "dir" && b.type !== "dir")
40203
+ return -1;
40204
+ if (a.type !== "dir" && b.type === "dir")
40205
+ return 1;
40206
+ const aMtime = stats[a.path]?.mtimeMs ?? 0;
40207
+ const bMtime = stats[b.path]?.mtimeMs ?? 0;
40208
+ return bMtime - aMtime;
40209
+ }).slice(0, MAX_CACHE_ENTRIES2);
40210
+ return { entries: sorted, paths: new Set(sorted.map((e) => e.path)) };
40211
+ }
40212
+ function ensureFileIndex2() {
40213
+ if (hasCompletedBuild2)
40214
+ return Promise.resolve();
40215
+ if (!buildPromise2) {
40216
+ let currentPromise;
40217
+ currentPromise = (async () => {
40218
+ let succeeded = false;
40219
+ try {
40220
+ const diskIndex = loadCachedIndex2();
40221
+ const previousData = diskIndex ? preparePreviousIndexData2(diskIndex) : undefined;
40222
+ const buildResult = await buildIndex2(previousData);
40223
+ if (diskIndex && diskIndex.metadata.rootHash === buildResult.rootHash) {
40224
+ ({ entries: cachedEntries2, paths: cachedEntryPaths2 } = buildCachedEntries2(buildResult.entries, buildResult.stats));
40225
+ succeeded = true;
40226
+ return;
40227
+ }
40228
+ if (buildResult.truncated) {
40229
+ debugLog("file-index", `Index truncated: workspace exceeds ${MAX_INDEX_DEPTH2} directory levels deep. ` + `Files beyond that depth will fall back to disk search.`);
40230
+ }
40231
+ cacheProjectIndex2(buildResult);
40232
+ ({ entries: cachedEntries2, paths: cachedEntryPaths2 } = buildCachedEntries2(buildResult.entries, buildResult.stats));
40233
+ succeeded = true;
40234
+ } finally {
40235
+ if (buildPromise2 === currentPromise)
40236
+ buildPromise2 = null;
40237
+ if (succeeded)
40238
+ hasCompletedBuild2 = true;
40239
+ }
40240
+ })();
40241
+ buildPromise2 = currentPromise;
40242
+ }
40243
+ return buildPromise2;
40244
+ }
40245
+ function refreshFileIndex() {
40246
+ hasCompletedBuild2 = false;
40247
+ buildPromise2 = null;
40248
+ return ensureFileIndex2();
40249
+ }
40250
+ function addEntriesToCache(matches) {
40251
+ const available = MAX_CACHE_ENTRIES2 - cachedEntries2.length;
40252
+ if (available <= 0)
40253
+ return;
40254
+ let added = 0;
40255
+ for (const match of matches) {
40256
+ if (added >= available)
40257
+ break;
40258
+ if (!cachedEntryPaths2.has(match.path)) {
40259
+ cachedEntries2.push({
40260
+ path: match.path,
40261
+ type: match.type,
40262
+ lowerPath: match.path.toLowerCase(),
40263
+ parent: normalizeParent2(match.path)
40264
+ });
40265
+ cachedEntryPaths2.add(match.path);
40266
+ added++;
40267
+ }
40268
+ }
40269
+ }
40270
+ function searchFileIndex(options) {
40271
+ const { searchDir, pattern, deep, maxResults } = options;
40272
+ const normalizedDir = searchDir === "." ? "" : searchDir;
40273
+ const dirWithSep = normalizedDir === "" ? "" : `${normalizedDir}${sep2}`;
40274
+ const lowerPattern = pattern.toLowerCase();
40275
+ const results = [];
40276
+ for (const entry of cachedEntries2) {
40277
+ if (normalizedDir) {
40278
+ if (entry.path !== normalizedDir && !entry.path.startsWith(dirWithSep)) {
40279
+ continue;
40280
+ }
40281
+ }
40282
+ if (!deep && entry.parent !== normalizedDir) {
40283
+ continue;
40284
+ }
40285
+ if (lowerPattern && !entry.lowerPath.includes(lowerPattern)) {
40286
+ continue;
40287
+ }
40288
+ results.push({ path: entry.path, type: entry.type });
40289
+ if (results.length >= maxResults) {
40290
+ break;
40291
+ }
40292
+ }
40293
+ return results;
40294
+ }
40295
+ var MAX_INDEX_DEPTH2 = 12, PROJECT_INDEX_FILENAME2 = "file-index.json", MAX_CACHE_ENTRIES2, cachedEntries2, cachedEntryPaths2, buildPromise2 = null, hasCompletedBuild2 = false;
40296
+ var init_fileIndex = __esm(() => {
40297
+ init_debug();
40298
+ init_lettaSettings();
40299
+ init_fileSearchConfig();
40300
+ MAX_CACHE_ENTRIES2 = readIntSetting("MAX_ENTRIES", 50000);
40301
+ cachedEntries2 = [];
40302
+ cachedEntryPaths2 = new Set;
40303
+ });
40304
+
40305
+ // src/cli/helpers/planName.ts
40306
+ import { homedir as homedir8 } from "node:os";
40307
+ import { join as join9 } from "node:path";
39812
40308
  function randomElement(arr) {
39813
40309
  return arr[Math.floor(Math.random() * arr.length)];
39814
40310
  }
@@ -39820,7 +40316,7 @@ function generatePlanName() {
39820
40316
  }
39821
40317
  function generatePlanFilePath() {
39822
40318
  const name = generatePlanName();
39823
- return join8(homedir7(), ".letta", "plans", `${name}.md`);
40319
+ return join9(homedir8(), ".letta", "plans", `${name}.md`);
39824
40320
  }
39825
40321
  var adjectives, nouns;
39826
40322
  var init_planName = __esm(() => {
@@ -40383,480 +40879,6 @@ var init_approval_execution = __esm(async () => {
40383
40879
  ]);
40384
40880
  });
40385
40881
 
40386
- // src/cli/helpers/fileIndex.ts
40387
- import { createHash as createHash2 } from "node:crypto";
40388
- import {
40389
- existsSync as existsSync9,
40390
- mkdirSync as mkdirSync7,
40391
- readdirSync as readdirSync4,
40392
- readFileSync as readFileSync6,
40393
- statSync as statSync2,
40394
- writeFileSync as writeFileSync4
40395
- } from "node:fs";
40396
- import { homedir as homedir8 } from "node:os";
40397
- import { join as join9, normalize as normalize3, relative as relative2, sep as sep2 } from "node:path";
40398
- function normalizeParent2(relativePath) {
40399
- if (relativePath.length === 0) {
40400
- return "";
40401
- }
40402
- const lastSepIndex = relativePath.lastIndexOf(sep2);
40403
- return lastSepIndex === -1 ? "" : relativePath.slice(0, lastSepIndex);
40404
- }
40405
- function hashValue2(input) {
40406
- return createHash2("sha256").update(input).digest("hex");
40407
- }
40408
- function lowerBound2(sorted, target) {
40409
- let low = 0;
40410
- let high = sorted.length;
40411
- while (low < high) {
40412
- const mid = low + high >> 1;
40413
- if ((sorted[mid] ?? "") < target) {
40414
- low = mid + 1;
40415
- } else {
40416
- high = mid;
40417
- }
40418
- }
40419
- return low;
40420
- }
40421
- function findPrefixRange2(sorted, prefix) {
40422
- const start = lowerBound2(sorted, prefix);
40423
- let end = start;
40424
- while (end < sorted.length) {
40425
- const candidate = sorted[end];
40426
- if (!candidate?.startsWith(prefix)) {
40427
- break;
40428
- }
40429
- end++;
40430
- }
40431
- return [start, end];
40432
- }
40433
- function preparePreviousIndexData2(cache5) {
40434
- const entryPaths = cache5.entries.map((entry) => entry.path);
40435
- const merkleKeys = Object.keys(cache5.merkle).sort();
40436
- const stats = { ...cache5.stats };
40437
- const statsKeys = Object.keys(stats).sort();
40438
- return {
40439
- entries: cache5.entries,
40440
- entryPaths,
40441
- merkle: cache5.merkle,
40442
- merkleKeys,
40443
- stats,
40444
- statsKeys
40445
- };
40446
- }
40447
- function appendSubtreeEntries2(targetEntries, previous, path4) {
40448
- if (path4 === "") {
40449
- for (const e of previous.entries)
40450
- targetEntries.push(e);
40451
- return;
40452
- }
40453
- const { entryPaths, entries: previousEntries } = previous;
40454
- const prefix = `${path4}/`;
40455
- const [start, end] = findPrefixRange2(entryPaths, prefix);
40456
- for (let i = start;i < end; i++) {
40457
- const entry = previousEntries[i];
40458
- if (entry !== undefined)
40459
- targetEntries.push(entry);
40460
- }
40461
- }
40462
- function copyMerkleSubtree2(previous, path4, target) {
40463
- if (path4 !== "" && previous.merkle[path4]) {
40464
- target[path4] = previous.merkle[path4];
40465
- }
40466
- const prefix = path4 === "" ? "" : `${path4}/`;
40467
- const [start, end] = prefix === "" ? [0, previous.merkleKeys.length] : findPrefixRange2(previous.merkleKeys, prefix);
40468
- for (let i = start;i < end; i++) {
40469
- const key = previous.merkleKeys[i];
40470
- if (key === undefined)
40471
- continue;
40472
- target[key] = previous.merkle[key] ?? "";
40473
- }
40474
- }
40475
- function copyStatsSubtree2(previous, path4, target) {
40476
- if (path4 !== "" && previous.stats[path4]) {
40477
- target[path4] = previous.stats[path4];
40478
- }
40479
- const prefix = path4 === "" ? "" : `${path4}/`;
40480
- const [start, end] = prefix === "" ? [0, previous.statsKeys.length] : findPrefixRange2(previous.statsKeys, prefix);
40481
- for (let i = start;i < end; i++) {
40482
- const key = previous.statsKeys[i];
40483
- if (key === undefined)
40484
- continue;
40485
- const val = previous.stats[key];
40486
- if (val !== undefined)
40487
- target[key] = val;
40488
- }
40489
- }
40490
- function collectPreviousChildNames2(previous, path4) {
40491
- const names = new Set;
40492
- const prefix = path4 === "" ? "" : `${path4}/`;
40493
- const [start, end] = prefix === "" ? [0, previous.statsKeys.length] : findPrefixRange2(previous.statsKeys, prefix);
40494
- for (let i = start;i < end; i++) {
40495
- const key = previous.statsKeys[i];
40496
- if (!key) {
40497
- continue;
40498
- }
40499
- const remainder = key.slice(prefix.length);
40500
- const slashIndex = remainder.indexOf("/");
40501
- const childName = slashIndex === -1 ? remainder : remainder.slice(0, slashIndex);
40502
- if (childName.length > 0) {
40503
- names.add(childName);
40504
- }
40505
- }
40506
- return names;
40507
- }
40508
- function statsMatch2(prev, current) {
40509
- if (prev.type === "dir" && !current.isDirectory()) {
40510
- return false;
40511
- }
40512
- if (prev.type === "file" && !current.isFile()) {
40513
- return false;
40514
- }
40515
- if (prev.mtimeMs !== current.mtimeMs || prev.ino !== (current.ino ?? 0)) {
40516
- return false;
40517
- }
40518
- if (prev.type === "file") {
40519
- return typeof prev.size === "number" ? prev.size === current.size : true;
40520
- }
40521
- return true;
40522
- }
40523
- function shouldReuseDirectory2(previous, path4, stats, childNames, childStats) {
40524
- if (!previous) {
40525
- return false;
40526
- }
40527
- const previousStats = previous.stats[path4];
40528
- if (!previousStats || previousStats.type !== "dir") {
40529
- return false;
40530
- }
40531
- if (previousStats.mtimeMs !== stats.mtimeMs || previousStats.ino !== stats.ino) {
40532
- return false;
40533
- }
40534
- const previousChildNames = collectPreviousChildNames2(previous, path4);
40535
- const seen = new Set;
40536
- for (const childName of childNames) {
40537
- const childPath = path4 === "" ? childName : `${path4}/${childName}`;
40538
- const prevStats = previous.stats[childPath];
40539
- const currentStats = childStats.get(childName);
40540
- if (!prevStats || !currentStats) {
40541
- return false;
40542
- }
40543
- if (!statsMatch2(prevStats, currentStats)) {
40544
- return false;
40545
- }
40546
- seen.add(childName);
40547
- }
40548
- if (seen.size !== previousChildNames.size) {
40549
- return false;
40550
- }
40551
- for (const name of previousChildNames) {
40552
- if (!seen.has(name)) {
40553
- return false;
40554
- }
40555
- }
40556
- return true;
40557
- }
40558
- async function buildDirectory2(dir, relativePath, entries, merkle, statsMap, previous, depth, context2) {
40559
- let dirStats;
40560
- try {
40561
- dirStats = statSync2(dir);
40562
- } catch {
40563
- const unreadableHash = hashValue2("__unreadable__");
40564
- merkle[relativePath] = unreadableHash;
40565
- return unreadableHash;
40566
- }
40567
- const currentStats = {
40568
- type: "dir",
40569
- mtimeMs: dirStats.mtimeMs,
40570
- ino: dirStats.ino ?? 0
40571
- };
40572
- let dirEntries;
40573
- try {
40574
- dirEntries = readdirSync4(dir);
40575
- } catch {
40576
- const unreadableHash = hashValue2("__unreadable__");
40577
- merkle[relativePath] = unreadableHash;
40578
- return unreadableHash;
40579
- }
40580
- const childNames = [];
40581
- const childStatsMap = new Map;
40582
- for (const entry of dirEntries) {
40583
- const entryRelPath = relativePath === "" ? entry : `${relativePath}/${entry}`;
40584
- if (shouldExcludeEntry(entry, entryRelPath)) {
40585
- continue;
40586
- }
40587
- try {
40588
- const childStat = statSync2(join9(dir, entry));
40589
- childNames.push(entry);
40590
- childStatsMap.set(entry, childStat);
40591
- } catch {}
40592
- }
40593
- if (previous !== undefined && shouldReuseDirectory2(previous, relativePath, currentStats, childNames, childStatsMap)) {
40594
- copyStatsSubtree2(previous, relativePath, statsMap);
40595
- appendSubtreeEntries2(entries, previous, relativePath);
40596
- copyMerkleSubtree2(previous, relativePath, merkle);
40597
- return previous.merkle[relativePath] ?? hashValue2("__reused__");
40598
- }
40599
- statsMap[relativePath] = currentStats;
40600
- if (depth >= MAX_INDEX_DEPTH2 || context2.newEntryCount >= MAX_CACHE_ENTRIES2) {
40601
- context2.truncated = true;
40602
- const truncatedHash = hashValue2("__truncated__");
40603
- merkle[relativePath] = truncatedHash;
40604
- return truncatedHash;
40605
- }
40606
- const childHashes = [];
40607
- for (const entry of childNames) {
40608
- if (context2.newEntryCount >= MAX_CACHE_ENTRIES2) {
40609
- context2.truncated = true;
40610
- break;
40611
- }
40612
- if (context2.newEntryCount > 0 && context2.newEntryCount % 500 === 0) {
40613
- await new Promise((resolve3) => setImmediate(resolve3));
40614
- }
40615
- const entryStat = childStatsMap.get(entry);
40616
- if (!entryStat) {
40617
- continue;
40618
- }
40619
- const fullPath = join9(dir, entry);
40620
- const entryPath = relative2(process.cwd(), fullPath);
40621
- if (!entryPath) {
40622
- continue;
40623
- }
40624
- if (entryStat.isDirectory()) {
40625
- entries.push({
40626
- path: entryPath,
40627
- type: "dir",
40628
- lowerPath: entryPath.toLowerCase(),
40629
- parent: normalizeParent2(entryPath)
40630
- });
40631
- context2.newEntryCount++;
40632
- const childHash = await buildDirectory2(fullPath, entryPath, entries, merkle, statsMap, previous, depth + 1, context2);
40633
- childHashes.push(`dir:${entry}:${childHash}`);
40634
- } else {
40635
- const fileHash = hashValue2(`${entryPath}:${entryStat.size}:${entryStat.mtimeMs}:${entryStat.ino ?? 0}`);
40636
- statsMap[entryPath] = {
40637
- type: "file",
40638
- mtimeMs: entryStat.mtimeMs,
40639
- ino: entryStat.ino ?? 0,
40640
- size: entryStat.size
40641
- };
40642
- merkle[entryPath] = fileHash;
40643
- entries.push({
40644
- path: entryPath,
40645
- type: "file",
40646
- lowerPath: entryPath.toLowerCase(),
40647
- parent: normalizeParent2(entryPath)
40648
- });
40649
- context2.newEntryCount++;
40650
- childHashes.push(`file:${entry}:${fileHash}`);
40651
- }
40652
- }
40653
- const dirHash = hashValue2(childHashes.sort().join("|"));
40654
- merkle[relativePath] = dirHash;
40655
- return dirHash;
40656
- }
40657
- async function buildIndex2(previous) {
40658
- const entries = [];
40659
- const merkle = {};
40660
- const statsMap = {};
40661
- const context2 = { newEntryCount: 0, truncated: false };
40662
- const rootHash = await buildDirectory2(process.cwd(), "", entries, merkle, statsMap, previous, 0, context2);
40663
- entries.sort((a, b) => a.path.localeCompare(b.path));
40664
- const seen = new Set;
40665
- const deduped = entries.filter((e) => {
40666
- if (seen.has(e.path))
40667
- return false;
40668
- seen.add(e.path);
40669
- return true;
40670
- });
40671
- return {
40672
- entries: deduped,
40673
- merkle,
40674
- stats: statsMap,
40675
- rootHash,
40676
- truncated: context2.truncated
40677
- };
40678
- }
40679
- function sanitizeWorkspacePath2(workspacePath) {
40680
- const normalizedPath = normalize3(workspacePath);
40681
- const strippedPath = normalizedPath.replace(/^[/\\]+/, "");
40682
- const sanitized = strippedPath.replace(/[/\\:]/g, "_").replace(/\s+/g, "_");
40683
- return sanitized.length === 0 ? "workspace" : sanitized;
40684
- }
40685
- function getProjectStorageDir2() {
40686
- const homeDir = homedir8();
40687
- const sanitizedWorkspace = sanitizeWorkspacePath2(process.cwd());
40688
- return join9(homeDir, ".letta", "projects", sanitizedWorkspace);
40689
- }
40690
- function ensureProjectStorageDir2() {
40691
- const storageDir = getProjectStorageDir2();
40692
- if (!existsSync9(storageDir)) {
40693
- mkdirSync7(storageDir, { recursive: true });
40694
- }
40695
- return storageDir;
40696
- }
40697
- function getProjectIndexPath2() {
40698
- return join9(getProjectStorageDir2(), PROJECT_INDEX_FILENAME2);
40699
- }
40700
- function loadCachedIndex2() {
40701
- const indexPath = getProjectIndexPath2();
40702
- if (!existsSync9(indexPath)) {
40703
- return null;
40704
- }
40705
- try {
40706
- const content = readFileSync6(indexPath, "utf-8");
40707
- const parsed = JSON.parse(content);
40708
- if (parsed?.metadata && typeof parsed.metadata.rootHash === "string" && Array.isArray(parsed.entries) && parsed.merkle && typeof parsed.merkle === "object") {
40709
- const merkle = {};
40710
- for (const [key, value] of Object.entries(parsed.merkle)) {
40711
- if (typeof value === "string") {
40712
- merkle[key] = value;
40713
- }
40714
- }
40715
- const stats = {};
40716
- if (parsed.stats && typeof parsed.stats === "object") {
40717
- for (const [path4, rawStats] of Object.entries(parsed.stats)) {
40718
- const sv = rawStats;
40719
- if (sv && typeof sv.mtimeMs === "number" && typeof sv.ino === "number" && (sv.type === "file" || sv.type === "dir")) {
40720
- stats[path4] = {
40721
- type: sv.type,
40722
- mtimeMs: sv.mtimeMs,
40723
- ino: sv.ino
40724
- };
40725
- }
40726
- }
40727
- }
40728
- return {
40729
- metadata: {
40730
- rootHash: parsed.metadata.rootHash
40731
- },
40732
- entries: parsed.entries,
40733
- merkle,
40734
- stats
40735
- };
40736
- }
40737
- } catch {}
40738
- return null;
40739
- }
40740
- function cacheProjectIndex2(result) {
40741
- try {
40742
- const storageDir = ensureProjectStorageDir2();
40743
- const indexPath = join9(storageDir, PROJECT_INDEX_FILENAME2);
40744
- const payload = {
40745
- metadata: {
40746
- rootHash: result.rootHash
40747
- },
40748
- entries: result.entries,
40749
- merkle: result.merkle,
40750
- stats: result.stats
40751
- };
40752
- writeFileSync4(indexPath, JSON.stringify(payload, null, 2), "utf-8");
40753
- } catch {}
40754
- }
40755
- function buildCachedEntries2(entries, stats) {
40756
- const sorted = [...entries].sort((a, b) => {
40757
- if (a.type === "dir" && b.type !== "dir")
40758
- return -1;
40759
- if (a.type !== "dir" && b.type === "dir")
40760
- return 1;
40761
- const aMtime = stats[a.path]?.mtimeMs ?? 0;
40762
- const bMtime = stats[b.path]?.mtimeMs ?? 0;
40763
- return bMtime - aMtime;
40764
- }).slice(0, MAX_CACHE_ENTRIES2);
40765
- return { entries: sorted, paths: new Set(sorted.map((e) => e.path)) };
40766
- }
40767
- function ensureFileIndex2() {
40768
- if (hasCompletedBuild2)
40769
- return Promise.resolve();
40770
- if (!buildPromise2) {
40771
- let currentPromise;
40772
- currentPromise = (async () => {
40773
- let succeeded = false;
40774
- try {
40775
- const diskIndex = loadCachedIndex2();
40776
- const previousData = diskIndex ? preparePreviousIndexData2(diskIndex) : undefined;
40777
- const buildResult = await buildIndex2(previousData);
40778
- if (diskIndex && diskIndex.metadata.rootHash === buildResult.rootHash) {
40779
- ({ entries: cachedEntries2, paths: cachedEntryPaths2 } = buildCachedEntries2(buildResult.entries, buildResult.stats));
40780
- succeeded = true;
40781
- return;
40782
- }
40783
- if (buildResult.truncated) {
40784
- debugLog("file-index", `Index truncated: workspace exceeds ${MAX_INDEX_DEPTH2} directory levels deep. ` + `Files beyond that depth will fall back to disk search.`);
40785
- }
40786
- cacheProjectIndex2(buildResult);
40787
- ({ entries: cachedEntries2, paths: cachedEntryPaths2 } = buildCachedEntries2(buildResult.entries, buildResult.stats));
40788
- succeeded = true;
40789
- } finally {
40790
- if (buildPromise2 === currentPromise)
40791
- buildPromise2 = null;
40792
- if (succeeded)
40793
- hasCompletedBuild2 = true;
40794
- }
40795
- })();
40796
- buildPromise2 = currentPromise;
40797
- }
40798
- return buildPromise2;
40799
- }
40800
- function refreshFileIndex() {
40801
- hasCompletedBuild2 = false;
40802
- buildPromise2 = null;
40803
- return ensureFileIndex2();
40804
- }
40805
- function addEntriesToCache(matches) {
40806
- const available = MAX_CACHE_ENTRIES2 - cachedEntries2.length;
40807
- if (available <= 0)
40808
- return;
40809
- let added = 0;
40810
- for (const match of matches) {
40811
- if (added >= available)
40812
- break;
40813
- if (!cachedEntryPaths2.has(match.path)) {
40814
- cachedEntries2.push({
40815
- path: match.path,
40816
- type: match.type,
40817
- lowerPath: match.path.toLowerCase(),
40818
- parent: normalizeParent2(match.path)
40819
- });
40820
- cachedEntryPaths2.add(match.path);
40821
- added++;
40822
- }
40823
- }
40824
- }
40825
- function searchFileIndex(options) {
40826
- const { searchDir, pattern, deep, maxResults } = options;
40827
- const normalizedDir = searchDir === "." ? "" : searchDir;
40828
- const dirWithSep = normalizedDir === "" ? "" : `${normalizedDir}${sep2}`;
40829
- const lowerPattern = pattern.toLowerCase();
40830
- const results = [];
40831
- for (const entry of cachedEntries2) {
40832
- if (normalizedDir) {
40833
- if (entry.path !== normalizedDir && !entry.path.startsWith(dirWithSep)) {
40834
- continue;
40835
- }
40836
- }
40837
- if (!deep && entry.parent !== normalizedDir) {
40838
- continue;
40839
- }
40840
- if (lowerPattern && !entry.lowerPath.includes(lowerPattern)) {
40841
- continue;
40842
- }
40843
- results.push({ path: entry.path, type: entry.type });
40844
- if (results.length >= maxResults) {
40845
- break;
40846
- }
40847
- }
40848
- return results;
40849
- }
40850
- var MAX_INDEX_DEPTH2 = 12, PROJECT_INDEX_FILENAME2 = "file-index.json", MAX_CACHE_ENTRIES2, cachedEntries2, cachedEntryPaths2, buildPromise2 = null, hasCompletedBuild2 = false;
40851
- var init_fileIndex = __esm(() => {
40852
- init_debug();
40853
- init_lettaSettings();
40854
- init_fileSearchConfig();
40855
- MAX_CACHE_ENTRIES2 = readIntSetting("MAX_ENTRIES", 50000);
40856
- cachedEntries2 = [];
40857
- cachedEntryPaths2 = new Set;
40858
- });
40859
-
40860
40882
  // src/permissions/session.ts
40861
40883
  var exports_session = {};
40862
40884
  __export(exports_session, {
@@ -43551,7 +43573,7 @@ If a directory has more than 1,000 entries, only the first 1,000 will be shown.`
43551
43573
  var init_LS = () => {};
43552
43574
 
43553
43575
  // src/tools/descriptions/Memory.md
43554
- var Memory_default = '# Memory\nA convinience tool for memories stored in the memory directory (`$MEMORY_DIR`) that automatically commits and pushes changes. \n\nFiles stored inside of `system/` eventually become part of the agent\'s system prompt, so are always in the context window and do not need to be re-read. Other files only have metadata in the system prompt, so may need to be explicitly read to be updated. \n\nSupported operations on memory files: \n- `str_replace`\n- `insert`\n- `delete`\n- `rename` (path rename or description update mode)\n- `create`\nMore general operations can be performanced through directory modifying the files. \n\nPath formats accepted:\n- relative memory file paths (e.g. `system/contacts.md`, `reference/project/team.md`)\n\nNote: absolute paths and `/memories/...` paths are not supported by this client-side tool.\n\nExamples:\n\n```python\n# Replace text in a memory file \nmemory(command="str_replace", reason="Update theme preference", path="system/human/preferences.md", old_string="theme: dark", new_string="theme: light")\n\n# Insert text at line 5\nmemory(command="insert", reason="Add note about meeting", path="reference/history/meeting-notes.md", insert_line=5, insert_text="New note here")\n\n# Delete a memory file \nmemory(command="delete", reason="Remove stale notes", path="reference/history/old_notes.md")\n\n# Rename a memory file \nmemory(command="rename", reason="Promote temp notes", old_path="reference/history/temp.md", new_path="reference/history/permanent.md")\n\n# Create a block with starting text\nmemory(command="create", reason="Track coding preferences", path="system/human/prefs/coding.md", description="The user\'s coding preferences.", file_text="The user seems to add type hints to all of their Python code.")\n\n# Create an empty block\nmemory(command="create", reason="Create coding preferences block", path="reference/history/coding_preferences.md", description="The user\'s coding preferences.")\n```\n';
43576
+ var Memory_default = '# Memory\nA convinience tool for memories stored in the memory directory (`$MEMORY_DIR`) that automatically commits and pushes changes. \n\nFiles stored inside of `system/` eventually become part of the agent\'s system prompt, so are always in the context window and do not need to be re-read. Other files only have metadata in the system prompt, so may need to be explicitly read to be updated. \n\nSupported operations on memory files: \n- `str_replace`\n- `insert`\n- `delete` (files, or directories recursively)\n- `rename` (path rename only)\n- `update_description`\n- `create`\nMore general operations can be performanced through directory modifying the files. \n\nPath formats accepted:\n- relative memory file paths (e.g. `system/contacts.md`, `reference/project/team.md`)\n- absolute paths only when they are inside `$MEMORY_DIR`\n\nNote: absolute paths outside `$MEMORY_DIR` are rejected.\n\nExamples:\n\n```python\n# Replace text in a memory file \nmemory(command="str_replace", reason="Update theme preference", path="system/human/preferences.md", old_string="theme: dark", new_string="theme: light")\n\n# Insert text at line 5\nmemory(command="insert", reason="Add note about meeting", path="reference/history/meeting-notes.md", insert_line=5, insert_text="New note here")\n\n# Delete a memory file \nmemory(command="delete", reason="Remove stale notes", path="reference/history/old_notes.md")\n\n# Rename a memory file \nmemory(command="rename", reason="Promote temp notes", old_path="reference/history/temp.md", new_path="reference/history/permanent.md")\n\n# Update a block description\nmemory(command="update_description", reason="Clarify coding prefs block", path="system/human/prefs/coding.md", description="Dr. Wooders\' coding preferences.")\n\n# Create a block with starting text\nmemory(command="create", reason="Track coding preferences", path="system/human/prefs/coding.md", description="The user\'s coding preferences.", file_text="The user seems to add type hints to all of their Python code.")\n\n# Create an empty block\nmemory(command="create", reason="Create coding preferences block", path="reference/history/coding_preferences.md", description="The user\'s coding preferences.")\n```\n';
43555
43577
  var init_Memory = () => {};
43556
43578
 
43557
43579
  // src/tools/descriptions/MultiEdit.md
@@ -44979,11 +45001,17 @@ var execFile6, GIT_MEMORY_ENABLED_TAG = "git-memory-enabled", PRE_COMMIT_HOOK_SC
44979
45001
  # Validate frontmatter in staged memory .md files
44980
45002
  # Installed by Letta Code CLI
44981
45003
 
44982
- AGENT_EDITABLE_KEYS="description limit"
45004
+ AGENT_EDITABLE_KEYS="description"
44983
45005
  PROTECTED_KEYS="read_only"
44984
- ALL_KNOWN_KEYS="description limit read_only"
45006
+ ALL_KNOWN_KEYS="description read_only limit"
44985
45007
  errors=""
44986
45008
 
45009
+ # Skills must always be directories: skills/<name>/SKILL.md
45010
+ # Reject legacy flat skill files (both current and legacy repo layouts).
45011
+ for file in $(git diff --cached --name-only --diff-filter=ACMR | grep -E '^(memory/)?skills/[^/]+\\.md$' || true); do
45012
+ errors="$errors\\n $file: invalid skill path (skills must be folders). Use skills/<name>/SKILL.md"
45013
+ done
45014
+
44987
45015
  # Helper: extract a frontmatter value from content
44988
45016
  get_fm_value() {
44989
45017
  local content="$1" key="$2"
@@ -44993,7 +45021,9 @@ get_fm_value() {
44993
45021
  echo "$content" | tail -n +2 | head -n $((closing_line - 1)) | grep "^$key:" | cut -d: -f2- | sed 's/^ *//;s/ *$//'
44994
45022
  }
44995
45023
 
44996
- for file in $(git diff --cached --name-only --diff-filter=ACM | grep '^memory/.*\\.md$'); do
45024
+ # Match .md files under system/ or reference/ (with optional memory/ prefix).
45025
+ # Skip skill SKILL.md files — they use a different frontmatter format.
45026
+ for file in $(git diff --cached --name-only --diff-filter=ACM | grep -E '^(memory/)?(system|reference)/.*\\.md$'); do
44997
45027
  staged=$(git show ":$file")
44998
45028
 
44999
45029
  # Frontmatter is required
@@ -45025,7 +45055,6 @@ for file in $(git diff --cached --name-only --diff-filter=ACM | grep '^memory/.*
45025
45055
 
45026
45056
  # Track required fields
45027
45057
  has_description=false
45028
- has_limit=false
45029
45058
 
45030
45059
  # Validate each line
45031
45060
  while IFS= read -r line; do
@@ -45066,10 +45095,7 @@ for file in $(git diff --cached --name-only --diff-filter=ACM | grep '^memory/.*
45066
45095
  # Validate value types
45067
45096
  case "$key" in
45068
45097
  limit)
45069
- has_limit=true
45070
- if ! echo "$value" | grep -qE '^[0-9]+$' || [ "$value" = "0" ]; then
45071
- errors="$errors\\n $file: 'limit' must be a positive integer, got '$value'"
45072
- fi
45098
+ # Legacy field accepted for backward compatibility.
45073
45099
  ;;
45074
45100
  description)
45075
45101
  has_description=true
@@ -45084,9 +45110,6 @@ for file in $(git diff --cached --name-only --diff-filter=ACM | grep '^memory/.*
45084
45110
  if [ "$has_description" = "false" ]; then
45085
45111
  errors="$errors\\n $file: missing required field 'description'"
45086
45112
  fi
45087
- if [ "$has_limit" = "false" ]; then
45088
- errors="$errors\\n $file: missing required field 'limit'"
45089
- fi
45090
45113
 
45091
45114
  # Check if protected keys were removed (existed in HEAD but not in staged)
45092
45115
  if [ -n "$head_content" ]; then
@@ -47563,7 +47586,15 @@ var init_ListDirectoryGemini2 = __esm(() => {
47563
47586
  // src/tools/impl/Memory.ts
47564
47587
  import { execFile as execFileCb2 } from "node:child_process";
47565
47588
  import { existsSync as existsSync13 } from "node:fs";
47566
- import { mkdir as mkdir2, readFile as readFile3, rename, unlink, writeFile as writeFile2 } from "node:fs/promises";
47589
+ import {
47590
+ mkdir as mkdir2,
47591
+ readFile as readFile3,
47592
+ rename,
47593
+ rm,
47594
+ stat as stat2,
47595
+ unlink,
47596
+ writeFile as writeFile2
47597
+ } from "node:fs/promises";
47567
47598
  import { homedir as homedir15 } from "node:os";
47568
47599
  import { dirname as dirname5, isAbsolute as isAbsolute8, relative as relative5, resolve as resolve12 } from "node:path";
47569
47600
  import { promisify as promisify9 } from "node:util";
@@ -47604,20 +47635,15 @@ async function memory(args) {
47604
47635
  if (command === "create") {
47605
47636
  const pathArg = requireString(args.path, "path", "create");
47606
47637
  const description = requireString(args.description, "description", "create");
47607
- const label = normalizeMemoryLabel(pathArg, "path");
47638
+ const label = normalizeMemoryLabel(memoryDir, pathArg, "path");
47608
47639
  const filePath = resolveMemoryFilePath(memoryDir, label);
47609
47640
  const relPath = toRepoRelative(memoryDir, filePath);
47610
47641
  if (existsSync13(filePath)) {
47611
47642
  throw new Error(`memory create: block already exists at ${pathArg}`);
47612
47643
  }
47613
- const limit2 = args.limit ?? DEFAULT_LIMIT3;
47614
- if (!Number.isInteger(limit2) || limit2 <= 0) {
47615
- throw new Error("memory create: 'limit' must be a positive integer");
47616
- }
47617
47644
  const body = args.file_text ?? "";
47618
47645
  const rendered = renderMemoryFile({
47619
- description,
47620
- limit: limit2
47646
+ description
47621
47647
  }, body);
47622
47648
  await mkdir2(dirname5(filePath), { recursive: true });
47623
47649
  await writeFile2(filePath, rendered, "utf8");
@@ -47626,7 +47652,7 @@ async function memory(args) {
47626
47652
  const pathArg = requireString(args.path, "path", "str_replace");
47627
47653
  const oldString = requireString(args.old_string, "old_string", "str_replace");
47628
47654
  const newString = requireString(args.new_string, "new_string", "str_replace");
47629
- const label = normalizeMemoryLabel(pathArg, "path");
47655
+ const label = normalizeMemoryLabel(memoryDir, pathArg, "path");
47630
47656
  const filePath = resolveMemoryFilePath(memoryDir, label);
47631
47657
  const relPath = toRepoRelative(memoryDir, filePath);
47632
47658
  const file = await loadEditableMemoryFile(filePath, pathArg);
@@ -47644,7 +47670,7 @@ async function memory(args) {
47644
47670
  if (typeof args.insert_line !== "number" || Number.isNaN(args.insert_line)) {
47645
47671
  throw new Error("memory insert: 'insert_line' must be a number");
47646
47672
  }
47647
- const label = normalizeMemoryLabel(pathArg, "path");
47673
+ const label = normalizeMemoryLabel(memoryDir, pathArg, "path");
47648
47674
  const filePath = resolveMemoryFilePath(memoryDir, label);
47649
47675
  const relPath = toRepoRelative(memoryDir, filePath);
47650
47676
  const file = await loadEditableMemoryFile(filePath, pathArg);
@@ -47662,44 +47688,48 @@ async function memory(args) {
47662
47688
  affectedPaths = [relPath];
47663
47689
  } else if (command === "delete") {
47664
47690
  const pathArg = requireString(args.path, "path", "delete");
47665
- const label = normalizeMemoryLabel(pathArg, "path");
47666
- const filePath = resolveMemoryFilePath(memoryDir, label);
47667
- const relPath = toRepoRelative(memoryDir, filePath);
47668
- await loadEditableMemoryFile(filePath, pathArg);
47669
- await unlink(filePath);
47670
- affectedPaths = [relPath];
47671
- } else if (command === "rename") {
47672
- const hasDescriptionUpdate = typeof args.path === "string" && args.path.trim().length > 0 && typeof args.description === "string" && args.description.trim().length > 0 && !args.old_path && !args.new_path;
47673
- if (hasDescriptionUpdate) {
47674
- const pathArg = requireString(args.path, "path", "rename");
47675
- const newDescription = requireString(args.description, "description", "rename description update");
47676
- const label = normalizeMemoryLabel(pathArg, "path");
47691
+ const label = normalizeMemoryLabel(memoryDir, pathArg, "path");
47692
+ const targetPath = resolveMemoryPath(memoryDir, label);
47693
+ if (existsSync13(targetPath) && (await stat2(targetPath)).isDirectory()) {
47694
+ const relPath = toRepoRelative(memoryDir, targetPath);
47695
+ await rm(targetPath, { recursive: true, force: false });
47696
+ affectedPaths = [relPath];
47697
+ } else {
47677
47698
  const filePath = resolveMemoryFilePath(memoryDir, label);
47678
47699
  const relPath = toRepoRelative(memoryDir, filePath);
47679
- const file = await loadEditableMemoryFile(filePath, pathArg);
47680
- const rendered = renderMemoryFile({
47681
- ...file.frontmatter,
47682
- description: newDescription
47683
- }, file.body);
47684
- await writeFile2(filePath, rendered, "utf8");
47700
+ await loadEditableMemoryFile(filePath, pathArg);
47701
+ await unlink(filePath);
47685
47702
  affectedPaths = [relPath];
47686
- } else {
47687
- const oldPathArg = requireString(args.old_path, "old_path", "rename");
47688
- const newPathArg = requireString(args.new_path, "new_path", "rename");
47689
- const oldLabel = normalizeMemoryLabel(oldPathArg, "old_path");
47690
- const newLabel = normalizeMemoryLabel(newPathArg, "new_path");
47691
- const oldFilePath = resolveMemoryFilePath(memoryDir, oldLabel);
47692
- const newFilePath = resolveMemoryFilePath(memoryDir, newLabel);
47693
- const oldRelPath = toRepoRelative(memoryDir, oldFilePath);
47694
- const newRelPath = toRepoRelative(memoryDir, newFilePath);
47695
- if (existsSync13(newFilePath)) {
47696
- throw new Error(`memory rename: destination already exists at ${newPathArg}`);
47697
- }
47698
- await loadEditableMemoryFile(oldFilePath, oldPathArg);
47699
- await mkdir2(dirname5(newFilePath), { recursive: true });
47700
- await rename(oldFilePath, newFilePath);
47701
- affectedPaths = [oldRelPath, newRelPath];
47702
47703
  }
47704
+ } else if (command === "rename") {
47705
+ const oldPathArg = requireString(args.old_path, "old_path", "rename");
47706
+ const newPathArg = requireString(args.new_path, "new_path", "rename");
47707
+ const oldLabel = normalizeMemoryLabel(memoryDir, oldPathArg, "old_path");
47708
+ const newLabel = normalizeMemoryLabel(memoryDir, newPathArg, "new_path");
47709
+ const oldFilePath = resolveMemoryFilePath(memoryDir, oldLabel);
47710
+ const newFilePath = resolveMemoryFilePath(memoryDir, newLabel);
47711
+ const oldRelPath = toRepoRelative(memoryDir, oldFilePath);
47712
+ const newRelPath = toRepoRelative(memoryDir, newFilePath);
47713
+ if (existsSync13(newFilePath)) {
47714
+ throw new Error(`memory rename: destination already exists at ${newPathArg}`);
47715
+ }
47716
+ await loadEditableMemoryFile(oldFilePath, oldPathArg);
47717
+ await mkdir2(dirname5(newFilePath), { recursive: true });
47718
+ await rename(oldFilePath, newFilePath);
47719
+ affectedPaths = [oldRelPath, newRelPath];
47720
+ } else if (command === "update_description") {
47721
+ const pathArg = requireString(args.path, "path", "update_description");
47722
+ const newDescription = requireString(args.description, "description", "update_description");
47723
+ const label = normalizeMemoryLabel(memoryDir, pathArg, "path");
47724
+ const filePath = resolveMemoryFilePath(memoryDir, label);
47725
+ const relPath = toRepoRelative(memoryDir, filePath);
47726
+ const file = await loadEditableMemoryFile(filePath, pathArg);
47727
+ const rendered = renderMemoryFile({
47728
+ ...file.frontmatter,
47729
+ description: newDescription
47730
+ }, file.body);
47731
+ await writeFile2(filePath, rendered, "utf8");
47732
+ affectedPaths = [relPath];
47703
47733
  } else {
47704
47734
  throw new Error(`Unsupported memory command: ${command}`);
47705
47735
  }
@@ -47743,18 +47773,31 @@ function ensureMemoryRepo(memoryDir) {
47743
47773
  throw new Error(`memory: ${memoryDir} is not a git repository. This tool requires a git-backed memory filesystem.`);
47744
47774
  }
47745
47775
  }
47746
- function normalizeMemoryLabel(inputPath, fieldName) {
47776
+ function normalizeMemoryLabel(memoryDir, inputPath, fieldName) {
47747
47777
  const raw = inputPath.trim();
47748
47778
  if (!raw) {
47749
47779
  throw new Error(`memory: '${fieldName}' must be a non-empty string`);
47750
47780
  }
47751
- const normalized = raw.replace(/\\/g, "/");
47752
- if (/^[a-zA-Z]:\//.test(normalized)) {
47753
- throw new Error(`memory: '${fieldName}' must be a memory-relative file path, not an absolute host path`);
47754
- }
47755
- if (normalized.startsWith("~/") || normalized.startsWith("$HOME/")) {
47781
+ if (raw.startsWith("~/") || raw.startsWith("$HOME/")) {
47756
47782
  throw new Error(`memory: '${fieldName}' must be a memory-relative file path, not a home-relative filesystem path`);
47757
47783
  }
47784
+ const isWindowsAbsolute = /^[a-zA-Z]:[\\/]/.test(raw);
47785
+ if (isAbsolute8(raw) || isWindowsAbsolute) {
47786
+ const absolutePath = resolve12(raw);
47787
+ const relToMemory = relative5(memoryDir, absolutePath);
47788
+ if (relToMemory && !relToMemory.startsWith("..") && !isAbsolute8(relToMemory)) {
47789
+ return normalizeRelativeMemoryLabel(relToMemory, fieldName);
47790
+ }
47791
+ throw new Error(memoryPrefixError(memoryDir));
47792
+ }
47793
+ return normalizeRelativeMemoryLabel(raw, fieldName);
47794
+ }
47795
+ function normalizeRelativeMemoryLabel(inputPath, fieldName) {
47796
+ const raw = inputPath.trim();
47797
+ if (!raw) {
47798
+ throw new Error(`memory: '${fieldName}' must be a non-empty string`);
47799
+ }
47800
+ const normalized = raw.replace(/\\/g, "/");
47758
47801
  if (normalized.startsWith("/")) {
47759
47802
  throw new Error(`memory: '${fieldName}' must be a relative path like system/contacts.md`);
47760
47803
  }
@@ -47778,8 +47821,15 @@ function normalizeMemoryLabel(inputPath, fieldName) {
47778
47821
  }
47779
47822
  return segments.join("/");
47780
47823
  }
47824
+ function memoryPrefixError(memoryDir) {
47825
+ return `The memory tool can only be used to modify files in {${memoryDir}} or provided as a relative path`;
47826
+ }
47781
47827
  function resolveMemoryFilePath(memoryDir, label) {
47782
- const absolute = resolve12(memoryDir, `${label}.md`);
47828
+ const absolute = resolveMemoryPath(memoryDir, `${label}.md`);
47829
+ return absolute;
47830
+ }
47831
+ function resolveMemoryPath(memoryDir, path11) {
47832
+ const absolute = resolve12(memoryDir, path11);
47783
47833
  const rel = relative5(memoryDir, absolute);
47784
47834
  if (rel.startsWith("..") || isAbsolute8(rel)) {
47785
47835
  throw new Error("memory: resolved path escapes memory directory");
@@ -47812,7 +47862,6 @@ function parseMemoryFile(content) {
47812
47862
  const frontmatterText = match[1] ?? "";
47813
47863
  const body = match[2] ?? "";
47814
47864
  let description;
47815
- let limit2;
47816
47865
  let readOnly;
47817
47866
  for (const line of frontmatterText.split(/\r?\n/)) {
47818
47867
  const idx = line.indexOf(":");
@@ -47822,11 +47871,6 @@ function parseMemoryFile(content) {
47822
47871
  const value = line.slice(idx + 1).trim();
47823
47872
  if (key === "description") {
47824
47873
  description = value;
47825
- } else if (key === "limit") {
47826
- const parsedLimit = Number.parseInt(value, 10);
47827
- if (!Number.isNaN(parsedLimit)) {
47828
- limit2 = parsedLimit;
47829
- }
47830
47874
  } else if (key === "read_only") {
47831
47875
  readOnly = value;
47832
47876
  }
@@ -47834,13 +47878,9 @@ function parseMemoryFile(content) {
47834
47878
  if (!description || !description.trim()) {
47835
47879
  throw new Error("memory: target file frontmatter is missing 'description'");
47836
47880
  }
47837
- if (!limit2 || !Number.isInteger(limit2) || limit2 <= 0) {
47838
- throw new Error("memory: target file frontmatter is missing a valid positive 'limit'");
47839
- }
47840
47881
  return {
47841
47882
  frontmatter: {
47842
47883
  description,
47843
- limit: limit2,
47844
47884
  ...readOnly !== undefined ? { read_only: readOnly } : {}
47845
47885
  },
47846
47886
  body
@@ -47851,13 +47891,9 @@ function renderMemoryFile(frontmatter, body) {
47851
47891
  if (!description) {
47852
47892
  throw new Error("memory: 'description' must not be empty");
47853
47893
  }
47854
- if (!Number.isInteger(frontmatter.limit) || frontmatter.limit <= 0) {
47855
- throw new Error("memory: 'limit' must be a positive integer");
47856
- }
47857
47894
  const lines = [
47858
47895
  "---",
47859
- `description: ${sanitizeFrontmatterValue(description)}`,
47860
- `limit: ${frontmatter.limit}`
47896
+ `description: ${sanitizeFrontmatterValue(description)}`
47861
47897
  ];
47862
47898
  if (frontmatter.read_only !== undefined) {
47863
47899
  lines.push(`read_only: ${frontmatter.read_only}`);
@@ -47939,7 +47975,7 @@ function requireString(value, field, command) {
47939
47975
  }
47940
47976
  return value;
47941
47977
  }
47942
- var execFile9, DEFAULT_LIMIT3 = 2000;
47978
+ var execFile9;
47943
47979
  var init_Memory2 = __esm(async () => {
47944
47980
  init_context();
47945
47981
  await init_client2();
@@ -61473,7 +61509,7 @@ __export(exports_skills, {
61473
61509
  GLOBAL_SKILLS_DIR: () => GLOBAL_SKILLS_DIR
61474
61510
  });
61475
61511
  import { existsSync as existsSync15 } from "node:fs";
61476
- import { readdir as readdir4, readFile as readFile4, realpath as realpath2, stat as stat2 } from "node:fs/promises";
61512
+ import { readdir as readdir4, readFile as readFile4, realpath as realpath2, stat as stat3 } from "node:fs/promises";
61477
61513
  import { dirname as dirname6, join as join18 } from "node:path";
61478
61514
  import { fileURLToPath as fileURLToPath7 } from "node:url";
61479
61515
  function getBundledSkillsPath() {
@@ -61570,7 +61606,7 @@ async function findSkillFiles(currentPath, rootPath, skills, errors, source, vis
61570
61606
  let isDirectory = entry.isDirectory();
61571
61607
  let isFile = entry.isFile();
61572
61608
  if (entry.isSymbolicLink()) {
61573
- const entryStat = await stat2(fullPath);
61609
+ const entryStat = await stat3(fullPath);
61574
61610
  isDirectory = entryStat.isDirectory();
61575
61611
  isFile = entryStat.isFile();
61576
61612
  }
@@ -63692,7 +63728,14 @@ var init_Memory3 = __esm(() => {
63692
63728
  properties: {
63693
63729
  command: {
63694
63730
  type: "string",
63695
- enum: ["str_replace", "insert", "delete", "rename", "create"],
63731
+ enum: [
63732
+ "str_replace",
63733
+ "insert",
63734
+ "delete",
63735
+ "rename",
63736
+ "update_description",
63737
+ "create"
63738
+ ],
63696
63739
  description: "Memory operation to perform"
63697
63740
  },
63698
63741
  reason: {
@@ -63701,15 +63744,15 @@ var init_Memory3 = __esm(() => {
63701
63744
  },
63702
63745
  path: {
63703
63746
  type: "string",
63704
- description: "Target memory file path relative to memory root (e.g. system/contacts.md)"
63747
+ description: "Target memory path (file or directory). Accepts relative paths like system/contacts.md or absolute paths under MEMORY_DIR"
63705
63748
  },
63706
63749
  old_path: {
63707
63750
  type: "string",
63708
- description: "Source memory file path for rename operations (e.g. system/temp.md)"
63751
+ description: "Source memory file path for rename operations (relative path or absolute path under MEMORY_DIR)"
63709
63752
  },
63710
63753
  new_path: {
63711
63754
  type: "string",
63712
- description: "Destination memory file path for rename operations (e.g. system/permanent.md)"
63755
+ description: "Destination memory file path for rename operations (relative path or absolute path under MEMORY_DIR)"
63713
63756
  },
63714
63757
  old_string: {
63715
63758
  type: "string",
@@ -63729,15 +63772,11 @@ var init_Memory3 = __esm(() => {
63729
63772
  },
63730
63773
  description: {
63731
63774
  type: "string",
63732
- description: "Block description (required for create, or used with rename + path to update description)"
63775
+ description: "Block description (required for create and update_description)"
63733
63776
  },
63734
63777
  file_text: {
63735
63778
  type: "string",
63736
63779
  description: "Initial block content for create"
63737
- },
63738
- limit: {
63739
- type: "number",
63740
- description: "Optional positive integer limit for create"
63741
63780
  }
63742
63781
  },
63743
63782
  required: ["command", "reason"],
@@ -70033,11 +70072,17 @@ function isTerminalKillCommand(value) {
70033
70072
  const c = value;
70034
70073
  return c.type === "terminal_kill" && typeof c.terminal_id === "string";
70035
70074
  }
70075
+ function isSearchFilesCommand(value) {
70076
+ if (!value || typeof value !== "object")
70077
+ return false;
70078
+ const c = value;
70079
+ return c.type === "search_files" && typeof c.query === "string" && typeof c.request_id === "string";
70080
+ }
70036
70081
  function parseServerMessage(data) {
70037
70082
  try {
70038
70083
  const raw = typeof data === "string" ? data : data.toString();
70039
70084
  const parsed = JSON.parse(raw);
70040
- if (isInputCommand(parsed) || isChangeDeviceStateCommand(parsed) || isAbortMessageCommand(parsed) || isSyncCommand(parsed) || isTerminalSpawnCommand(parsed) || isTerminalInputCommand(parsed) || isTerminalResizeCommand(parsed) || isTerminalKillCommand(parsed)) {
70085
+ if (isInputCommand(parsed) || isChangeDeviceStateCommand(parsed) || isAbortMessageCommand(parsed) || isSyncCommand(parsed) || isTerminalSpawnCommand(parsed) || isTerminalInputCommand(parsed) || isTerminalResizeCommand(parsed) || isTerminalKillCommand(parsed) || isSearchFilesCommand(parsed)) {
70041
70086
  return parsed;
70042
70087
  }
70043
70088
  const invalidInput = getInvalidInputReason(parsed);
@@ -72692,7 +72737,8 @@ async function sendMessageStream(conversationId, messages, opts = { streamTokens
72692
72737
  await waitForToolsetReady();
72693
72738
  const { clientTools, contextId } = captureToolExecutionContext(opts.workingDirectory, opts.permissionModeState);
72694
72739
  const { clientSkills, errors: clientSkillDiscoveryErrors } = await buildClientSkillsPayload({
72695
- agentId: opts.agentId
72740
+ agentId: opts.agentId,
72741
+ skillSources: ALL_SKILL_SOURCES
72696
72742
  });
72697
72743
  const resolvedConversationId = conversationId;
72698
72744
  const requestBody = buildConversationMessagesCreateRequestBody(conversationId, messages, opts, clientTools, clientSkills);
@@ -72756,6 +72802,7 @@ var init_message = __esm(async () => {
72756
72802
  init_timing();
72757
72803
  init_approval_result_normalization();
72758
72804
  init_clientSkills();
72805
+ init_skillSources();
72759
72806
  await __promiseAll([
72760
72807
  init_manager3(),
72761
72808
  init_client2()
@@ -76705,6 +76752,8 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
76705
76752
  });
76706
76753
  return;
76707
76754
  }
76755
+ setCurrentAgentId(agentId);
76756
+ setConversationId2(conversationId);
76708
76757
  if (isDebugEnabled()) {
76709
76758
  console.log(`[Listen] Handling message: agentId=${agentId}, requestedConversationId=${requestedConversationId}, conversationId=${conversationId}`);
76710
76759
  }
@@ -77134,6 +77183,7 @@ async function handleIncomingMessage(msg, socket, runtime, onStatusChange, conne
77134
77183
  }
77135
77184
  var init_turn = __esm(async () => {
77136
77185
  init_check_approval();
77186
+ init_context();
77137
77187
  init_turn_recovery_policy();
77138
77188
  init_errorFormatter();
77139
77189
  init_listenContext();
@@ -77159,7 +77209,7 @@ var init_turn = __esm(async () => {
77159
77209
  });
77160
77210
 
77161
77211
  // src/websocket/listener/client.ts
77162
- import { realpath as realpath3, stat as stat3 } from "node:fs/promises";
77212
+ import { realpath as realpath3, stat as stat4 } from "node:fs/promises";
77163
77213
  import path22 from "node:path";
77164
77214
  import WebSocket4 from "ws";
77165
77215
  function handleModeChange(msg, socket, runtime, scope) {
@@ -77323,7 +77373,7 @@ async function handleCwdChange(msg, socket, runtime) {
77323
77373
  }
77324
77374
  const resolvedPath = path22.isAbsolute(requestedPath) ? requestedPath : path22.resolve(currentWorkingDirectory, requestedPath);
77325
77375
  const normalizedPath = await realpath3(resolvedPath);
77326
- const stats = await stat3(normalizedPath);
77376
+ const stats = await stat4(normalizedPath);
77327
77377
  if (!stats.isDirectory()) {
77328
77378
  throw new Error(`Not a directory: ${normalizedPath}`);
77329
77379
  }
@@ -77668,8 +77718,26 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
77668
77718
  scheduleQueuePump(scopedRuntime, socket, opts, processQueuedTurn);
77669
77719
  return;
77670
77720
  }
77721
+ if (isSearchFilesCommand(parsed)) {
77722
+ (async () => {
77723
+ await ensureFileIndex2();
77724
+ const files = searchFileIndex({
77725
+ searchDir: ".",
77726
+ pattern: parsed.query,
77727
+ deep: true,
77728
+ maxResults: parsed.max_results ?? 5
77729
+ });
77730
+ socket.send(JSON.stringify({
77731
+ type: "search_files_response",
77732
+ request_id: parsed.request_id,
77733
+ files,
77734
+ success: true
77735
+ }));
77736
+ })();
77737
+ return;
77738
+ }
77671
77739
  if (parsed.type === "terminal_spawn") {
77672
- handleTerminalSpawn(parsed, socket, runtime.bootWorkingDirectory);
77740
+ handleTerminalSpawn(parsed, socket, parsed.cwd ?? runtime.bootWorkingDirectory);
77673
77741
  return;
77674
77742
  }
77675
77743
  if (parsed.type === "terminal_input") {
@@ -77901,6 +77969,7 @@ function createLegacyTestRuntime() {
77901
77969
  }
77902
77970
  var __listenClientTestUtils;
77903
77971
  var init_client4 = __esm(async () => {
77972
+ init_fileIndex();
77904
77973
  init_planName();
77905
77974
  init_constants();
77906
77975
  init_queueRuntime();
@@ -78015,7 +78084,7 @@ __export(exports_skills2, {
78015
78084
  GLOBAL_SKILLS_DIR: () => GLOBAL_SKILLS_DIR2
78016
78085
  });
78017
78086
  import { existsSync as existsSync20 } from "node:fs";
78018
- import { readdir as readdir6, readFile as readFile6, realpath as realpath4, stat as stat4 } from "node:fs/promises";
78087
+ import { readdir as readdir6, readFile as readFile6, realpath as realpath4, stat as stat5 } from "node:fs/promises";
78019
78088
  import { dirname as dirname10, join as join27 } from "node:path";
78020
78089
  import { fileURLToPath as fileURLToPath8 } from "node:url";
78021
78090
  function getBundledSkillsPath2() {
@@ -78112,7 +78181,7 @@ async function findSkillFiles2(currentPath, rootPath, skills, errors, source, vi
78112
78181
  let isDirectory = entry.isDirectory();
78113
78182
  let isFile = entry.isFile();
78114
78183
  if (entry.isSymbolicLink()) {
78115
- const entryStat = await stat4(fullPath);
78184
+ const entryStat = await stat5(fullPath);
78116
78185
  isDirectory = entryStat.isDirectory();
78117
78186
  isFile = entryStat.isFile();
78118
78187
  }
@@ -78549,7 +78618,7 @@ __export(exports_auto_update, {
78549
78618
  });
78550
78619
  import { execFile as execFile10 } from "node:child_process";
78551
78620
  import { realpathSync as realpathSync2 } from "node:fs";
78552
- import { readdir as readdir7, rm } from "node:fs/promises";
78621
+ import { readdir as readdir7, rm as rm2 } from "node:fs/promises";
78553
78622
  import { join as join28 } from "node:path";
78554
78623
  import { promisify as promisify10 } from "node:util";
78555
78624
  function debugLog3(...args) {
@@ -78717,7 +78786,7 @@ async function cleanupOrphanedDirs(globalPath) {
78717
78786
  if (entry.startsWith(".letta-code-")) {
78718
78787
  const orphanPath = join28(lettaAiDir, entry);
78719
78788
  debugLog3("Cleaning orphaned temp directory:", orphanPath);
78720
- await rm(orphanPath, { recursive: true, force: true });
78789
+ await rm2(orphanPath, { recursive: true, force: true });
78721
78790
  }
78722
78791
  }
78723
78792
  } catch {}
@@ -80637,7 +80706,6 @@ async function handleHeadlessCommand(parsedArgs, model, skillsDirectoryOverride,
80637
80706
  if (values.resume) {
80638
80707
  console.error(`Error: --resume is for interactive mode only (opens conversation selector).
80639
80708
  In headless mode, use:
80640
- --continue Resume the last session (agent + conversation)
80641
80709
  --conversation <id> Resume a specific conversation by ID`);
80642
80710
  process.exit(1);
80643
80711
  }
@@ -80647,7 +80715,6 @@ In headless mode, use:
80647
80715
  let specifiedAgentId = values.agent;
80648
80716
  const specifiedAgentName = values.name;
80649
80717
  let specifiedConversationId = values.conversation;
80650
- const shouldContinue = values.continue;
80651
80718
  const forceNew = values["new-agent"];
80652
80719
  const systemPromptPreset = values.system;
80653
80720
  const systemCustom = values["system-custom"];
@@ -80740,10 +80807,6 @@ In headless mode, use:
80740
80807
  console.error("Error: --from-agent requires --agent <id> or --conversation <id>.");
80741
80808
  process.exit(1);
80742
80809
  }
80743
- if (shouldContinue) {
80744
- console.error("Error: --from-agent cannot be used with --continue");
80745
- process.exit(1);
80746
- }
80747
80810
  if (forceNew) {
80748
80811
  console.error("Error: --from-agent cannot be used with --new-agent");
80749
80812
  process.exit(1);
@@ -80771,20 +80834,12 @@ In headless mode, use:
80771
80834
  {
80772
80835
  when: fromAfFile,
80773
80836
  message: "--conversation cannot be used with --import"
80774
- },
80775
- {
80776
- when: shouldContinue,
80777
- message: "--conversation cannot be used with --continue"
80778
80837
  }
80779
80838
  ]
80780
80839
  });
80781
80840
  validateFlagConflicts2({
80782
80841
  guard: forceNewConversation,
80783
80842
  checks: [
80784
- {
80785
- when: shouldContinue,
80786
- message: "--new cannot be used with --continue"
80787
- },
80788
80843
  {
80789
80844
  when: specifiedConversationId,
80790
80845
  message: "--new cannot be used with --conversation"
@@ -80809,10 +80864,6 @@ In headless mode, use:
80809
80864
  when: specifiedAgentName,
80810
80865
  message: "--import cannot be used with --name"
80811
80866
  },
80812
- {
80813
- when: shouldContinue,
80814
- message: "--import cannot be used with --continue"
80815
- },
80816
80867
  {
80817
80868
  when: forceNew,
80818
80869
  message: "--import cannot be used with --new-agent"
@@ -80987,11 +81038,6 @@ In headless mode, use:
80987
81038
  } catch (_error) {}
80988
81039
  }
80989
81040
  }
80990
- if (!agent && shouldContinue) {
80991
- console.error("No recent session found in .letta/ or ~/.letta.");
80992
- console.error("Run 'letta' to get started.");
80993
- process.exit(1);
80994
- }
80995
81041
  if (!agent) {
80996
81042
  const { ensureDefaultAgents: ensureDefaultAgents2 } = await init_defaults().then(() => exports_defaults);
80997
81043
  const defaultAgent = await ensureDefaultAgents2(client, {
@@ -81006,7 +81052,7 @@ In headless mode, use:
81006
81052
  process.exit(1);
81007
81053
  }
81008
81054
  markMilestone("HEADLESS_AGENT_RESOLVED");
81009
- const isResumingAgent = !!(specifiedAgentId || shouldContinue || !forceNew && !fromAfFile);
81055
+ const isResumingAgent = !!(specifiedAgentId || !forceNew && !fromAfFile);
81010
81056
  if (isResumingAgent) {
81011
81057
  if (model) {
81012
81058
  const modelHandle = resolveModel2(model);
@@ -81132,28 +81178,6 @@ In headless mode, use:
81132
81178
  process.exit(1);
81133
81179
  }
81134
81180
  }
81135
- } else if (shouldContinue) {
81136
- await settingsManager.loadLocalProjectSettings();
81137
- const lastSession = settingsManager.getLocalLastSession(process.cwd()) ?? settingsManager.getGlobalLastSession();
81138
- if (lastSession && lastSession.agentId === agent.id) {
81139
- if (lastSession.conversationId === "default") {
81140
- conversationId = "default";
81141
- } else {
81142
- try {
81143
- debugLog("conversations", `retrieve(${lastSession.conversationId}) [headless lastSession resume]`);
81144
- await client.conversations.retrieve(lastSession.conversationId);
81145
- conversationId = lastSession.conversationId;
81146
- } catch {
81147
- console.error(`Attempting to resume conversation ${lastSession.conversationId}, but conversation was not found.`);
81148
- console.error("Resume the default conversation with 'letta -p ...', view recent conversations with 'letta --resume', or start a new conversation with 'letta -p ... --new'.");
81149
- process.exit(1);
81150
- }
81151
- }
81152
- } else {
81153
- console.error("No previous session found for this agent to resume.");
81154
- console.error("Resume the default conversation with 'letta -p ...', or start a new conversation with 'letta -p ... --new'.");
81155
- process.exit(1);
81156
- }
81157
81181
  } else if (forceNewConversation) {
81158
81182
  const conversation = await client.conversations.create({
81159
81183
  agent_id: agent.id,
@@ -103794,20 +103818,6 @@ function ConversationSelector2({
103794
103818
  children: "⎿ "
103795
103819
  }, undefined, false, undefined, this);
103796
103820
  const indent = " ";
103797
- if (conv.summary) {
103798
- return /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Box_default, {
103799
- flexDirection: "row",
103800
- marginLeft: 2,
103801
- children: [
103802
- bracket,
103803
- /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text2, {
103804
- dimColor: true,
103805
- italic: true,
103806
- children: conv.summary.length > 57 ? `${conv.summary.slice(0, 54)}...` : conv.summary
103807
- }, undefined, false, undefined, this)
103808
- ]
103809
- }, undefined, true, undefined, this);
103810
- }
103811
103821
  if (previewLines.length > 0) {
103812
103822
  return /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(jsx_dev_runtime34.Fragment, {
103813
103823
  children: previewLines.map((line, idx) => /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Box_default, {
@@ -103880,9 +103890,9 @@ function ConversationSelector2({
103880
103890
  /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text2, {
103881
103891
  bold: isSelected,
103882
103892
  color: isSelected ? colors.selector.itemHighlighted : undefined,
103883
- children: isDefault ? "default" : conv.id
103893
+ children: conv.summary ? `${conv.summary.length > 40 ? `${conv.summary.slice(0, 37)}...` : conv.summary} (${conv.id})` : isDefault ? "default" : conv.id
103884
103894
  }, undefined, false, undefined, this),
103885
- isDefault && /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text2, {
103895
+ !conv.summary && isDefault && /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text2, {
103886
103896
  dimColor: true,
103887
103897
  children: " (agent's default conversation)"
103888
103898
  }, undefined, false, undefined, this),
@@ -116536,7 +116546,7 @@ function MessageSearch({
116536
116546
  const [searchInput, setSearchInput] = import_react79.useState(initialQuery ?? "");
116537
116547
  const [activeQuery, setActiveQuery] = import_react79.useState(initialQuery ?? "");
116538
116548
  const [searchMode, setSearchMode] = import_react79.useState("hybrid");
116539
- const [searchRange, setSearchRange] = import_react79.useState("all");
116549
+ const [searchRange, setSearchRange] = import_react79.useState("agent");
116540
116550
  const [results, setResults] = import_react79.useState([]);
116541
116551
  const [loading, setLoading] = import_react79.useState(false);
116542
116552
  const [error, setError] = import_react79.useState(null);
@@ -123674,22 +123684,75 @@ function buildParentMemoryTree(files) {
123674
123684
  }
123675
123685
  return nameA.localeCompare(nameB);
123676
123686
  });
123677
- const lines = ["/memory/"];
123687
+ const limits = getDirectoryLimits();
123688
+ const maxLines = Math.max(2, limits.memfsTreeMaxLines);
123689
+ const maxChars = Math.max(128, limits.memfsTreeMaxChars);
123690
+ const maxChildrenPerDir = Math.max(1, limits.memfsTreeMaxChildrenPerDir);
123691
+ const rootLine = "/memory/";
123692
+ const lines = [rootLine];
123693
+ let totalChars = rootLine.length;
123694
+ const countTreeEntries = (node) => {
123695
+ let total = 0;
123696
+ for (const [, child] of node.children) {
123697
+ total += 1;
123698
+ if (child.children.size > 0) {
123699
+ total += countTreeEntries(child);
123700
+ }
123701
+ }
123702
+ return total;
123703
+ };
123704
+ const canAppendLine = (line) => {
123705
+ const nextLineCount = lines.length + 1;
123706
+ const nextCharCount = totalChars + 1 + line.length;
123707
+ return nextLineCount <= maxLines && nextCharCount <= maxChars;
123708
+ };
123678
123709
  const render2 = (node, prefix) => {
123679
123710
  const entries = sortedEntries(node);
123680
- for (const [index, [name, child]] of entries.entries()) {
123681
- const isLast = index === entries.length - 1;
123711
+ const visibleEntries = entries.slice(0, maxChildrenPerDir);
123712
+ const omittedEntries = Math.max(0, entries.length - visibleEntries.length);
123713
+ const renderItems = visibleEntries.map(([name, child]) => ({
123714
+ kind: "entry",
123715
+ name,
123716
+ child
123717
+ }));
123718
+ if (omittedEntries > 0) {
123719
+ renderItems.push({ kind: "omitted", omittedCount: omittedEntries });
123720
+ }
123721
+ for (const [index, item] of renderItems.entries()) {
123722
+ const isLast = index === renderItems.length - 1;
123682
123723
  const branch = isLast ? "└──" : "├──";
123683
- const suffix = child.isFile ? "" : "/";
123684
- const description = child.description ? ` (${child.description})` : "";
123685
- lines.push(`${prefix}${branch} ${name}${suffix}${description}`);
123686
- if (child.children.size > 0) {
123724
+ const line = item.kind === "entry" ? `${prefix}${branch} ${item.name}${item.child.isFile ? "" : "/"}${item.child.description ? ` (${item.child.description})` : ""}` : `${prefix}${branch} … (${item.omittedCount.toLocaleString()} more entries)`;
123725
+ if (!canAppendLine(line)) {
123726
+ return false;
123727
+ }
123728
+ lines.push(line);
123729
+ totalChars += 1 + line.length;
123730
+ if (item.kind === "entry" && item.child.children.size > 0) {
123687
123731
  const nextPrefix = `${prefix}${isLast ? " " : "│ "}`;
123688
- render2(child, nextPrefix);
123732
+ if (!render2(item.child, nextPrefix)) {
123733
+ return false;
123734
+ }
123689
123735
  }
123690
123736
  }
123737
+ return true;
123691
123738
  };
123692
- render2(root, "");
123739
+ const totalEntries = countTreeEntries(root);
123740
+ const fullyRendered = render2(root, "");
123741
+ if (!fullyRendered) {
123742
+ while (lines.length > 1) {
123743
+ const shownEntries = Math.max(0, lines.length - 1);
123744
+ const omittedEntries = Math.max(1, totalEntries - shownEntries);
123745
+ const notice = `[Tree truncated: showing ${shownEntries.toLocaleString()} of ${totalEntries.toLocaleString()} entries. ${omittedEntries.toLocaleString()} omitted.]`;
123746
+ if (canAppendLine(notice)) {
123747
+ lines.push(notice);
123748
+ break;
123749
+ }
123750
+ const removed = lines.pop();
123751
+ if (removed) {
123752
+ totalChars -= 1 + removed.length;
123753
+ }
123754
+ }
123755
+ }
123693
123756
  return lines.join(`
123694
123757
  `);
123695
123758
  }
@@ -123905,6 +123968,7 @@ async function finalizeAutoReflectionPayload(agentId, conversationId, _payloadPa
123905
123968
  var TRANSCRIPT_ROOT_ENV = "LETTA_TRANSCRIPT_ROOT", DEFAULT_TRANSCRIPT_DIR = "transcripts";
123906
123969
  var init_reflectionTranscript = __esm(async () => {
123907
123970
  init_memoryFilesystem();
123971
+ init_directoryLimits();
123908
123972
  await init_accumulator();
123909
123973
  });
123910
123974
 
@@ -130079,8 +130143,10 @@ Type your task to begin the loop.`, true);
130079
130143
  }
130080
130144
  return { submitted: true };
130081
130145
  }
130082
- if (msg.trim() === "/new") {
130083
- const cmd = commandRunner.start(msg.trim(), "Starting new conversation...");
130146
+ const newMatch = msg.trim().match(/^\/new(?:\s+(.+))?$/);
130147
+ if (newMatch) {
130148
+ const conversationName = newMatch[1]?.trim();
130149
+ const cmd = commandRunner.start(msg.trim(), conversationName ? `Starting new conversation: ${conversationName}...` : "Starting new conversation...");
130084
130150
  resetPendingReasoningCycle();
130085
130151
  setCommandRunning(true);
130086
130152
  await runEndHooks();
@@ -130088,8 +130154,12 @@ Type your task to begin the loop.`, true);
130088
130154
  const client = await getClient2();
130089
130155
  const conversation = await client.conversations.create({
130090
130156
  agent_id: agentId,
130091
- isolated_block_labels: [...ISOLATED_BLOCK_LABELS]
130157
+ isolated_block_labels: [...ISOLATED_BLOCK_LABELS],
130158
+ ...conversationName && { summary: conversationName }
130092
130159
  });
130160
+ if (conversationName) {
130161
+ hasSetConversationSummaryRef.current = true;
130162
+ }
130093
130163
  await maybeCarryOverActiveConversationModel(conversation.id);
130094
130164
  setConversationId3(conversation.id);
130095
130165
  pendingConversationSwitchRef.current = {
@@ -133862,6 +133932,9 @@ If using apply_patch, use this exact relative patch path: ${applyPatchRelativePa
133862
133932
  summary: selectorContext?.summary,
133863
133933
  messageHistory: resumeData.messageHistory
133864
133934
  };
133935
+ if (selectorContext?.summary) {
133936
+ hasSetConversationSummaryRef.current = true;
133937
+ }
133865
133938
  settingsManager.setLocalLastSession({ agentId, conversationId: convId }, process.cwd());
133866
133939
  settingsManager.setGlobalLastSession({
133867
133940
  agentId,
@@ -134864,7 +134937,7 @@ var init_release_notes = __esm(async () => {
134864
134937
  → Read more: https://docs.letta.com/letta-code/changelog#0134`,
134865
134938
  "0.13.0": `\uD83C\uDF81 **Letta Code 0.13.0: Introducing Conversations!**
134866
134939
  → Letta Code now starts a new conversation on each startup (memory is shared across all conversations)
134867
- → Use **/resume** to switch conversations, or run **letta --continue** to continue an existing conversation
134940
+ → Use **/resume** to switch conversations, or run **letta --conv <id>** to continue a specific conversation
134868
134941
  → Read more: https://docs.letta.com/letta-code/changelog#0130`
134869
134942
  };
134870
134943
  });
@@ -136449,13 +136522,6 @@ var CLI_FLAG_CATALOG = {
136449
136522
  mode: "both",
136450
136523
  help: { description: "Show current directory, skills, and pinned agents" }
136451
136524
  },
136452
- continue: {
136453
- parser: { type: "boolean", short: "c" },
136454
- mode: "both",
136455
- help: {
136456
- description: "Resume last session (agent + conversation) directly"
136457
- }
136458
- },
136459
136525
  resume: {
136460
136526
  parser: { type: "boolean", short: "r" },
136461
136527
  mode: "interactive",
@@ -137005,20 +137071,6 @@ function ConversationSelector({
137005
137071
  children: "⎿ "
137006
137072
  }, undefined, false, undefined, this);
137007
137073
  const indent = " ";
137008
- if (conv.summary) {
137009
- return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
137010
- flexDirection: "row",
137011
- marginLeft: 2,
137012
- children: [
137013
- bracket,
137014
- /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text2, {
137015
- dimColor: true,
137016
- italic: true,
137017
- children: conv.summary.length > 57 ? `${conv.summary.slice(0, 54)}...` : conv.summary
137018
- }, undefined, false, undefined, this)
137019
- ]
137020
- }, undefined, true, undefined, this);
137021
- }
137022
137074
  if (previewLines.length > 0) {
137023
137075
  return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(jsx_dev_runtime4.Fragment, {
137024
137076
  children: previewLines.map((line, idx) => /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
@@ -137091,9 +137143,9 @@ function ConversationSelector({
137091
137143
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text2, {
137092
137144
  bold: isSelected,
137093
137145
  color: isSelected ? colors.selector.itemHighlighted : undefined,
137094
- children: isDefault ? "default" : conv.id
137146
+ children: conv.summary ? `${conv.summary.length > 40 ? `${conv.summary.slice(0, 37)}...` : conv.summary} (${conv.id})` : isDefault ? "default" : conv.id
137095
137147
  }, undefined, false, undefined, this),
137096
- isDefault && /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text2, {
137148
+ !conv.summary && isDefault && /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text2, {
137097
137149
  dimColor: true,
137098
137150
  children: " (agent's default conversation)"
137099
137151
  }, undefined, false, undefined, this),
@@ -139653,8 +139705,8 @@ async function listBackups(agentId) {
139653
139705
  const path23 = join23(agentRoot, entry.name);
139654
139706
  let createdAt = null;
139655
139707
  try {
139656
- const stat4 = statSync5(path23);
139657
- createdAt = stat4.mtime.toISOString();
139708
+ const stat5 = statSync5(path23);
139709
+ createdAt = stat5.mtime.toISOString();
139658
139710
  } catch {
139659
139711
  createdAt = null;
139660
139712
  }
@@ -139762,8 +139814,8 @@ async function runMemfsSubcommand(argv) {
139762
139814
  console.error(`Backup not found: ${backupPath}`);
139763
139815
  return 1;
139764
139816
  }
139765
- const stat4 = statSync5(backupPath);
139766
- if (!stat4.isDirectory()) {
139817
+ const stat5 = statSync5(backupPath);
139818
+ if (!stat5.isDirectory()) {
139767
139819
  console.error(`Backup path is not a directory: ${backupPath}`);
139768
139820
  return 1;
139769
139821
  }
@@ -139785,8 +139837,8 @@ async function runMemfsSubcommand(argv) {
139785
139837
  return 1;
139786
139838
  }
139787
139839
  if (existsSync18(out)) {
139788
- const stat4 = statSync5(out);
139789
- if (stat4.isDirectory()) {
139840
+ const stat5 = statSync5(out);
139841
+ if (stat5.isDirectory()) {
139790
139842
  const contents = await readdir5(out);
139791
139843
  if (contents.length > 0) {
139792
139844
  console.error(`Export directory not empty: ${out}`);
@@ -141990,9 +142042,8 @@ Letta Code is a general purpose CLI for interacting with Letta agents
141990
142042
 
141991
142043
  USAGE
141992
142044
  # interactive TUI
141993
- letta Resume default conversation (OG single-threaded experience)
142045
+ letta Resume last conversation for this project
141994
142046
  letta --new Create a new conversation (for concurrent sessions)
141995
- letta --continue Resume last session (agent + conversation) directly
141996
142047
  letta --resume Open agent selector UI to pick agent/conversation
141997
142048
  letta --new-agent Create a new agent directly (skip profile selector)
141998
142049
  letta --agent <id> Open a specific agent by ID
@@ -142261,7 +142312,6 @@ Note: Flags should use double dashes for full names (e.g., --yolo, not -yolo)`);
142261
142312
  console.log(result.message);
142262
142313
  process.exit(result.success ? 0 : 1);
142263
142314
  }
142264
- const shouldContinue = values.continue ?? false;
142265
142315
  const shouldResume = values.resume ?? false;
142266
142316
  let specifiedConversationId = values.conversation ?? null;
142267
142317
  const forceNew = values["new-agent"] ?? false;
@@ -142396,20 +142446,12 @@ Note: Flags should use double dashes for full names (e.g., --yolo, not -yolo)`);
142396
142446
  {
142397
142447
  when: shouldResume,
142398
142448
  message: "--conversation cannot be used with --resume"
142399
- },
142400
- {
142401
- when: shouldContinue,
142402
- message: "--conversation cannot be used with --continue"
142403
142449
  }
142404
142450
  ]
142405
142451
  });
142406
142452
  validateFlagConflicts({
142407
142453
  guard: forceNewConversation,
142408
142454
  checks: [
142409
- {
142410
- when: shouldContinue,
142411
- message: "--new cannot be used with --continue"
142412
- },
142413
142455
  {
142414
142456
  when: specifiedConversationId,
142415
142457
  message: "--new cannot be used with --conversation"
@@ -142600,7 +142642,6 @@ Error: ${message}`);
142600
142642
  const AppModule = await init_App2().then(() => exports_App);
142601
142643
  const App3 = AppModule.default;
142602
142644
  function LoadingApp({
142603
- continueSession,
142604
142645
  forceNew: forceNew2,
142605
142646
  initBlocks: initBlocks2,
142606
142647
  baseTools: baseTools2,
@@ -142754,8 +142795,8 @@ Error: ${message}`);
142754
142795
  }
142755
142796
  }
142756
142797
  if (shouldResume) {
142757
- const localSession = settingsManager2.getLocalLastSession(process.cwd());
142758
- const localAgentId2 = localSession?.agentId ?? localSettings.lastAgent;
142798
+ const localSession2 = settingsManager2.getLocalLastSession(process.cwd());
142799
+ const localAgentId2 = localSession2?.agentId ?? localSettings.lastAgent;
142759
142800
  if (localAgentId2) {
142760
142801
  try {
142761
142802
  const agent = await client.agents.retrieve(localAgentId2);
@@ -142782,41 +142823,6 @@ Error: ${message}`);
142782
142823
  console.error("Run 'letta' to get started.");
142783
142824
  process.exit(1);
142784
142825
  }
142785
- if (continueSession) {
142786
- const localSession = settingsManager2.getLocalLastSession(process.cwd());
142787
- const localAgentId2 = localSession?.agentId ?? localSettings.lastAgent;
142788
- if (localAgentId2) {
142789
- try {
142790
- await client.agents.retrieve(localAgentId2);
142791
- setSelectedGlobalAgentId(localAgentId2);
142792
- if (localSession?.conversationId) {
142793
- setSelectedConversationId(localSession.conversationId);
142794
- }
142795
- setLoadingState("assembling");
142796
- return;
142797
- } catch {
142798
- setFailedAgentMessage(`Unable to locate agent ${localAgentId2} in .letta/, checking global (~/.letta)`);
142799
- }
142800
- } else {
142801
- console.log("No recent agent in .letta/, using global (~/.letta)");
142802
- }
142803
- const globalSession = settingsManager2.getGlobalLastSession();
142804
- const globalAgentId2 = globalSession?.agentId;
142805
- if (globalAgentId2) {
142806
- try {
142807
- await client.agents.retrieve(globalAgentId2);
142808
- setSelectedGlobalAgentId(globalAgentId2);
142809
- if (globalSession?.conversationId) {
142810
- setSelectedConversationId(globalSession.conversationId);
142811
- }
142812
- setLoadingState("assembling");
142813
- return;
142814
- } catch {}
142815
- }
142816
- console.error("No recent session found in .letta/ or ~/.letta.");
142817
- console.error("Run 'letta' to get started.");
142818
- process.exit(1);
142819
- }
142820
142826
  if (forceNew2 || agentIdArg || fromAfFile2) {
142821
142827
  setLoadingState("assembling");
142822
142828
  return;
@@ -142844,9 +142850,10 @@ Error: ${message}`);
142844
142850
  }
142845
142851
  const mergedPinned = settingsManager2.getMergedPinnedAgents(process.cwd());
142846
142852
  const { resolveStartupTarget: resolveStartupTarget2 } = await Promise.resolve().then(() => exports_resolve_startup_agent);
142853
+ const localSession = settingsManager2.getLocalLastSession(process.cwd());
142847
142854
  const target = resolveStartupTarget2({
142848
142855
  localAgentId,
142849
- localConversationId: null,
142856
+ localConversationId: localSession?.conversationId ?? null,
142850
142857
  localAgentExists,
142851
142858
  globalAgentId,
142852
142859
  globalAgentExists,
@@ -142860,6 +142867,9 @@ Error: ${message}`);
142860
142867
  if (cachedAgent && cachedAgent.id === target.agentId) {
142861
142868
  setValidatedAgent(cachedAgent);
142862
142869
  }
142870
+ if (target.conversationId && !forceNewConversation) {
142871
+ setSelectedConversationId(target.conversationId);
142872
+ }
142863
142873
  setLoadingState("assembling");
142864
142874
  return;
142865
142875
  case "select":
@@ -142890,7 +142900,6 @@ Error: ${message}`);
142890
142900
  forceNew2,
142891
142901
  agentIdArg,
142892
142902
  fromAfFile2,
142893
- continueSession,
142894
142903
  shouldResume,
142895
142904
  specifiedConversationId
142896
142905
  ]);
@@ -142930,15 +142939,6 @@ Error: ${message}`);
142930
142939
  return;
142931
142940
  }
142932
142941
  }
142933
- if (!resumingAgentId && continueSession && settings.lastAgent) {
142934
- try {
142935
- await client.agents.retrieve(settings.lastAgent);
142936
- resumingAgentId = settings.lastAgent;
142937
- } catch {
142938
- setLoadingState("selecting_global");
142939
- return;
142940
- }
142941
- }
142942
142942
  }
142943
142943
  setIsResumingSession(!!resumingAgentId);
142944
142944
  const modelForTools = getModelForToolLoading(model, toolset);
@@ -143044,15 +143044,6 @@ Error: ${message}`);
143044
143044
  return;
143045
143045
  }
143046
143046
  }
143047
- if (!agent && continueSession && settings.lastAgent) {
143048
- try {
143049
- agent = await client.agents.retrieve(settings.lastAgent);
143050
- } catch (error) {
143051
- console.error(`Previous agent ${settings.lastAgent} not found (error: ${JSON.stringify(error)})`);
143052
- setLoadingState("selecting_global");
143053
- return;
143054
- }
143055
- }
143056
143047
  if (!agent) {
143057
143048
  console.error("No agent found. Use --new-agent to create a new agent.");
143058
143049
  process.exit(1);
@@ -143073,7 +143064,7 @@ Error: ${message}`);
143073
143064
  }));
143074
143065
  const isResumingProject = !shouldCreateNew && !!resumingAgentId;
143075
143066
  const isReusingExistingAgent = !shouldCreateNew && !fromAfFile2 && agent && agent.id;
143076
- const resuming = !!(continueSession || agentIdArg || isResumingProject || isReusingExistingAgent);
143067
+ const resuming = !!(agentIdArg || isResumingProject || isReusingExistingAgent);
143077
143068
  setIsResumingSession(resuming);
143078
143069
  if (resuming) {
143079
143070
  if (model) {
@@ -143122,7 +143113,6 @@ Error: ${message}`);
143122
143113
  });
143123
143114
  let conversationIdToUse;
143124
143115
  if (isDebugEnabled2()) {
143125
- debugLog2("startup", "shouldContinue=%o", shouldContinue);
143126
143116
  debugLog2("startup", "shouldResume=%o", shouldResume);
143127
143117
  debugLog2("startup", "specifiedConversationId=%s", specifiedConversationId);
143128
143118
  }
@@ -143140,34 +143130,6 @@ Error: ${message}`);
143140
143130
  }
143141
143131
  throw error;
143142
143132
  }
143143
- } else if (shouldContinue) {
143144
- const lastSession = settingsManager2.getLocalLastSession(process.cwd()) ?? settingsManager2.getGlobalLastSession();
143145
- if (isDebugEnabled2()) {
143146
- debugLog2("startup", "lastSession=%s", JSON.stringify(lastSession));
143147
- debugLog2("startup", "agent.id=%s", agent.id);
143148
- }
143149
- let resumedSuccessfully = false;
143150
- if (lastSession && lastSession.agentId === agent.id) {
143151
- try {
143152
- setLoadingState("checking");
143153
- const data = await getResumeData(client, agent, lastSession.conversationId);
143154
- conversationIdToUse = lastSession.conversationId;
143155
- setResumedExistingConversation(true);
143156
- setResumeData(data);
143157
- resumedSuccessfully = true;
143158
- } catch (error) {
143159
- if (error instanceof APIError && (error.status === 404 || error.status === 422)) {
143160
- console.warn(`Previous conversation ${lastSession.conversationId} not found, creating new`);
143161
- } else {
143162
- throw error;
143163
- }
143164
- }
143165
- }
143166
- if (!resumedSuccessfully) {
143167
- console.error(`Attempting to resume conversation ${lastSession?.conversationId ?? "(unknown)"}, but conversation was not found.`);
143168
- console.error("Resume the default conversation with 'letta', view recent conversations with 'letta --resume', or start a new conversation with 'letta --new'.");
143169
- process.exit(1);
143170
- }
143171
143133
  } else if (selectedConversationId) {
143172
143134
  try {
143173
143135
  setLoadingState("checking");
@@ -143177,10 +143139,15 @@ Error: ${message}`);
143177
143139
  setResumeData(data);
143178
143140
  } catch (error) {
143179
143141
  if (error instanceof APIError && (error.status === 404 || error.status === 422)) {
143180
- console.error(`Conversation ${selectedConversationId} not found`);
143181
- process.exit(1);
143142
+ console.warn(`Previous conversation ${selectedConversationId} not found, falling back to default`);
143143
+ conversationIdToUse = "default";
143144
+ setLoadingState("checking");
143145
+ const data = await getResumeData(client, agent, "default");
143146
+ setResumeData(data);
143147
+ setResumedExistingConversation(data.messageHistory.length > 0);
143148
+ } else {
143149
+ throw error;
143182
143150
  }
143183
- throw error;
143184
143151
  }
143185
143152
  } else if (forceNewConversation) {
143186
143153
  const conversation = await client.conversations.create({
@@ -143251,7 +143218,6 @@ Error during initialization: ${message}`);
143251
143218
  process.exit(1);
143252
143219
  });
143253
143220
  }, [
143254
- continueSession,
143255
143221
  forceNew2,
143256
143222
  userRequestedNewAgent,
143257
143223
  agentIdArg,
@@ -143261,7 +143227,6 @@ Error during initialization: ${message}`);
143261
143227
  loadingState,
143262
143228
  selectedGlobalAgentId,
143263
143229
  validatedAgent,
143264
- shouldContinue,
143265
143230
  resumeAgentId,
143266
143231
  selectedConversationId
143267
143232
  ]);
@@ -143362,7 +143327,6 @@ Error during initialization: ${message}`);
143362
143327
  }
143363
143328
  markMilestone2("REACT_RENDER_START");
143364
143329
  render2(React14.createElement(LoadingApp, {
143365
- continueSession: shouldContinue,
143366
143330
  forceNew,
143367
143331
  initBlocks,
143368
143332
  baseTools,
@@ -143379,4 +143343,4 @@ Error during initialization: ${message}`);
143379
143343
  }
143380
143344
  main();
143381
143345
 
143382
- //# debugId=06EC6034A250F60F64756E2164756E21
143346
+ //# debugId=671451F09469FC0A64756E2164756E21