refactor: unify agent session persistence
This commit is contained in:
+30
-19
@@ -1,4 +1,5 @@
|
||||
import { dirname, join, posix } from "node:path";
|
||||
import { dirname, isAbsolute, join, posix, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { config } from "../config.js";
|
||||
import {
|
||||
@@ -17,8 +18,13 @@ import {
|
||||
} from "../utils/persistencePolicy.js";
|
||||
|
||||
const LEARNED_PATTERNS_MARKER = "## Learned Patterns";
|
||||
const SKILLS_ROOT_DIR = ".opencode/skills";
|
||||
const SKILLS_HISTORY_DIR = join(config.PERSISTENCE_HISTORY_DIR, "skills");
|
||||
const PROJECT_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const resolveProjectPath = (path: string) =>
|
||||
isAbsolute(path) ? path : resolve(PROJECT_ROOT_DIR, path);
|
||||
const DEFAULT_SKILLS_ROOT_DIR = resolveProjectPath(config.OPENCODE_SKILLS_ROOT_DIR);
|
||||
const DEFAULT_SKILLS_BACKUP_DIR = resolveProjectPath(
|
||||
join(config.PERSISTENCE_BACKUP_DIR, "skills"),
|
||||
);
|
||||
|
||||
export type SkillPatternRecord = {
|
||||
id: string;
|
||||
@@ -28,6 +34,11 @@ export type SkillPatternRecord = {
|
||||
export class SkillStore {
|
||||
private writeQueue: Promise<void> = Promise.resolve();
|
||||
|
||||
constructor(
|
||||
private readonly rootDir = DEFAULT_SKILLS_ROOT_DIR,
|
||||
private readonly backupDir = DEFAULT_SKILLS_BACKUP_DIR,
|
||||
) {}
|
||||
|
||||
async list(skillPath: string) {
|
||||
const normalizedSkillPath = normalizeSkillPath(skillPath);
|
||||
if (!normalizedSkillPath) {
|
||||
@@ -70,10 +81,10 @@ export class SkillStore {
|
||||
`${LEARNED_PATTERNS_MARKER}\n- [${record.id}] ${record.content}`,
|
||||
)
|
||||
: `${current.trimEnd()}\n\n${LEARNED_PATTERNS_MARKER}\n- [${record.id}] ${record.content}\n`;
|
||||
await ensureDirectory(join(SKILLS_ROOT_DIR, normalizedSkillPath));
|
||||
await ensureDirectory(join(this.rootDir, normalizedSkillPath));
|
||||
await atomicWriteFileWithHistory(target, next, {
|
||||
historyDir: SKILLS_HISTORY_DIR,
|
||||
rootDir: SKILLS_ROOT_DIR,
|
||||
backupDir: this.backupDir,
|
||||
rootDir: this.rootDir,
|
||||
});
|
||||
return { changed: true, detail: "skill file updated", target };
|
||||
});
|
||||
@@ -97,8 +108,8 @@ export class SkillStore {
|
||||
}
|
||||
const next = rewriteLearnedPatterns(current, remaining);
|
||||
await atomicWriteFileWithHistory(target, next, {
|
||||
historyDir: SKILLS_HISTORY_DIR,
|
||||
rootDir: SKILLS_ROOT_DIR,
|
||||
backupDir: this.backupDir,
|
||||
rootDir: this.rootDir,
|
||||
});
|
||||
return { changed: true, detail: "pattern removed", target };
|
||||
});
|
||||
@@ -118,11 +129,11 @@ export class SkillStore {
|
||||
return { changed: false, detail: "reference content rejected by persistence policy", target: "" };
|
||||
}
|
||||
return this.serializeWrite(async () => {
|
||||
const target = join(SKILLS_ROOT_DIR, normalizedSkillPath, normalizedReferencePath);
|
||||
const target = join(this.rootDir, normalizedSkillPath, normalizedReferencePath);
|
||||
await ensureDirectory(dirname(target));
|
||||
await atomicWriteFileWithHistory(target, `${sanitizedContent}\n`, {
|
||||
historyDir: SKILLS_HISTORY_DIR,
|
||||
rootDir: SKILLS_ROOT_DIR,
|
||||
backupDir: this.backupDir,
|
||||
rootDir: this.rootDir,
|
||||
});
|
||||
return { changed: true, detail: "reference written", target };
|
||||
});
|
||||
@@ -138,7 +149,7 @@ export class SkillStore {
|
||||
return { changed: false, detail: "invalid reference file_path", target: "" };
|
||||
}
|
||||
return this.serializeWrite(async () => {
|
||||
const target = join(SKILLS_ROOT_DIR, normalizedSkillPath, normalizedReferencePath);
|
||||
const target = join(this.rootDir, normalizedSkillPath, normalizedReferencePath);
|
||||
const previous = await readTextFile(target);
|
||||
if (previous === null) {
|
||||
return { changed: false, detail: "reference not found", target };
|
||||
@@ -162,11 +173,11 @@ export class SkillStore {
|
||||
return { changed: false, detail: "script content rejected by persistence policy", target: "" };
|
||||
}
|
||||
return this.serializeWrite(async () => {
|
||||
const target = join(SKILLS_ROOT_DIR, normalizedSkillPath, normalizedScriptPath);
|
||||
const target = join(this.rootDir, normalizedSkillPath, normalizedScriptPath);
|
||||
await ensureDirectory(dirname(target));
|
||||
await atomicWriteFileWithHistory(target, sanitizedContent, {
|
||||
historyDir: SKILLS_HISTORY_DIR,
|
||||
rootDir: SKILLS_ROOT_DIR,
|
||||
backupDir: this.backupDir,
|
||||
rootDir: this.rootDir,
|
||||
});
|
||||
return { changed: true, detail: "script written", target };
|
||||
});
|
||||
@@ -182,7 +193,7 @@ export class SkillStore {
|
||||
return { changed: false, detail: "invalid script file_path", target: "" };
|
||||
}
|
||||
return this.serializeWrite(async () => {
|
||||
const target = join(SKILLS_ROOT_DIR, normalizedSkillPath, normalizedScriptPath);
|
||||
const target = join(this.rootDir, normalizedSkillPath, normalizedScriptPath);
|
||||
const previous = await readTextFile(target);
|
||||
if (previous === null) {
|
||||
return { changed: false, detail: "script not found", target };
|
||||
@@ -193,19 +204,19 @@ export class SkillStore {
|
||||
}
|
||||
|
||||
private async listReferenceFiles(skillPath: string) {
|
||||
const referenceDir = join(SKILLS_ROOT_DIR, skillPath, "references");
|
||||
const referenceDir = join(this.rootDir, skillPath, "references");
|
||||
const files = await listFiles(referenceDir);
|
||||
return files.map((file) => file.slice(referenceDir.length + 1));
|
||||
}
|
||||
|
||||
private async listScriptFiles(skillPath: string) {
|
||||
const scriptDir = join(SKILLS_ROOT_DIR, skillPath, "scripts");
|
||||
const scriptDir = join(this.rootDir, skillPath, "scripts");
|
||||
const files = await listFiles(scriptDir);
|
||||
return files.map((file) => file.slice(scriptDir.length + 1));
|
||||
}
|
||||
|
||||
private skillFilePath(skillPath: string) {
|
||||
return join(SKILLS_ROOT_DIR, skillPath, "SKILL.md");
|
||||
return join(this.rootDir, skillPath, "SKILL.md");
|
||||
}
|
||||
|
||||
private async serializeWrite<T>(task: () => Promise<T>) {
|
||||
|
||||
Reference in New Issue
Block a user