@letta-ai/letta-code 0.14.2 → 0.14.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/letta.js +210 -77
- package/package.json +1 -1
- package/skills/syncing-memory-filesystem/SKILL.md +2 -2
package/letta.js
CHANGED
|
@@ -3108,7 +3108,7 @@ var package_default;
|
|
|
3108
3108
|
var init_package = __esm(() => {
|
|
3109
3109
|
package_default = {
|
|
3110
3110
|
name: "@letta-ai/letta-code",
|
|
3111
|
-
version: "0.14.
|
|
3111
|
+
version: "0.14.3",
|
|
3112
3112
|
description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
|
|
3113
3113
|
type: "module",
|
|
3114
3114
|
bin: {
|
|
@@ -5671,10 +5671,10 @@ Your memory blocks are synchronized with a filesystem tree at \`~/.letta/agents/
|
|
|
5671
5671
|
### Sync Behavior
|
|
5672
5672
|
- **Startup**: Automatic sync when the CLI starts
|
|
5673
5673
|
- **After memory edits**: Automatic sync after using memory tools
|
|
5674
|
-
- **Manual**: Run \`/memfs
|
|
5674
|
+
- **Manual**: Run \`/memfs sync\` to sync on demand
|
|
5675
5675
|
- **Conflict detection**: After each turn, the system checks for conflicts (both file and block changed since last sync)
|
|
5676
5676
|
- **Agent-driven resolution**: If conflicts are detected, you'll receive a system reminder with the conflicting labels and instructions to resolve them using the \`syncing-memory-filesystem\` skill scripts
|
|
5677
|
-
- **User fallback**: The user can also run \`/memfs
|
|
5677
|
+
- **User fallback**: The user can also run \`/memfs sync\` to resolve conflicts manually via an interactive prompt
|
|
5678
5678
|
|
|
5679
5679
|
### How It Works
|
|
5680
5680
|
1. Each \`.md\` file path maps to a block label (e.g., \`system/persona/git_safety.md\` → label \`persona/git_safety\`)
|
|
@@ -61137,6 +61137,10 @@ function ensureMemoryFilesystemDirs(agentId, homeDir = homedir10()) {
|
|
|
61137
61137
|
function hashContent(content) {
|
|
61138
61138
|
return createHash3("sha256").update(content).digest("hex");
|
|
61139
61139
|
}
|
|
61140
|
+
function hashFileBody(content) {
|
|
61141
|
+
const { body } = parseMdxFrontmatter(content);
|
|
61142
|
+
return hashContent(body);
|
|
61143
|
+
}
|
|
61140
61144
|
function loadSyncState(agentId, homeDir = homedir10()) {
|
|
61141
61145
|
const statePath = getMemoryStatePath(agentId, homeDir);
|
|
61142
61146
|
const emptyState = {
|
|
@@ -61246,6 +61250,49 @@ async function writeMemoryFile(dir, label, content) {
|
|
|
61246
61250
|
await ensureFilePath(filePath);
|
|
61247
61251
|
await writeFile5(filePath, content, "utf-8");
|
|
61248
61252
|
}
|
|
61253
|
+
function renderBlockToFileContent(block) {
|
|
61254
|
+
const lines = ["---"];
|
|
61255
|
+
if (block.description) {
|
|
61256
|
+
const desc = block.description.includes(":") || block.description.includes(`
|
|
61257
|
+
`) ? `"${block.description.replace(/"/g, "\\\"")}"` : block.description;
|
|
61258
|
+
lines.push(`description: ${desc}`);
|
|
61259
|
+
}
|
|
61260
|
+
if (block.limit) {
|
|
61261
|
+
lines.push(`limit: ${block.limit}`);
|
|
61262
|
+
}
|
|
61263
|
+
if (block.read_only === true) {
|
|
61264
|
+
lines.push("read_only: true");
|
|
61265
|
+
}
|
|
61266
|
+
lines.push("---");
|
|
61267
|
+
lines.push("");
|
|
61268
|
+
lines.push(block.value || "");
|
|
61269
|
+
return lines.join(`
|
|
61270
|
+
`);
|
|
61271
|
+
}
|
|
61272
|
+
function parseBlockUpdateFromFileContent(fileContent, defaultLabel) {
|
|
61273
|
+
const { frontmatter, body } = parseMdxFrontmatter(fileContent);
|
|
61274
|
+
const label = frontmatter.label || defaultLabel;
|
|
61275
|
+
const hasDescription = Object.hasOwn(frontmatter, "description");
|
|
61276
|
+
const hasLimit = Object.hasOwn(frontmatter, "limit");
|
|
61277
|
+
const hasReadOnly = Object.hasOwn(frontmatter, "read_only");
|
|
61278
|
+
let limit2;
|
|
61279
|
+
if (hasLimit && frontmatter.limit) {
|
|
61280
|
+
const parsed = Number.parseInt(frontmatter.limit, 10);
|
|
61281
|
+
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
61282
|
+
limit2 = parsed;
|
|
61283
|
+
}
|
|
61284
|
+
}
|
|
61285
|
+
return {
|
|
61286
|
+
label,
|
|
61287
|
+
value: body,
|
|
61288
|
+
...hasDescription && { description: frontmatter.description },
|
|
61289
|
+
...hasLimit && limit2 !== undefined && { limit: limit2 },
|
|
61290
|
+
...hasReadOnly && { read_only: frontmatter.read_only === "true" },
|
|
61291
|
+
hasDescription,
|
|
61292
|
+
hasLimit,
|
|
61293
|
+
hasReadOnly
|
|
61294
|
+
};
|
|
61295
|
+
}
|
|
61249
61296
|
async function deleteMemoryFile(dir, label) {
|
|
61250
61297
|
const filePath = join19(dir, `${label}.md`);
|
|
61251
61298
|
if (existsSync10(filePath)) {
|
|
@@ -61388,7 +61435,7 @@ async function syncMemoryFilesystem(agentId, options = {}) {
|
|
|
61388
61435
|
const detachedBlockMap = new Map;
|
|
61389
61436
|
for (const block of detachedBlocks) {
|
|
61390
61437
|
if (block.label && block.id) {
|
|
61391
|
-
if (
|
|
61438
|
+
if (MEMFS_MANAGED_LABELS.has(block.label)) {
|
|
61392
61439
|
continue;
|
|
61393
61440
|
}
|
|
61394
61441
|
if (systemBlockMap.has(block.label)) {
|
|
@@ -61408,7 +61455,7 @@ async function syncMemoryFilesystem(agentId, options = {}) {
|
|
|
61408
61455
|
const allBlocksMap = new Map;
|
|
61409
61456
|
const allFilesMap = new Map;
|
|
61410
61457
|
for (const label of Array.from(allLabels).sort()) {
|
|
61411
|
-
if (
|
|
61458
|
+
if (MEMFS_MANAGED_LABELS.has(label)) {
|
|
61412
61459
|
continue;
|
|
61413
61460
|
}
|
|
61414
61461
|
const systemFile = systemFiles.get(label);
|
|
@@ -61421,6 +61468,7 @@ async function syncMemoryFilesystem(agentId, options = {}) {
|
|
|
61421
61468
|
const isAttached = !!attachedBlock;
|
|
61422
61469
|
const fileDir = fileInSystem ? systemDir : detachedDir;
|
|
61423
61470
|
const fileHash = fileEntry ? hashContent(fileEntry.content) : null;
|
|
61471
|
+
const fileBodyHash = fileEntry ? hashFileBody(fileEntry.content) : null;
|
|
61424
61472
|
const blockHash = blockEntry ? hashContent(blockEntry.value || "") : null;
|
|
61425
61473
|
const lastFileHash = lastState.fileHashes[label] || null;
|
|
61426
61474
|
const lastBlockHash = lastState.blockHashes[label] || null;
|
|
@@ -61440,6 +61488,12 @@ async function syncMemoryFilesystem(agentId, options = {}) {
|
|
|
61440
61488
|
allFilesMap.delete(label);
|
|
61441
61489
|
continue;
|
|
61442
61490
|
}
|
|
61491
|
+
if (READ_ONLY_BLOCK_LABELS.includes(label)) {
|
|
61492
|
+
await deleteMemoryFile(fileDir, label);
|
|
61493
|
+
deletedFiles.push(label);
|
|
61494
|
+
allFilesMap.delete(label);
|
|
61495
|
+
continue;
|
|
61496
|
+
}
|
|
61443
61497
|
const blockData = parseBlockFromFileContent(fileEntry.content, label);
|
|
61444
61498
|
const createdBlock = await client.blocks.create({
|
|
61445
61499
|
...blockData,
|
|
@@ -61460,6 +61514,14 @@ async function syncMemoryFilesystem(agentId, options = {}) {
|
|
|
61460
61514
|
continue;
|
|
61461
61515
|
}
|
|
61462
61516
|
if (!fileEntry && blockEntry) {
|
|
61517
|
+
if (blockEntry.read_only) {
|
|
61518
|
+
const targetDir2 = isAttached ? systemDir : detachedDir;
|
|
61519
|
+
const fileContent2 = renderBlockToFileContent(blockEntry);
|
|
61520
|
+
await writeMemoryFile(targetDir2, label, fileContent2);
|
|
61521
|
+
createdFiles.push(label);
|
|
61522
|
+
allFilesMap.set(label, { content: fileContent2 });
|
|
61523
|
+
continue;
|
|
61524
|
+
}
|
|
61463
61525
|
if (lastFileHash && !blockChanged) {
|
|
61464
61526
|
if (blockEntry.id) {
|
|
61465
61527
|
try {
|
|
@@ -61482,16 +61544,17 @@ async function syncMemoryFilesystem(agentId, options = {}) {
|
|
|
61482
61544
|
continue;
|
|
61483
61545
|
}
|
|
61484
61546
|
const targetDir = isAttached ? systemDir : detachedDir;
|
|
61485
|
-
|
|
61547
|
+
const fileContent = renderBlockToFileContent(blockEntry);
|
|
61548
|
+
await writeMemoryFile(targetDir, label, fileContent);
|
|
61486
61549
|
createdFiles.push(label);
|
|
61487
|
-
allFilesMap.set(label, { content:
|
|
61550
|
+
allFilesMap.set(label, { content: fileContent });
|
|
61488
61551
|
continue;
|
|
61489
61552
|
}
|
|
61490
61553
|
if (!fileEntry || !blockEntry) {
|
|
61491
61554
|
continue;
|
|
61492
61555
|
}
|
|
61493
61556
|
const locationMismatch = fileInSystem && !isAttached || !fileInSystem && isAttached;
|
|
61494
|
-
if (
|
|
61557
|
+
if (fileBodyHash === blockHash) {
|
|
61495
61558
|
if (locationMismatch && blockEntry.id) {
|
|
61496
61559
|
if (fileInSystem && !isAttached) {
|
|
61497
61560
|
await client.agents.blocks.attach(blockEntry.id, {
|
|
@@ -61506,9 +61569,10 @@ async function syncMemoryFilesystem(agentId, options = {}) {
|
|
|
61506
61569
|
continue;
|
|
61507
61570
|
}
|
|
61508
61571
|
if (fileChanged && blockChanged && resolution && resolution.resolution === "block") {
|
|
61509
|
-
|
|
61572
|
+
const fileContent = renderBlockToFileContent(blockEntry);
|
|
61573
|
+
await writeMemoryFile(fileDir, label, fileContent);
|
|
61510
61574
|
updatedFiles.push(label);
|
|
61511
|
-
allFilesMap.set(label, { content:
|
|
61575
|
+
allFilesMap.set(label, { content: fileContent });
|
|
61512
61576
|
if (locationMismatch && blockEntry.id) {
|
|
61513
61577
|
if (fileInSystem && !isAttached) {
|
|
61514
61578
|
await client.agents.blocks.attach(blockEntry.id, {
|
|
@@ -61523,9 +61587,10 @@ async function syncMemoryFilesystem(agentId, options = {}) {
|
|
|
61523
61587
|
continue;
|
|
61524
61588
|
}
|
|
61525
61589
|
if (resolution?.resolution === "block") {
|
|
61526
|
-
|
|
61590
|
+
const fileContent = renderBlockToFileContent(blockEntry);
|
|
61591
|
+
await writeMemoryFile(fileDir, label, fileContent);
|
|
61527
61592
|
updatedFiles.push(label);
|
|
61528
|
-
allFilesMap.set(label, { content:
|
|
61593
|
+
allFilesMap.set(label, { content: fileContent });
|
|
61529
61594
|
if (locationMismatch && blockEntry.id) {
|
|
61530
61595
|
if (fileInSystem && !isAttached) {
|
|
61531
61596
|
await client.agents.blocks.attach(blockEntry.id, {
|
|
@@ -61540,14 +61605,31 @@ async function syncMemoryFilesystem(agentId, options = {}) {
|
|
|
61540
61605
|
continue;
|
|
61541
61606
|
}
|
|
61542
61607
|
if (fileChanged) {
|
|
61608
|
+
if (blockEntry.read_only) {
|
|
61609
|
+
const fileContent = renderBlockToFileContent(blockEntry);
|
|
61610
|
+
await writeMemoryFile(fileDir, label, fileContent);
|
|
61611
|
+
updatedFiles.push(label);
|
|
61612
|
+
allFilesMap.set(label, { content: fileContent });
|
|
61613
|
+
continue;
|
|
61614
|
+
}
|
|
61543
61615
|
if (blockEntry.id) {
|
|
61544
61616
|
try {
|
|
61545
|
-
const
|
|
61546
|
-
const updatePayload =
|
|
61617
|
+
const parsed = parseBlockUpdateFromFileContent(fileEntry.content, label);
|
|
61618
|
+
const updatePayload = {
|
|
61619
|
+
value: parsed.value
|
|
61620
|
+
};
|
|
61621
|
+
if (parsed.hasDescription)
|
|
61622
|
+
updatePayload.description = parsed.description;
|
|
61623
|
+
if (parsed.hasLimit)
|
|
61624
|
+
updatePayload.limit = parsed.limit;
|
|
61625
|
+
if (parsed.hasReadOnly)
|
|
61626
|
+
updatePayload.read_only = parsed.read_only;
|
|
61627
|
+
if (!isAttached)
|
|
61628
|
+
updatePayload.label = label;
|
|
61547
61629
|
await client.blocks.update(blockEntry.id, updatePayload);
|
|
61548
61630
|
updatedBlocks.push(label);
|
|
61549
61631
|
allBlocksMap.set(label, {
|
|
61550
|
-
value:
|
|
61632
|
+
value: parsed.value,
|
|
61551
61633
|
id: blockEntry.id
|
|
61552
61634
|
});
|
|
61553
61635
|
if (locationMismatch) {
|
|
@@ -61588,9 +61670,10 @@ async function syncMemoryFilesystem(agentId, options = {}) {
|
|
|
61588
61670
|
continue;
|
|
61589
61671
|
}
|
|
61590
61672
|
if (blockChanged) {
|
|
61591
|
-
|
|
61673
|
+
const fileContent = renderBlockToFileContent(blockEntry);
|
|
61674
|
+
await writeMemoryFile(fileDir, label, fileContent);
|
|
61592
61675
|
updatedFiles.push(label);
|
|
61593
|
-
allFilesMap.set(label, { content:
|
|
61676
|
+
allFilesMap.set(label, { content: fileContent });
|
|
61594
61677
|
if (locationMismatch && blockEntry.id) {
|
|
61595
61678
|
if (fileInSystem && !isAttached) {
|
|
61596
61679
|
await client.agents.blocks.attach(blockEntry.id, {
|
|
@@ -61633,8 +61716,14 @@ ${tree}`;
|
|
|
61633
61716
|
const memfsBlock = blocks.find((block) => block.label === MEMORY_FILESYSTEM_BLOCK_LABEL);
|
|
61634
61717
|
if (memfsBlock?.id) {
|
|
61635
61718
|
await client.blocks.update(memfsBlock.id, { value: content });
|
|
61719
|
+
const fileContent = renderBlockToFileContent({
|
|
61720
|
+
value: content,
|
|
61721
|
+
description: memfsBlock.description,
|
|
61722
|
+
limit: memfsBlock.limit,
|
|
61723
|
+
read_only: memfsBlock.read_only
|
|
61724
|
+
});
|
|
61725
|
+
await writeMemoryFile(systemDir, MEMORY_FILESYSTEM_BLOCK_LABEL, fileContent);
|
|
61636
61726
|
}
|
|
61637
|
-
await writeMemoryFile(systemDir, MEMORY_FILESYSTEM_BLOCK_LABEL, content);
|
|
61638
61727
|
}
|
|
61639
61728
|
async function ensureMemoryFilesystemBlock(agentId) {
|
|
61640
61729
|
const client = await getClient2();
|
|
@@ -61702,7 +61791,7 @@ async function checkMemoryFilesystemStatus(agentId, options) {
|
|
|
61702
61791
|
const detachedBlockMap = new Map;
|
|
61703
61792
|
for (const block of detachedBlocks) {
|
|
61704
61793
|
if (block.label) {
|
|
61705
|
-
if (
|
|
61794
|
+
if (MEMFS_MANAGED_LABELS.has(block.label)) {
|
|
61706
61795
|
continue;
|
|
61707
61796
|
}
|
|
61708
61797
|
if (systemBlockMap.has(block.label)) {
|
|
@@ -61720,7 +61809,7 @@ async function checkMemoryFilesystemStatus(agentId, options) {
|
|
|
61720
61809
|
...Object.keys(lastState.fileHashes)
|
|
61721
61810
|
]);
|
|
61722
61811
|
for (const label of Array.from(allLabels).sort()) {
|
|
61723
|
-
if (
|
|
61812
|
+
if (MEMFS_MANAGED_LABELS.has(label))
|
|
61724
61813
|
continue;
|
|
61725
61814
|
const systemFile = systemFiles.get(label);
|
|
61726
61815
|
const detachedFile = detachedFiles.get(label);
|
|
@@ -61758,6 +61847,9 @@ function classifyLabel(label, fileContent, blockValue, lastFileHash, lastBlockHa
|
|
|
61758
61847
|
if (lastBlockHash && !fileChanged) {
|
|
61759
61848
|
return;
|
|
61760
61849
|
}
|
|
61850
|
+
if (READ_ONLY_BLOCK_LABELS.includes(label)) {
|
|
61851
|
+
return;
|
|
61852
|
+
}
|
|
61761
61853
|
newFiles.push(label);
|
|
61762
61854
|
return;
|
|
61763
61855
|
}
|
|
@@ -61790,14 +61882,11 @@ async function detachMemoryFilesystemBlock(agentId) {
|
|
|
61790
61882
|
await client.agents.blocks.detach(memfsBlock.id, { agent_id: agentId });
|
|
61791
61883
|
}
|
|
61792
61884
|
}
|
|
61793
|
-
var MEMORY_FILESYSTEM_BLOCK_LABEL = "memory_filesystem", MEMORY_FS_ROOT = ".letta", MEMORY_FS_AGENTS_DIR = "agents", MEMORY_FS_MEMORY_DIR = "memory", MEMORY_SYSTEM_DIR = "system", MEMORY_FS_STATE_FILE = ".sync-state.json",
|
|
61885
|
+
var MEMORY_FILESYSTEM_BLOCK_LABEL = "memory_filesystem", MEMORY_FS_ROOT = ".letta", MEMORY_FS_AGENTS_DIR = "agents", MEMORY_FS_MEMORY_DIR = "memory", MEMORY_SYSTEM_DIR = "system", MEMORY_FS_STATE_FILE = ".sync-state.json", MEMFS_MANAGED_LABELS;
|
|
61794
61886
|
var init_memoryFilesystem = __esm(async () => {
|
|
61795
61887
|
init_memory();
|
|
61796
61888
|
await init_client2();
|
|
61797
|
-
|
|
61798
|
-
MEMORY_FILESYSTEM_BLOCK_LABEL,
|
|
61799
|
-
...ISOLATED_BLOCK_LABELS
|
|
61800
|
-
]);
|
|
61889
|
+
MEMFS_MANAGED_LABELS = new Set([MEMORY_FILESYSTEM_BLOCK_LABEL]);
|
|
61801
61890
|
});
|
|
61802
61891
|
|
|
61803
61892
|
// src/agent/message.ts
|
|
@@ -73563,17 +73652,10 @@ var init_registry = __esm(() => {
|
|
|
73563
73652
|
return "Opening memory viewer...";
|
|
73564
73653
|
}
|
|
73565
73654
|
},
|
|
73566
|
-
"/memfs-sync": {
|
|
73567
|
-
desc: "Sync memory blocks with filesystem (requires memFS enabled)",
|
|
73568
|
-
order: 15.5,
|
|
73569
|
-
handler: () => {
|
|
73570
|
-
return "Syncing memory filesystem...";
|
|
73571
|
-
}
|
|
73572
|
-
},
|
|
73573
73655
|
"/memfs": {
|
|
73574
|
-
desc: "
|
|
73575
|
-
args: "[enable|disable]",
|
|
73576
|
-
order: 15.
|
|
73656
|
+
desc: "Manage filesystem-backed memory (/memfs [enable|disable|sync|reset])",
|
|
73657
|
+
args: "[enable|disable|sync|reset]",
|
|
73658
|
+
order: 15.5,
|
|
73577
73659
|
handler: () => {
|
|
73578
73660
|
return "Managing memory filesystem...";
|
|
73579
73661
|
}
|
|
@@ -88957,8 +89039,8 @@ var exports_App = {};
|
|
|
88957
89039
|
__export(exports_App, {
|
|
88958
89040
|
default: () => App2
|
|
88959
89041
|
});
|
|
88960
|
-
import { existsSync as existsSync16, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
88961
|
-
import { homedir as homedir15 } from "node:os";
|
|
89042
|
+
import { existsSync as existsSync16, readFileSync as readFileSync8, renameSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
89043
|
+
import { homedir as homedir15, tmpdir as tmpdir3 } from "node:os";
|
|
88962
89044
|
import { join as join28 } from "node:path";
|
|
88963
89045
|
function getErrorHintForStopReason(stopReason, currentModelId) {
|
|
88964
89046
|
if (currentModelId === "opus" && stopReason === "llm_api_error" && getModelInfo("bedrock-opus")) {
|
|
@@ -89355,7 +89437,7 @@ function App2({
|
|
|
89355
89437
|
const [memorySyncConflicts, setMemorySyncConflicts] = import_react88.useState(null);
|
|
89356
89438
|
const memorySyncProcessedToolCallsRef = import_react88.useRef(new Set);
|
|
89357
89439
|
const memorySyncCommandIdRef = import_react88.useRef(null);
|
|
89358
|
-
const memorySyncCommandInputRef = import_react88.useRef("/memfs
|
|
89440
|
+
const memorySyncCommandInputRef = import_react88.useRef("/memfs sync");
|
|
89359
89441
|
const memorySyncInFlightRef = import_react88.useRef(false);
|
|
89360
89442
|
const memoryFilesystemInitializedRef = import_react88.useRef(false);
|
|
89361
89443
|
const pendingMemfsConflictsRef = import_react88.useRef(null);
|
|
@@ -89868,7 +89950,7 @@ function App2({
|
|
|
89868
89950
|
});
|
|
89869
89951
|
}
|
|
89870
89952
|
}, [refreshDerived, currentModelId]);
|
|
89871
|
-
const updateMemorySyncCommand = import_react88.useCallback((commandId, output, success, input = "/memfs
|
|
89953
|
+
const updateMemorySyncCommand = import_react88.useCallback((commandId, output, success, input = "/memfs sync", keepRunning = false) => {
|
|
89872
89954
|
buffersRef.current.byId.set(commandId, {
|
|
89873
89955
|
kind: "command",
|
|
89874
89956
|
id: commandId,
|
|
@@ -89899,7 +89981,7 @@ function App2({
|
|
|
89899
89981
|
setMemorySyncConflicts(result.conflicts);
|
|
89900
89982
|
setActiveOverlay("memfs-sync");
|
|
89901
89983
|
if (commandId) {
|
|
89902
|
-
updateMemorySyncCommand(commandId, `Memory sync paused — resolve ${result.conflicts.length} conflict${result.conflicts.length === 1 ? "" : "s"} to continue.`, false, "/memfs
|
|
89984
|
+
updateMemorySyncCommand(commandId, `Memory sync paused — resolve ${result.conflicts.length} conflict${result.conflicts.length === 1 ? "" : "s"} to continue.`, false, "/memfs sync", true);
|
|
89903
89985
|
}
|
|
89904
89986
|
} else {
|
|
89905
89987
|
debugLog("memfs", `${source} sync found ${result.conflicts.length} conflict(s), queuing for agent`);
|
|
@@ -90028,7 +90110,7 @@ function App2({
|
|
|
90028
90110
|
const commandId = memorySyncCommandIdRef.current;
|
|
90029
90111
|
const commandInput = memorySyncCommandInputRef.current;
|
|
90030
90112
|
memorySyncCommandIdRef.current = null;
|
|
90031
|
-
memorySyncCommandInputRef.current = "/memfs
|
|
90113
|
+
memorySyncCommandInputRef.current = "/memfs sync";
|
|
90032
90114
|
const resolutions = memorySyncConflicts.map((conflict) => {
|
|
90033
90115
|
const answer = answers[`Conflict for ${conflict.label}`];
|
|
90034
90116
|
return {
|
|
@@ -90077,7 +90159,7 @@ ${resolutionSummary}`, true, commandInput);
|
|
|
90077
90159
|
const commandId = memorySyncCommandIdRef.current;
|
|
90078
90160
|
const commandInput = memorySyncCommandInputRef.current;
|
|
90079
90161
|
memorySyncCommandIdRef.current = null;
|
|
90080
|
-
memorySyncCommandInputRef.current = "/memfs
|
|
90162
|
+
memorySyncCommandInputRef.current = "/memfs sync";
|
|
90081
90163
|
memorySyncInFlightRef.current = false;
|
|
90082
90164
|
setMemorySyncConflicts(null);
|
|
90083
90165
|
setActiveOverlay(null);
|
|
@@ -92624,46 +92706,32 @@ Press Enter to continue, or type anything to cancel.`, false, "running");
|
|
|
92624
92706
|
}
|
|
92625
92707
|
return { submitted: true };
|
|
92626
92708
|
}
|
|
92627
|
-
if (trimmed
|
|
92628
|
-
|
|
92629
|
-
|
|
92630
|
-
|
|
92709
|
+
if (trimmed.startsWith("/memfs")) {
|
|
92710
|
+
const [, subcommand] = trimmed.split(/\s+/);
|
|
92711
|
+
const cmdId = uid4("cmd");
|
|
92712
|
+
if (!subcommand || subcommand === "help") {
|
|
92713
|
+
const output = [
|
|
92714
|
+
"memfs commands:",
|
|
92715
|
+
"- /memfs status — show status",
|
|
92716
|
+
"- /memfs enable — enable filesystem-backed memory",
|
|
92717
|
+
"- /memfs disable — disable filesystem-backed memory",
|
|
92718
|
+
"- /memfs sync — sync blocks and files now",
|
|
92719
|
+
"- /memfs reset — move local memfs to /tmp and recreate dirs"
|
|
92720
|
+
].join(`
|
|
92721
|
+
`);
|
|
92722
|
+
buffersRef.current.byId.set(cmdId, {
|
|
92631
92723
|
kind: "command",
|
|
92632
|
-
id:
|
|
92724
|
+
id: cmdId,
|
|
92633
92725
|
input: msg,
|
|
92634
|
-
output
|
|
92726
|
+
output,
|
|
92635
92727
|
phase: "finished",
|
|
92636
|
-
success:
|
|
92728
|
+
success: true
|
|
92637
92729
|
});
|
|
92638
|
-
buffersRef.current.order.push(
|
|
92730
|
+
buffersRef.current.order.push(cmdId);
|
|
92639
92731
|
refreshDerived();
|
|
92640
92732
|
return { submitted: true };
|
|
92641
92733
|
}
|
|
92642
|
-
|
|
92643
|
-
buffersRef.current.byId.set(cmdId, {
|
|
92644
|
-
kind: "command",
|
|
92645
|
-
id: cmdId,
|
|
92646
|
-
input: msg,
|
|
92647
|
-
output: "Syncing memory filesystem...",
|
|
92648
|
-
phase: "running"
|
|
92649
|
-
});
|
|
92650
|
-
buffersRef.current.order.push(cmdId);
|
|
92651
|
-
refreshDerived();
|
|
92652
|
-
setCommandRunning(true);
|
|
92653
|
-
try {
|
|
92654
|
-
await runMemoryFilesystemSync("command", cmdId);
|
|
92655
|
-
} catch (error) {
|
|
92656
|
-
const errorText = error instanceof Error ? error.message : String(error);
|
|
92657
|
-
updateMemorySyncCommand(cmdId, `Failed: ${errorText}`, false);
|
|
92658
|
-
} finally {
|
|
92659
|
-
setCommandRunning(false);
|
|
92660
|
-
}
|
|
92661
|
-
return { submitted: true };
|
|
92662
|
-
}
|
|
92663
|
-
if (trimmed.startsWith("/memfs")) {
|
|
92664
|
-
const [, subcommand] = trimmed.split(/\s+/);
|
|
92665
|
-
const cmdId = uid4("cmd");
|
|
92666
|
-
if (!subcommand || subcommand === "status") {
|
|
92734
|
+
if (subcommand === "status") {
|
|
92667
92735
|
const enabled = settingsManager.isMemfsEnabled(agentId);
|
|
92668
92736
|
let output;
|
|
92669
92737
|
if (enabled) {
|
|
@@ -92725,6 +92793,71 @@ ${formatMemorySyncSummary(result2)}`, true, msg);
|
|
|
92725
92793
|
}
|
|
92726
92794
|
return { submitted: true };
|
|
92727
92795
|
}
|
|
92796
|
+
if (subcommand === "sync") {
|
|
92797
|
+
if (!settingsManager.isMemfsEnabled(agentId)) {
|
|
92798
|
+
buffersRef.current.byId.set(cmdId, {
|
|
92799
|
+
kind: "command",
|
|
92800
|
+
id: cmdId,
|
|
92801
|
+
input: msg,
|
|
92802
|
+
output: "Memory filesystem is disabled. Run `/memfs enable` first.",
|
|
92803
|
+
phase: "finished",
|
|
92804
|
+
success: false
|
|
92805
|
+
});
|
|
92806
|
+
buffersRef.current.order.push(cmdId);
|
|
92807
|
+
refreshDerived();
|
|
92808
|
+
return { submitted: true };
|
|
92809
|
+
}
|
|
92810
|
+
buffersRef.current.byId.set(cmdId, {
|
|
92811
|
+
kind: "command",
|
|
92812
|
+
id: cmdId,
|
|
92813
|
+
input: msg,
|
|
92814
|
+
output: "Syncing memory filesystem...",
|
|
92815
|
+
phase: "running"
|
|
92816
|
+
});
|
|
92817
|
+
buffersRef.current.order.push(cmdId);
|
|
92818
|
+
refreshDerived();
|
|
92819
|
+
setCommandRunning(true);
|
|
92820
|
+
try {
|
|
92821
|
+
await runMemoryFilesystemSync("command", cmdId);
|
|
92822
|
+
} catch (error) {
|
|
92823
|
+
const errorText = error instanceof Error ? error.message : String(error);
|
|
92824
|
+
updateMemorySyncCommand(cmdId, `Failed: ${errorText}`, false);
|
|
92825
|
+
} finally {
|
|
92826
|
+
setCommandRunning(false);
|
|
92827
|
+
}
|
|
92828
|
+
return { submitted: true };
|
|
92829
|
+
}
|
|
92830
|
+
if (subcommand === "reset") {
|
|
92831
|
+
buffersRef.current.byId.set(cmdId, {
|
|
92832
|
+
kind: "command",
|
|
92833
|
+
id: cmdId,
|
|
92834
|
+
input: msg,
|
|
92835
|
+
output: "Resetting memory filesystem...",
|
|
92836
|
+
phase: "running"
|
|
92837
|
+
});
|
|
92838
|
+
buffersRef.current.order.push(cmdId);
|
|
92839
|
+
refreshDerived();
|
|
92840
|
+
setCommandRunning(true);
|
|
92841
|
+
try {
|
|
92842
|
+
const memoryDir = getMemoryFilesystemRoot(agentId);
|
|
92843
|
+
if (!existsSync16(memoryDir)) {
|
|
92844
|
+
updateMemorySyncCommand(cmdId, "No local memory filesystem found to reset.", true, msg);
|
|
92845
|
+
return { submitted: true };
|
|
92846
|
+
}
|
|
92847
|
+
const backupDir = join28(tmpdir3(), `letta-memfs-reset-${agentId}-${Date.now()}`);
|
|
92848
|
+
renameSync(memoryDir, backupDir);
|
|
92849
|
+
ensureMemoryFilesystemDirs(agentId);
|
|
92850
|
+
updateMemorySyncCommand(cmdId, `Memory filesystem reset.
|
|
92851
|
+
Backup moved to ${backupDir}
|
|
92852
|
+
Run \`/memfs sync\` to repopulate from API.`, true, msg);
|
|
92853
|
+
} catch (error) {
|
|
92854
|
+
const errorText = error instanceof Error ? error.message : String(error);
|
|
92855
|
+
updateMemorySyncCommand(cmdId, `Failed to reset memfs: ${errorText}`, false, msg);
|
|
92856
|
+
} finally {
|
|
92857
|
+
setCommandRunning(false);
|
|
92858
|
+
}
|
|
92859
|
+
return { submitted: true };
|
|
92860
|
+
}
|
|
92728
92861
|
if (subcommand === "disable") {
|
|
92729
92862
|
buffersRef.current.byId.set(cmdId, {
|
|
92730
92863
|
kind: "command",
|
|
@@ -92767,7 +92900,7 @@ Files on disk have been kept.`, true, msg);
|
|
|
92767
92900
|
kind: "command",
|
|
92768
92901
|
id: cmdId,
|
|
92769
92902
|
input: msg,
|
|
92770
|
-
output: `Unknown subcommand: ${subcommand}. Use /memfs, /memfs enable, or /memfs
|
|
92903
|
+
output: `Unknown subcommand: ${subcommand}. Use /memfs, /memfs enable, /memfs disable, /memfs sync, or /memfs reset.`,
|
|
92771
92904
|
phase: "finished",
|
|
92772
92905
|
success: false
|
|
92773
92906
|
});
|
|
@@ -100954,4 +101087,4 @@ Error during initialization: ${message}`);
|
|
|
100954
101087
|
}
|
|
100955
101088
|
main();
|
|
100956
101089
|
|
|
100957
|
-
//# debugId=
|
|
101090
|
+
//# debugId=B77A56113A42C53064756E2164756E21
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@ description: Manage memory filesystem sync conflicts with git-like commands. Loa
|
|
|
5
5
|
|
|
6
6
|
# Memory Filesystem Sync
|
|
7
7
|
|
|
8
|
-
When memFS is enabled, your memory blocks are mirrored as `.md` files on disk at `~/.letta/agents/<agent-id>/memory/`. Changes to blocks or files are detected via content hashing and synced at startup and on manual `/memfs
|
|
8
|
+
When memFS is enabled, your memory blocks are mirrored as `.md` files on disk at `~/.letta/agents/<agent-id>/memory/`. Changes to blocks or files are detected via content hashing and synced at startup and on manual `/memfs sync`.
|
|
9
9
|
|
|
10
10
|
**Conflicts** occur when both the file and the block are modified since the last sync (e.g., user edits a file in their editor while the block is also updated manually by the user via the API). Non-conflicting changes (only one side changed) are resolved automatically during the next sync.
|
|
11
11
|
|
|
@@ -96,5 +96,5 @@ npx tsx <SKILL_DIR>/scripts/memfs-resolve.ts $LETTA_AGENT_ID --resolutions '[{"l
|
|
|
96
96
|
## Notes
|
|
97
97
|
|
|
98
98
|
- Non-conflicting changes (only one side modified) are resolved automatically during the next sync — you only need to intervene for true conflicts
|
|
99
|
-
- The `/memfs
|
|
99
|
+
- The `/memfs sync` command is still available for users to manually trigger sync and resolve conflicts via the CLI overlay
|
|
100
100
|
- After resolving, the sync state is updated so the same conflicts won't reappear
|