195 lines
5.5 KiB
TypeScript
195 lines
5.5 KiB
TypeScript
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,
|
|
});
|