mirror of
https://github.com/harivansh-afk/clanker-agent.git
synced 2026-04-21 06:04:44 +00:00
feat: add first-class memory management
Expose gateway memory APIs for status, init, files, search, and sync. Align pi-memory-md with project-scoped, local-first memory behavior. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
parent
df702d95a3
commit
2886855706
4 changed files with 1437 additions and 74 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import fs from "node:fs";
|
||||
import { createHash } from "node:crypto";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type {
|
||||
|
|
@ -61,7 +62,7 @@ export type ParsedFrontmatter = GrayMatterFile<string>["data"];
|
|||
const DEFAULT_LOCAL_PATH = path.join(os.homedir(), ".pi", "memory-md");
|
||||
|
||||
export function getCurrentDate(): string {
|
||||
return new Date().toISOString().split("T")[0];
|
||||
return new Date().toISOString().split("T")[0] ?? "";
|
||||
}
|
||||
|
||||
function expandPath(p: string): string {
|
||||
|
|
@ -71,12 +72,73 @@ function expandPath(p: string): string {
|
|||
return p;
|
||||
}
|
||||
|
||||
function getLegacyProjectDirName(cwd: string): string {
|
||||
return path.basename(cwd);
|
||||
}
|
||||
|
||||
function getProjectDirName(cwd: string): string {
|
||||
const projectName = getLegacyProjectDirName(cwd);
|
||||
const hash = createHash("sha256")
|
||||
.update(path.resolve(cwd))
|
||||
.digest("hex")
|
||||
.slice(0, 12);
|
||||
return `${projectName}-${hash}`;
|
||||
}
|
||||
|
||||
function migrateLegacyMemoryDir(
|
||||
preferredDir: string,
|
||||
legacyDir: string,
|
||||
): string {
|
||||
try {
|
||||
fs.renameSync(legacyDir, preferredDir);
|
||||
return preferredDir;
|
||||
} catch (error) {
|
||||
console.warn("Failed to migrate legacy memory dir:", error);
|
||||
return legacyDir;
|
||||
}
|
||||
}
|
||||
|
||||
export function getMemoryDir(
|
||||
settings: MemoryMdSettings,
|
||||
ctx: ExtensionContext,
|
||||
): string {
|
||||
const basePath = settings.localPath || DEFAULT_LOCAL_PATH;
|
||||
return path.join(basePath, path.basename(ctx.cwd));
|
||||
const preferredDir = path.join(basePath, getProjectDirName(ctx.cwd));
|
||||
if (fs.existsSync(preferredDir)) {
|
||||
return preferredDir;
|
||||
}
|
||||
|
||||
const legacyDir = path.join(basePath, getLegacyProjectDirName(ctx.cwd));
|
||||
if (fs.existsSync(legacyDir)) {
|
||||
return migrateLegacyMemoryDir(preferredDir, legacyDir);
|
||||
}
|
||||
|
||||
return preferredDir;
|
||||
}
|
||||
|
||||
export function getProjectRepoPath(
|
||||
settings: MemoryMdSettings,
|
||||
ctx: ExtensionContext,
|
||||
): string {
|
||||
const basePath = settings.localPath || DEFAULT_LOCAL_PATH;
|
||||
return path.relative(basePath, getMemoryDir(settings, ctx)).split(path.sep).join("/");
|
||||
}
|
||||
|
||||
export function resolveMemoryPath(
|
||||
settings: MemoryMdSettings,
|
||||
ctx: ExtensionContext,
|
||||
relativePath: string,
|
||||
): string {
|
||||
const memoryDir = getMemoryDir(settings, ctx);
|
||||
const resolvedPath = path.resolve(memoryDir, relativePath.trim());
|
||||
const resolvedRoot = path.resolve(memoryDir);
|
||||
if (
|
||||
resolvedPath !== resolvedRoot &&
|
||||
!resolvedPath.startsWith(`${resolvedRoot}${path.sep}`)
|
||||
) {
|
||||
throw new Error(`Memory path escapes root: ${relativePath}`);
|
||||
}
|
||||
return resolvedPath;
|
||||
}
|
||||
|
||||
function getRepoName(settings: MemoryMdSettings): string {
|
||||
|
|
@ -85,7 +147,26 @@ function getRepoName(settings: MemoryMdSettings): string {
|
|||
return match ? match[1] : "memory-md";
|
||||
}
|
||||
|
||||
function loadSettings(): MemoryMdSettings {
|
||||
function loadScopedSettings(settingsPath: string): MemoryMdSettings {
|
||||
if (!fs.existsSync(settingsPath)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(settingsPath, "utf-8");
|
||||
const parsed = JSON.parse(content);
|
||||
const scoped = parsed["pi-memory-md"];
|
||||
if (!scoped || typeof scoped !== "object" || Array.isArray(scoped)) {
|
||||
return {};
|
||||
}
|
||||
return scoped as MemoryMdSettings;
|
||||
} catch (error) {
|
||||
console.warn("Failed to load memory settings:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function loadSettings(cwd?: string): MemoryMdSettings {
|
||||
const DEFAULT_SETTINGS: MemoryMdSettings = {
|
||||
enabled: true,
|
||||
repoUrl: "",
|
||||
|
|
@ -104,27 +185,34 @@ function loadSettings(): MemoryMdSettings {
|
|||
"agent",
|
||||
"settings.json",
|
||||
);
|
||||
if (!fs.existsSync(globalSettings)) {
|
||||
return DEFAULT_SETTINGS;
|
||||
const projectSettings = cwd
|
||||
? path.join(cwd, ".pi", "settings.json")
|
||||
: undefined;
|
||||
const globalLoaded = loadScopedSettings(globalSettings);
|
||||
const projectLoaded = projectSettings
|
||||
? loadScopedSettings(projectSettings)
|
||||
: {};
|
||||
const loadedSettings = {
|
||||
...DEFAULT_SETTINGS,
|
||||
...globalLoaded,
|
||||
...projectLoaded,
|
||||
autoSync: {
|
||||
...DEFAULT_SETTINGS.autoSync,
|
||||
...globalLoaded.autoSync,
|
||||
...projectLoaded.autoSync,
|
||||
},
|
||||
systemPrompt: {
|
||||
...DEFAULT_SETTINGS.systemPrompt,
|
||||
...globalLoaded.systemPrompt,
|
||||
...projectLoaded.systemPrompt,
|
||||
},
|
||||
};
|
||||
|
||||
if (loadedSettings.localPath) {
|
||||
loadedSettings.localPath = expandPath(loadedSettings.localPath);
|
||||
}
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(globalSettings, "utf-8");
|
||||
const parsed = JSON.parse(content);
|
||||
const loadedSettings = {
|
||||
...DEFAULT_SETTINGS,
|
||||
...(parsed["pi-memory-md"] as MemoryMdSettings),
|
||||
};
|
||||
|
||||
if (loadedSettings.localPath) {
|
||||
loadedSettings.localPath = expandPath(loadedSettings.localPath);
|
||||
}
|
||||
|
||||
return loadedSettings;
|
||||
} catch (error) {
|
||||
console.warn("Failed to load memory settings:", error);
|
||||
return DEFAULT_SETTINGS;
|
||||
}
|
||||
return loadedSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -165,6 +253,33 @@ export async function syncRepository(
|
|||
if (fs.existsSync(localPath)) {
|
||||
const gitDir = path.join(localPath, ".git");
|
||||
if (!fs.existsSync(gitDir)) {
|
||||
let existingEntries: string[];
|
||||
try {
|
||||
existingEntries = fs.readdirSync(localPath);
|
||||
} catch {
|
||||
return {
|
||||
success: false,
|
||||
message: `Path exists but is not a directory: ${localPath}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (existingEntries.length === 0) {
|
||||
const cloneIntoEmptyDir = await gitExec(pi, localPath, "clone", repoUrl, ".");
|
||||
if (cloneIntoEmptyDir.success) {
|
||||
isRepoInitialized.value = true;
|
||||
const repoName = getRepoName(settings);
|
||||
return {
|
||||
success: true,
|
||||
message: `Cloned [${repoName}] successfully`,
|
||||
updated: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
message: "Clone failed - check repo URL and auth",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: `Directory exists but is not a git repo: ${localPath}`,
|
||||
|
|
@ -322,7 +437,7 @@ export function writeMemoryFile(
|
|||
* Build memory context for agent prompt.
|
||||
*/
|
||||
|
||||
function ensureDirectoryStructure(memoryDir: string): void {
|
||||
export function ensureDirectoryStructure(memoryDir: string): void {
|
||||
const dirs = [
|
||||
path.join(memoryDir, "core", "user"),
|
||||
path.join(memoryDir, "core", "project"),
|
||||
|
|
@ -334,7 +449,7 @@ function ensureDirectoryStructure(memoryDir: string): void {
|
|||
}
|
||||
}
|
||||
|
||||
function createDefaultFiles(memoryDir: string): void {
|
||||
export function createDefaultFiles(memoryDir: string): void {
|
||||
const identityFile = path.join(memoryDir, "core", "user", "identity.md");
|
||||
if (!fs.existsSync(identityFile)) {
|
||||
writeMemoryFile(
|
||||
|
|
@ -434,7 +549,7 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
|
|||
let memoryInjected = false;
|
||||
|
||||
pi.on("session_start", async (_event, ctx) => {
|
||||
settings = loadSettings();
|
||||
settings = loadSettings(ctx.cwd);
|
||||
|
||||
if (!settings.enabled) {
|
||||
return;
|
||||
|
|
@ -451,7 +566,11 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (settings.autoSync?.onSessionStart && settings.localPath) {
|
||||
if (
|
||||
settings.autoSync?.onSessionStart &&
|
||||
settings.localPath &&
|
||||
settings.repoUrl
|
||||
) {
|
||||
syncPromise = syncRepository(pi, settings, repoInitialized).then(
|
||||
(syncResult) => {
|
||||
if (settings.repoUrl) {
|
||||
|
|
@ -509,14 +628,20 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
|
|||
return undefined;
|
||||
});
|
||||
|
||||
registerAllTools(pi, settings, repoInitialized);
|
||||
registerAllTools(pi, () => settings, repoInitialized);
|
||||
|
||||
pi.registerCommand("memory-status", {
|
||||
description: "Show memory repository status",
|
||||
handler: async (_args, ctx) => {
|
||||
settings = loadSettings(ctx.cwd);
|
||||
const projectName = path.basename(ctx.cwd);
|
||||
const memoryDir = getMemoryDir(settings, ctx);
|
||||
const projectRepoPath = getProjectRepoPath(settings, ctx);
|
||||
const coreUserDir = path.join(memoryDir, "core", "user");
|
||||
const repoConfigured = Boolean(settings.repoUrl);
|
||||
const repoReady = Boolean(
|
||||
settings.localPath && fs.existsSync(path.join(settings.localPath, ".git")),
|
||||
);
|
||||
|
||||
if (!fs.existsSync(coreUserDir)) {
|
||||
ctx.ui.notify(
|
||||
|
|
@ -526,12 +651,37 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!repoConfigured) {
|
||||
ctx.ui.notify(
|
||||
`Memory: ${projectName} | Local only | Path: ${memoryDir}`,
|
||||
"info",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!repoReady || !settings.localPath) {
|
||||
ctx.ui.notify(
|
||||
`Memory: ${projectName} | Repo not initialized | Path: ${memoryDir}`,
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await gitExec(
|
||||
pi,
|
||||
settings.localPath!,
|
||||
settings.localPath,
|
||||
"status",
|
||||
"--porcelain",
|
||||
"--",
|
||||
projectRepoPath,
|
||||
);
|
||||
if (!result.success) {
|
||||
ctx.ui.notify(
|
||||
`Memory: ${projectName} | Repo status unavailable | Path: ${memoryDir}`,
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
}
|
||||
const isDirty = result.stdout.trim().length > 0;
|
||||
|
||||
ctx.ui.notify(
|
||||
|
|
@ -544,26 +694,36 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
|
|||
pi.registerCommand("memory-init", {
|
||||
description: "Initialize memory repository",
|
||||
handler: async (_args, ctx) => {
|
||||
settings = loadSettings(ctx.cwd);
|
||||
const memoryDir = getMemoryDir(settings, ctx);
|
||||
const alreadyInitialized = fs.existsSync(
|
||||
path.join(memoryDir, "core", "user"),
|
||||
);
|
||||
|
||||
const result = await syncRepository(pi, settings, repoInitialized);
|
||||
|
||||
if (!result.success) {
|
||||
ctx.ui.notify(`Initialization failed: ${result.message}`, "error");
|
||||
return;
|
||||
if (settings.repoUrl) {
|
||||
const result = await syncRepository(pi, settings, repoInitialized);
|
||||
if (!result.success) {
|
||||
ctx.ui.notify(`Initialization failed: ${result.message}`, "error");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ensureDirectoryStructure(memoryDir);
|
||||
createDefaultFiles(memoryDir);
|
||||
repoInitialized.value = true;
|
||||
|
||||
if (alreadyInitialized) {
|
||||
ctx.ui.notify(`Memory already exists: ${result.message}`, "info");
|
||||
ctx.ui.notify(
|
||||
settings.repoUrl
|
||||
? "Memory already exists and repository is ready"
|
||||
: "Local memory already exists",
|
||||
"info",
|
||||
);
|
||||
} else {
|
||||
ctx.ui.notify(
|
||||
`Memory initialized: ${result.message}\n\nCreated:\n - core/user\n - core/project\n - reference`,
|
||||
settings.repoUrl
|
||||
? "Memory initialized and repository is ready\n\nCreated:\n - core/user\n - core/project\n - reference"
|
||||
: "Local memory initialized\n\nCreated:\n - core/user\n - core/project\n - reference",
|
||||
"info",
|
||||
);
|
||||
}
|
||||
|
|
@ -573,6 +733,7 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
|
|||
pi.registerCommand("memory-refresh", {
|
||||
description: "Refresh memory context from files",
|
||||
handler: async (_args, ctx) => {
|
||||
settings = loadSettings(ctx.cwd);
|
||||
const memoryContext = buildMemoryContext(settings, ctx);
|
||||
|
||||
if (!memoryContext) {
|
||||
|
|
@ -610,6 +771,7 @@ export default function memoryMdExtension(pi: ExtensionAPI) {
|
|||
pi.registerCommand("memory-check", {
|
||||
description: "Check memory folder structure",
|
||||
handler: async (_args, ctx) => {
|
||||
settings = loadSettings(ctx.cwd);
|
||||
const memoryDir = getMemoryDir(settings, ctx);
|
||||
|
||||
if (!fs.existsSync(memoryDir)) {
|
||||
|
|
|
|||
|
|
@ -6,15 +6,21 @@ import { Text } from "@mariozechner/pi-tui";
|
|||
import { Type } from "@sinclair/typebox";
|
||||
import type { MemoryFrontmatter, MemoryMdSettings } from "./memory-md.js";
|
||||
import {
|
||||
createDefaultFiles,
|
||||
ensureDirectoryStructure,
|
||||
getCurrentDate,
|
||||
getMemoryDir,
|
||||
getProjectRepoPath,
|
||||
gitExec,
|
||||
listMemoryFiles,
|
||||
readMemoryFile,
|
||||
resolveMemoryPath,
|
||||
syncRepository,
|
||||
writeMemoryFile,
|
||||
} from "./memory-md.js";
|
||||
|
||||
type MemorySettingsGetter = () => MemoryMdSettings;
|
||||
|
||||
function renderWithExpandHint(
|
||||
text: string,
|
||||
theme: Theme,
|
||||
|
|
@ -34,7 +40,7 @@ function renderWithExpandHint(
|
|||
|
||||
export function registerMemorySync(
|
||||
pi: ExtensionAPI,
|
||||
settings: MemoryMdSettings,
|
||||
getSettings: MemorySettingsGetter,
|
||||
isRepoInitialized: { value: boolean },
|
||||
): void {
|
||||
pi.registerTool({
|
||||
|
|
@ -52,26 +58,73 @@ export function registerMemorySync(
|
|||
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
const { action } = params as { action: "pull" | "push" | "status" };
|
||||
const localPath = settings.localPath!;
|
||||
const settings = getSettings();
|
||||
const localPath = settings.localPath;
|
||||
const memoryDir = getMemoryDir(settings, ctx);
|
||||
const projectRepoPath = getProjectRepoPath(settings, ctx);
|
||||
const coreUserDir = path.join(memoryDir, "core", "user");
|
||||
const configured = Boolean(settings.repoUrl);
|
||||
const initialized = fs.existsSync(coreUserDir);
|
||||
const repoReady = Boolean(
|
||||
localPath && fs.existsSync(path.join(localPath, ".git")),
|
||||
);
|
||||
|
||||
if (action === "status") {
|
||||
const initialized =
|
||||
isRepoInitialized.value && fs.existsSync(coreUserDir);
|
||||
if (!initialized) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Memory repository not initialized. Use memory_init to set up.",
|
||||
text: "Memory not initialized. Use memory_init to set up.",
|
||||
},
|
||||
],
|
||||
details: { initialized: false },
|
||||
details: { initialized: false, configured, dirty: null },
|
||||
};
|
||||
}
|
||||
|
||||
const result = await gitExec(pi, localPath, "status", "--porcelain");
|
||||
if (!configured) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Memory repository is not configured. Local memory is available only on this machine.",
|
||||
},
|
||||
],
|
||||
details: { initialized: true, configured: false, dirty: null },
|
||||
};
|
||||
}
|
||||
|
||||
if (!repoReady || !localPath) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Memory repository is configured but not initialized locally.",
|
||||
},
|
||||
],
|
||||
details: { initialized: true, configured: true, dirty: null },
|
||||
};
|
||||
}
|
||||
|
||||
const result = await gitExec(
|
||||
pi,
|
||||
localPath,
|
||||
"status",
|
||||
"--porcelain",
|
||||
"--",
|
||||
projectRepoPath,
|
||||
);
|
||||
if (!result.success) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Unable to inspect memory repository status.",
|
||||
},
|
||||
],
|
||||
details: { initialized: true, configured: true, dirty: null },
|
||||
};
|
||||
}
|
||||
const dirty = result.stdout.trim().length > 0;
|
||||
|
||||
return {
|
||||
|
|
@ -88,36 +141,87 @@ export function registerMemorySync(
|
|||
}
|
||||
|
||||
if (action === "pull") {
|
||||
if (!configured) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Memory repository is not configured. Nothing to pull.",
|
||||
},
|
||||
],
|
||||
details: { success: false, configured: false },
|
||||
};
|
||||
}
|
||||
const result = await syncRepository(pi, settings, isRepoInitialized);
|
||||
return {
|
||||
content: [{ type: "text", text: result.message }],
|
||||
details: { success: result.success },
|
||||
details: { success: result.success, configured: true },
|
||||
};
|
||||
}
|
||||
|
||||
if (action === "push") {
|
||||
if (!configured || !localPath) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Memory repository is not configured. Nothing to push.",
|
||||
},
|
||||
],
|
||||
details: { success: false, configured: false },
|
||||
};
|
||||
}
|
||||
|
||||
if (!repoReady) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Memory repository is configured but not initialized locally.",
|
||||
},
|
||||
],
|
||||
details: { success: false, configured: true },
|
||||
};
|
||||
}
|
||||
|
||||
const statusResult = await gitExec(
|
||||
pi,
|
||||
localPath,
|
||||
"status",
|
||||
"--porcelain",
|
||||
"--",
|
||||
projectRepoPath,
|
||||
);
|
||||
if (!statusResult.success) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Unable to inspect memory repository before push.",
|
||||
},
|
||||
],
|
||||
details: { success: false, configured: true },
|
||||
};
|
||||
}
|
||||
const hasChanges = statusResult.stdout.trim().length > 0;
|
||||
|
||||
if (hasChanges) {
|
||||
await gitExec(pi, localPath, "add", ".");
|
||||
await gitExec(pi, localPath, "add", "-A", "--", projectRepoPath);
|
||||
|
||||
const timestamp = new Date()
|
||||
.toISOString()
|
||||
.replace(/[:.]/g, "-")
|
||||
.slice(0, 19);
|
||||
const commitMessage = `Update memory - ${timestamp}`;
|
||||
const commitMessage = `Update memory for ${path.basename(ctx.cwd)} - ${timestamp}`;
|
||||
const commitResult = await gitExec(
|
||||
pi,
|
||||
localPath,
|
||||
"commit",
|
||||
"-m",
|
||||
commitMessage,
|
||||
"--only",
|
||||
"--",
|
||||
projectRepoPath,
|
||||
);
|
||||
|
||||
if (!commitResult.success) {
|
||||
|
|
@ -189,7 +293,7 @@ export function registerMemorySync(
|
|||
|
||||
export function registerMemoryRead(
|
||||
pi: ExtensionAPI,
|
||||
settings: MemoryMdSettings,
|
||||
getSettings: MemorySettingsGetter,
|
||||
): void {
|
||||
pi.registerTool({
|
||||
name: "memory_read",
|
||||
|
|
@ -204,8 +308,8 @@ export function registerMemoryRead(
|
|||
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
const { path: relPath } = params as { path: string };
|
||||
const memoryDir = getMemoryDir(settings, ctx);
|
||||
const fullPath = path.join(memoryDir, relPath);
|
||||
const settings = getSettings();
|
||||
const fullPath = resolveMemoryPath(settings, ctx, relPath);
|
||||
|
||||
const memory = readMemoryFile(fullPath);
|
||||
if (!memory) {
|
||||
|
|
@ -271,7 +375,7 @@ export function registerMemoryRead(
|
|||
|
||||
export function registerMemoryWrite(
|
||||
pi: ExtensionAPI,
|
||||
settings: MemoryMdSettings,
|
||||
getSettings: MemorySettingsGetter,
|
||||
): void {
|
||||
pi.registerTool({
|
||||
name: "memory_write",
|
||||
|
|
@ -300,17 +404,24 @@ export function registerMemoryWrite(
|
|||
tags?: string[];
|
||||
};
|
||||
|
||||
const memoryDir = getMemoryDir(settings, ctx);
|
||||
const fullPath = path.join(memoryDir, relPath);
|
||||
const settings = getSettings();
|
||||
const fullPath = resolveMemoryPath(settings, ctx, relPath);
|
||||
|
||||
const existing = readMemoryFile(fullPath);
|
||||
const existingFrontmatter = existing?.frontmatter || { description };
|
||||
const existingFrontmatter = existing?.frontmatter;
|
||||
|
||||
const frontmatter: MemoryFrontmatter = {
|
||||
...existingFrontmatter,
|
||||
description,
|
||||
created: existingFrontmatter?.created ?? getCurrentDate(),
|
||||
updated: getCurrentDate(),
|
||||
...(tags && { tags }),
|
||||
...(existingFrontmatter?.limit !== undefined
|
||||
? { limit: existingFrontmatter.limit }
|
||||
: {}),
|
||||
...(tags !== undefined
|
||||
? { tags }
|
||||
: existingFrontmatter?.tags
|
||||
? { tags: existingFrontmatter.tags }
|
||||
: {}),
|
||||
};
|
||||
|
||||
writeMemoryFile(fullPath, content, frontmatter);
|
||||
|
|
@ -367,7 +478,7 @@ export function registerMemoryWrite(
|
|||
|
||||
export function registerMemoryList(
|
||||
pi: ExtensionAPI,
|
||||
settings: MemoryMdSettings,
|
||||
getSettings: MemorySettingsGetter,
|
||||
): void {
|
||||
pi.registerTool({
|
||||
name: "memory_list",
|
||||
|
|
@ -381,8 +492,11 @@ export function registerMemoryList(
|
|||
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
const { directory } = params as { directory?: string };
|
||||
const settings = getSettings();
|
||||
const memoryDir = getMemoryDir(settings, ctx);
|
||||
const searchDir = directory ? path.join(memoryDir, directory) : memoryDir;
|
||||
const searchDir = directory
|
||||
? resolveMemoryPath(settings, ctx, directory)
|
||||
: memoryDir;
|
||||
const files = listMemoryFiles(searchDir);
|
||||
const relPaths = files.map((f) => path.relative(memoryDir, f));
|
||||
|
||||
|
|
@ -432,7 +546,7 @@ export function registerMemoryList(
|
|||
|
||||
export function registerMemorySearch(
|
||||
pi: ExtensionAPI,
|
||||
settings: MemoryMdSettings,
|
||||
getSettings: MemorySettingsGetter,
|
||||
): void {
|
||||
pi.registerTool({
|
||||
name: "memory_search",
|
||||
|
|
@ -457,6 +571,7 @@ export function registerMemorySearch(
|
|||
query: string;
|
||||
searchIn: "content" | "tags" | "description";
|
||||
};
|
||||
const settings = getSettings();
|
||||
const memoryDir = getMemoryDir(settings, ctx);
|
||||
const files = listMemoryFiles(memoryDir);
|
||||
const results: Array<{ path: string; match: string }> = [];
|
||||
|
|
@ -544,7 +659,7 @@ export function registerMemorySearch(
|
|||
|
||||
export function registerMemoryInit(
|
||||
pi: ExtensionAPI,
|
||||
settings: MemoryMdSettings,
|
||||
getSettings: MemorySettingsGetter,
|
||||
isRepoInitialized: { value: boolean },
|
||||
): void {
|
||||
pi.registerTool({
|
||||
|
|
@ -558,10 +673,19 @@ export function registerMemoryInit(
|
|||
),
|
||||
}) as any,
|
||||
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
||||
const { force = false } = params as { force?: boolean };
|
||||
const settings = getSettings();
|
||||
const memoryDir = getMemoryDir(settings, ctx);
|
||||
const alreadyInitialized = fs.existsSync(
|
||||
path.join(memoryDir, "core", "user"),
|
||||
);
|
||||
const repoReady = Boolean(
|
||||
settings.localPath &&
|
||||
fs.existsSync(path.join(settings.localPath, ".git")),
|
||||
);
|
||||
|
||||
if (isRepoInitialized.value && !force) {
|
||||
if (alreadyInitialized && (!settings.repoUrl || repoReady) && !force) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
|
|
@ -573,18 +697,35 @@ export function registerMemoryInit(
|
|||
};
|
||||
}
|
||||
|
||||
const result = await syncRepository(pi, settings, isRepoInitialized);
|
||||
if (settings.repoUrl) {
|
||||
const result = await syncRepository(pi, settings, isRepoInitialized);
|
||||
if (!result.success) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Initialization failed: ${result.message}`,
|
||||
},
|
||||
],
|
||||
details: { success: false },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ensureDirectoryStructure(memoryDir);
|
||||
createDefaultFiles(memoryDir);
|
||||
isRepoInitialized.value = true;
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: result.success
|
||||
? `Memory repository initialized:\n${result.message}\n\nCreated directory structure:\n${["core/user", "core/project", "reference"].map((d) => ` - ${d}`).join("\n")}`
|
||||
: `Initialization failed: ${result.message}`,
|
||||
text: settings.repoUrl
|
||||
? `Memory repository initialized.\n\nCreated directory structure:\n${["core/user", "core/project", "reference"].map((d) => ` - ${d}`).join("\n")}`
|
||||
: `Local memory initialized.\n\nCreated directory structure:\n${["core/user", "core/project", "reference"].map((d) => ` - ${d}`).join("\n")}`,
|
||||
},
|
||||
],
|
||||
details: { success: result.success },
|
||||
details: { success: true },
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -628,7 +769,7 @@ export function registerMemoryInit(
|
|||
|
||||
export function registerMemoryCheck(
|
||||
pi: ExtensionAPI,
|
||||
settings: MemoryMdSettings,
|
||||
getSettings: MemorySettingsGetter,
|
||||
): void {
|
||||
pi.registerTool({
|
||||
name: "memory_check",
|
||||
|
|
@ -637,6 +778,7 @@ export function registerMemoryCheck(
|
|||
parameters: Type.Object({}) as any,
|
||||
|
||||
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
||||
const settings = getSettings();
|
||||
const memoryDir = getMemoryDir(settings, ctx);
|
||||
|
||||
if (!fs.existsSync(memoryDir)) {
|
||||
|
|
@ -719,14 +861,14 @@ export function registerMemoryCheck(
|
|||
|
||||
export function registerAllTools(
|
||||
pi: ExtensionAPI,
|
||||
settings: MemoryMdSettings,
|
||||
getSettings: MemorySettingsGetter,
|
||||
isRepoInitialized: { value: boolean },
|
||||
): void {
|
||||
registerMemorySync(pi, settings, isRepoInitialized);
|
||||
registerMemoryRead(pi, settings);
|
||||
registerMemoryWrite(pi, settings);
|
||||
registerMemoryList(pi, settings);
|
||||
registerMemorySearch(pi, settings);
|
||||
registerMemoryInit(pi, settings, isRepoInitialized);
|
||||
registerMemoryCheck(pi, settings);
|
||||
registerMemorySync(pi, getSettings, isRepoInitialized);
|
||||
registerMemoryRead(pi, getSettings);
|
||||
registerMemoryWrite(pi, getSettings);
|
||||
registerMemoryList(pi, getSettings);
|
||||
registerMemorySearch(pi, getSettings);
|
||||
registerMemoryInit(pi, getSettings, isRepoInitialized);
|
||||
registerMemoryCheck(pi, getSettings);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue