import { type MemoryScope, MemoryStore } from "../memory/store.js"; import { setRuntimeSessionContext, type RuntimeSessionContext, } from "../runtime/sessionContext.js"; import { SkillStore } from "../skills/store.js"; export type MemoryManagerInput = { action: "add" | "list" | "replace" | "remove"; content?: string; scope: string; target_id?: string; }; export type SkillManagerInput = { action: | "list" | "write_skill" | "remove_skill" | "append_pattern" | "remove_pattern" | "write_reference" | "remove_reference" | "write_script" | "remove_script"; content?: string; file_path?: string; pattern?: string; skill_path: string; target_id?: string; }; export const executeMemoryManager = async ( memoryStore: MemoryStore, sessionContext: RuntimeSessionContext, input: MemoryManagerInput, ) => { const scope: MemoryScope | null = input.scope === "user" ? "user" : input.scope === "workspace" ? "workspace" : null; if (!scope) { return rejected( "memory", `unsupported scope: ${input.scope}; use exact keyword 'user' or 'workspace'`, ); } if (sessionContext.allowLearningWrite === false && input.action !== "list") { return rejected("memory", "memory writes are disabled for this session"); } const scopeKey = scope === "user" ? sessionContext.actorKey : sessionContext.projectKey; if (input.action === "list") { setRuntimeSessionContext({ ...sessionContext, memoryListReadScopes: { ...(sessionContext.memoryListReadScopes ?? {}), [scope]: true, }, }); return { ok: true, kind: "memory", decision: "accepted", detail: "memory listed", items: await memoryStore.list(scope, scopeKey), target: scope, }; } if (input.action === "add") { if (sessionContext.memoryListReadScopes?.[scope] !== true) { return { ...rejected( "memory", `must list ${scope} memory and review existing entries before add`, ), target: scope, }; } const result = await memoryStore.upsert(scope, scopeKey, { content: input.content ?? "", sessionId: sessionContext.clientSessionId, source: "tool", traceId: sessionContext.traceId, }); if (!result.entry) { return rejected("memory", "content rejected by persistence policy"); } return { ok: true, kind: "memory", decision: result.changed ? "accepted" : "deduped", detail: result.detail, entry: result.entry, target: scope, }; } const result = input.action === "replace" ? await memoryStore.replace(scope, scopeKey, input.target_id ?? "", { content: input.content ?? "", sessionId: sessionContext.clientSessionId, source: "tool", traceId: sessionContext.traceId, }) : await memoryStore.remove(scope, scopeKey, input.target_id ?? ""); return { ok: true, kind: "memory", decision: result.changed ? "accepted" : "rejected", detail: result.detail, target: scope, }; }; export const executeSkillManager = async ( skillStore: SkillStore, sessionContext: RuntimeSessionContext, input: SkillManagerInput, ) => { if (sessionContext.allowLearningWrite === false && input.action !== "list") { return rejected("skill", "skill writes are disabled for this session"); } if (input.action === "list") { const result = await skillStore.list(input.skill_path); if (!result) { return rejected( "skill", "invalid skill_path; expected a relative path under .opencode/skills", ); } return { ok: true, kind: "skill", decision: "accepted", detail: "skill listed", references: result.references, scripts: result.scripts, skill_path: result.skillPath, target: result.target, patterns: result.patterns, }; } const result = input.action === "write_skill" ? await skillStore.writeSkill(input.skill_path, input.content ?? "") : input.action === "remove_skill" ? await skillStore.removeSkill(input.skill_path) : input.action === "append_pattern" ? await skillStore.appendPattern(input.skill_path, input.pattern ?? "") : input.action === "remove_pattern" ? await skillStore.removePattern(input.skill_path, input.target_id ?? "") : input.action === "write_reference" ? await skillStore.writeReference( input.skill_path, input.file_path ?? "", input.content ?? "", ) : input.action === "remove_reference" ? await skillStore.removeReference( input.skill_path, input.file_path ?? "", ) : input.action === "write_script" ? await skillStore.writeScript( input.skill_path, input.file_path ?? "", input.content ?? "", ) : await skillStore.removeScript( input.skill_path, input.file_path ?? "", ); return { ok: true, kind: "skill", decision: result.changed ? "accepted" : "rejected", detail: result.detail, target: result.target, }; }; const rejected = (kind: "memory" | "skill", detail: string) => ({ ok: true, kind, decision: "rejected", detail, });