@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 +758 -794
- package/package.json +2 -2
- package/scripts/latency-benchmark.ts +0 -2
- package/skills/initializing-memory/SKILL.md +48 -54
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.
|
|
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.
|
|
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": "
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
45004
|
+
AGENT_EDITABLE_KEYS="description"
|
|
44983
45005
|
PROTECTED_KEYS="read_only"
|
|
44984
|
-
ALL_KNOWN_KEYS="description limit
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
47667
|
-
|
|
47668
|
-
|
|
47669
|
-
|
|
47670
|
-
|
|
47671
|
-
|
|
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
|
-
|
|
47680
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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: [
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ||
|
|
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("
|
|
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
|
|
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
|
-
|
|
123681
|
-
|
|
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
|
|
123684
|
-
|
|
123685
|
-
|
|
123686
|
-
|
|
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
|
-
|
|
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
|
-
|
|
130083
|
-
|
|
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 --
|
|
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
|
|
139657
|
-
createdAt =
|
|
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
|
|
139766
|
-
if (!
|
|
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
|
|
139789
|
-
if (
|
|
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
|
|
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
|
|
142758
|
-
const localAgentId2 =
|
|
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 = !!(
|
|
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.
|
|
143181
|
-
|
|
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=
|
|
143346
|
+
//# debugId=671451F09469FC0A64756E2164756E21
|