feat(agent): add credential refresh and unify learning tools

This commit is contained in:
2026-08-06 10:16:50 +08:00
parent 2dc37e3fd8
commit 258f4996eb
21 changed files with 1270 additions and 770 deletions
+1 -1
View File
@@ -77,12 +77,12 @@ type TurnReviewInput = {
export class LearningOrchestrator {
private readonly activeReviews = new Set<string>();
private readonly sessionLearningStateStore = new SessionLearningStateStore();
private readonly skillStore = new SkillStore();
constructor(
private readonly runtime: OpencodeRuntimeAdapter,
private readonly memoryStore: MemoryStore,
private readonly transcriptStore: SessionTranscriptStore,
private readonly skillStore: SkillStore,
) {}
async initialize() {
+194
View File
@@ -0,0 +1,194 @@
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,
});