- reference/memfs-selfhosted-fix.patch: Implementation patch - reference/PR-memfs-selfhosted.md: PR description - E: Local-first initialization - F: Configurable backend support - Addresses subagent spawn failures on self-hosted
91 lines
3.1 KiB
Diff
91 lines
3.1 KiB
Diff
--- a/src/agent/memoryGit.ts
|
|
+++ b/src/agent/memoryGit.ts
|
|
@@ -308,6 +308,45 @@ export function isGitRepo(agentId: string): boolean {
|
|
return existsSync(join(getMemoryRepoDir(agentId), ".git"));
|
|
}
|
|
|
|
+/**
|
|
+ * Initialize a local memory repository without remote.
|
|
+ * Used for self-hosted or Gitea backends where cloud git endpoint isn't available.
|
|
+ */
|
|
+export async function initLocalMemoryRepo(agentId: string): Promise<void> {
|
|
+ const dir = getMemoryRepoDir(agentId);
|
|
+
|
|
+ debugLog("memfs-git", `Initializing local repo at ${dir}`);
|
|
+
|
|
+ if (!existsSync(dir)) {
|
|
+ mkdirSync(dir, { recursive: true });
|
|
+ }
|
|
+
|
|
+ // Initialize git repo
|
|
+ await runGit(dir, ["init"], undefined);
|
|
+
|
|
+ // Configure user (required for commits)
|
|
+ await runGit(dir, ["config", "user.email", "ani@localhost"], undefined);
|
|
+ await runGit(dir, ["config", "user.name", "Ani"], undefined);
|
|
+
|
|
+ // Create initial structure
|
|
+ const systemDir = join(dir, "system");
|
|
+ if (!existsSync(systemDir)) {
|
|
+ mkdirSync(systemDir, { recursive: true });
|
|
+ }
|
|
+
|
|
+ // Install pre-commit hook
|
|
+ installPreCommitHook(dir);
|
|
+
|
|
+ debugLog("memfs-git", "Local memory repo initialized");
|
|
+}
|
|
+
|
|
/**
|
|
* Clone the agent's state repo into the memory directory.
|
|
*
|
|
--- a/src/agent/memoryFilesystem.ts
|
|
+++ b/src/agent/memoryFilesystem.ts
|
|
@@ -250,13 +250,40 @@ export async function applyMemfsFlags(
|
|
// 4. Add git tag + clone/pull repo.
|
|
let pullSummary: string | undefined;
|
|
if (isEnabled) {
|
|
- const { addGitMemoryTag, isGitRepo, cloneMemoryRepo, pullMemory } =
|
|
+ const { addGitMemoryTag, isGitRepo, cloneMemoryRepo, pullMemory, initLocalMemoryRepo } =
|
|
await import("./memoryGit");
|
|
await addGitMemoryTag(
|
|
agentId,
|
|
options?.agentTags ? { tags: options.agentTags } : undefined,
|
|
);
|
|
- if (!isGitRepo(agentId) && (await isLettaCloud())) {
|
|
- await cloneMemoryRepo(agentId);
|
|
+
|
|
+ // E: Local-first initialization
|
|
+ // F: Configurable backend support
|
|
+ const memfsBackend = process.env.LETTABOT_MEMFS_BACKEND || "auto";
|
|
+ const isCloud = await isLettaCloud();
|
|
+
|
|
+ if (!isGitRepo(agentId)) {
|
|
+ if (memfsBackend === "none" || memfsBackend === "local") {
|
|
+ // Local-only mode (self-hosted, Gitea, Codeberg)
|
|
+ await initLocalMemoryRepo(agentId);
|
|
+ } else if (memfsBackend === "cloud" || (memfsBackend === "auto" && isCloud)) {
|
|
+ // Cloud mode (Letta Cloud)
|
|
+ await cloneMemoryRepo(agentId);
|
|
+ } else {
|
|
+ // Auto mode, self-hosted detected - use local init
|
|
+ await initLocalMemoryRepo(agentId);
|
|
+ }
|
|
} else if (options?.pullOnExistingRepo) {
|
|
- const result = await pullMemory(agentId);
|
|
- pullSummary = result.summary;
|
|
+ // Try to pull, but don't fail if remote unavailable
|
|
+ try {
|
|
+ const result = await pullMemory(agentId);
|
|
+ pullSummary = result.summary;
|
|
+ } catch (error) {
|
|
+ if (error.message?.includes("501") || error.message?.includes("404")) {
|
|
+ // Remote not available - continue with local
|
|
+ console.log("[memfs] Remote unavailable, using local state");
|
|
+ } else {
|
|
+ throw error;
|
|
+ }
|
|
+ }
|
|
}
|
|
}
|
|
|