feat(agent): add credential refresh and unify learning tools
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
Reference in New Issue
Block a user