feat: add master-agent prompts and memory management
This commit is contained in:
@@ -218,6 +218,44 @@ export interface UserAttachmentStorageConfig {
|
||||
validatedAt?: string;
|
||||
}
|
||||
|
||||
export interface MasterAgentPromptPolicy {
|
||||
globalPrompt: string;
|
||||
updatedAt: string;
|
||||
updatedBy?: string;
|
||||
}
|
||||
|
||||
export interface UserMasterPrompt {
|
||||
account: string;
|
||||
content: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export type MasterMemoryScope = "global" | "project";
|
||||
export type MasterMemoryType =
|
||||
| "user_preference"
|
||||
| "project_progress"
|
||||
| "decision"
|
||||
| "risk"
|
||||
| "blocking_issue"
|
||||
| "research_note"
|
||||
| "workflow_rule";
|
||||
|
||||
export interface MasterAgentMemory {
|
||||
memoryId: string;
|
||||
account: string;
|
||||
scope: MasterMemoryScope;
|
||||
projectId?: string;
|
||||
title: string;
|
||||
content: string;
|
||||
memoryType: MasterMemoryType;
|
||||
tags: string[];
|
||||
sourceMessageId?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
lastUsedAt?: string;
|
||||
archived: boolean;
|
||||
}
|
||||
|
||||
export interface GoalItem {
|
||||
id: string;
|
||||
text: string;
|
||||
@@ -328,6 +366,7 @@ function buildCollaborationGate(project: Pick<Project, "isGroup" | "collaboratio
|
||||
export interface ProjectAgentControls {
|
||||
modelOverride?: string;
|
||||
reasoningEffortOverride?: ReasoningEffort;
|
||||
promptOverride?: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
@@ -809,6 +848,9 @@ export interface BossState {
|
||||
deviceSkills: DeviceSkill[];
|
||||
appLogs: AppLogEntry[];
|
||||
userAttachmentStorageConfigs: UserAttachmentStorageConfig[];
|
||||
masterAgentPromptPolicy: MasterAgentPromptPolicy | null;
|
||||
userMasterPrompts: UserMasterPrompt[];
|
||||
masterAgentMemories: MasterAgentMemory[];
|
||||
threadContextSnapshots: ThreadContextSnapshot[];
|
||||
threadHandoffPackages: ThreadHandoffPackage[];
|
||||
threadContextAlerts: ThreadContextAlert[];
|
||||
@@ -1223,6 +1265,9 @@ const initialState: BossState = {
|
||||
updatedAt: nowIso(),
|
||||
},
|
||||
],
|
||||
masterAgentPromptPolicy: null,
|
||||
userMasterPrompts: [],
|
||||
masterAgentMemories: [],
|
||||
masterAgentTasks: [],
|
||||
dispatchPlans: [],
|
||||
dispatchExecutions: [],
|
||||
@@ -2079,14 +2124,16 @@ function normalizeProjectAgentControls(
|
||||
const reasoningEffortOverride = isReasoningEffort(raw?.reasoningEffortOverride)
|
||||
? raw.reasoningEffortOverride
|
||||
: undefined;
|
||||
const promptOverride = trimToDefined(raw?.promptOverride);
|
||||
|
||||
if (!modelOverride && !reasoningEffortOverride) {
|
||||
if (!modelOverride && !reasoningEffortOverride && !promptOverride) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
modelOverride,
|
||||
reasoningEffortOverride,
|
||||
promptOverride,
|
||||
updatedAt: raw?.updatedAt ?? nowIso(),
|
||||
};
|
||||
}
|
||||
@@ -2512,6 +2559,67 @@ function normalizeAttachmentStorageConfig(
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeMasterAgentPromptPolicy(
|
||||
raw: Partial<MasterAgentPromptPolicy> | null | undefined,
|
||||
fallback?: MasterAgentPromptPolicy | null,
|
||||
): MasterAgentPromptPolicy | null {
|
||||
if (!raw) {
|
||||
return fallback ?? null;
|
||||
}
|
||||
const globalPrompt = raw.globalPrompt?.trim();
|
||||
if (!globalPrompt) {
|
||||
return fallback ?? null;
|
||||
}
|
||||
return {
|
||||
globalPrompt,
|
||||
updatedAt: raw.updatedAt ?? fallback?.updatedAt ?? nowIso(),
|
||||
updatedBy: raw.updatedBy?.trim() || fallback?.updatedBy,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeUserMasterPrompt(
|
||||
raw: Partial<UserMasterPrompt>,
|
||||
fallback?: UserMasterPrompt,
|
||||
): UserMasterPrompt {
|
||||
const account = raw.account ?? fallback?.account ?? "";
|
||||
return {
|
||||
account,
|
||||
content: raw.content?.trim() ?? fallback?.content ?? "",
|
||||
updatedAt: raw.updatedAt ?? fallback?.updatedAt ?? nowIso(),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeMasterMemoryTags(values: string[] | undefined) {
|
||||
return dedupeStrings(
|
||||
(values ?? [])
|
||||
.map((value) => value.trim())
|
||||
.filter((value) => Boolean(value)),
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeUserMasterMemory(
|
||||
raw: Partial<MasterAgentMemory>,
|
||||
fallback?: MasterAgentMemory,
|
||||
): MasterAgentMemory {
|
||||
const scope = raw.scope ?? fallback?.scope ?? "global";
|
||||
const projectId = scope === "project" ? raw.projectId ?? fallback?.projectId : undefined;
|
||||
return {
|
||||
memoryId: raw.memoryId ?? fallback?.memoryId ?? randomToken("memory"),
|
||||
account: raw.account ?? fallback?.account ?? "",
|
||||
scope,
|
||||
projectId,
|
||||
title: raw.title?.trim() ?? fallback?.title ?? "",
|
||||
content: raw.content?.trim() ?? fallback?.content ?? "",
|
||||
memoryType: raw.memoryType ?? fallback?.memoryType ?? "user_preference",
|
||||
tags: normalizeMasterMemoryTags(raw.tags ?? fallback?.tags ?? []),
|
||||
sourceMessageId: raw.sourceMessageId ?? fallback?.sourceMessageId,
|
||||
createdAt: raw.createdAt ?? fallback?.createdAt ?? nowIso(),
|
||||
updatedAt: raw.updatedAt ?? fallback?.updatedAt ?? nowIso(),
|
||||
lastUsedAt: raw.lastUsedAt ?? fallback?.lastUsedAt,
|
||||
archived: raw.archived ?? fallback?.archived ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeProject(raw: Partial<Project>, fallback?: Project): Project {
|
||||
const base = fallback ?? cloneInitialState().projects[0];
|
||||
const projectId = raw.id ?? base.id;
|
||||
@@ -2745,6 +2853,24 @@ function normalizeState(raw: Partial<BossState> | undefined): BossState {
|
||||
base.userAttachmentStorageConfigs[index % base.userAttachmentStorageConfigs.length],
|
||||
),
|
||||
),
|
||||
masterAgentPromptPolicy: normalizeMasterAgentPromptPolicy(
|
||||
raw.masterAgentPromptPolicy,
|
||||
base.masterAgentPromptPolicy,
|
||||
),
|
||||
userMasterPrompts: ensureArray(raw.userMasterPrompts, base.userMasterPrompts).map(
|
||||
(prompt, index) =>
|
||||
normalizeUserMasterPrompt(
|
||||
prompt,
|
||||
base.userMasterPrompts[index % Math.max(1, base.userMasterPrompts.length)],
|
||||
),
|
||||
),
|
||||
masterAgentMemories: ensureArray(raw.masterAgentMemories, base.masterAgentMemories).map(
|
||||
(memory, index) =>
|
||||
normalizeUserMasterMemory(
|
||||
memory,
|
||||
base.masterAgentMemories[index % Math.max(1, base.masterAgentMemories.length)],
|
||||
),
|
||||
),
|
||||
threadContextSnapshots: ensureArray(raw.threadContextSnapshots, base.threadContextSnapshots).map(
|
||||
(snapshot, index) => ({
|
||||
...base.threadContextSnapshots[index % base.threadContextSnapshots.length],
|
||||
@@ -3410,6 +3536,7 @@ export async function updateProjectAgentControls(
|
||||
payload: {
|
||||
modelOverride?: unknown;
|
||||
reasoningEffortOverride?: unknown;
|
||||
promptOverride?: unknown;
|
||||
},
|
||||
) {
|
||||
if (projectId !== "master-agent") {
|
||||
@@ -3422,12 +3549,18 @@ export async function updateProjectAgentControls(
|
||||
const reasoningEffortInput = Object.prototype.hasOwnProperty.call(payload, "reasoningEffortOverride")
|
||||
? parseReasoningEffortOverride(payload.reasoningEffortOverride)
|
||||
: { kind: "preserve" as const };
|
||||
const promptOverrideInput = Object.prototype.hasOwnProperty.call(payload, "promptOverride")
|
||||
? parseControlTextOverride(payload.promptOverride)
|
||||
: { kind: "preserve" as const };
|
||||
if (modelOverrideInput.kind === "invalid") {
|
||||
throw new Error("INVALID_MODEL_OVERRIDE");
|
||||
}
|
||||
if (reasoningEffortInput.kind === "invalid") {
|
||||
throw new Error("INVALID_REASONING_EFFORT_OVERRIDE");
|
||||
}
|
||||
if (promptOverrideInput.kind === "invalid") {
|
||||
throw new Error("INVALID_PROMPT_OVERRIDE");
|
||||
}
|
||||
|
||||
return mutateStateIfChanged((state) => {
|
||||
const project = state.projects.find((item) => item.id === projectId);
|
||||
@@ -3446,16 +3579,28 @@ export async function updateProjectAgentControls(
|
||||
: reasoningEffortInput.kind === "clear"
|
||||
? undefined
|
||||
: currentControls?.reasoningEffortOverride;
|
||||
const promptOverride =
|
||||
promptOverrideInput.kind === "set"
|
||||
? promptOverrideInput.value
|
||||
: promptOverrideInput.kind === "clear"
|
||||
? undefined
|
||||
: currentControls?.promptOverride;
|
||||
|
||||
const currentModelOverride = currentControls?.modelOverride;
|
||||
const currentReasoningEffortOverride = currentControls?.reasoningEffortOverride;
|
||||
if (currentModelOverride === modelOverride && currentReasoningEffortOverride === reasoningEffortOverride) {
|
||||
const currentPromptOverride = currentControls?.promptOverride;
|
||||
if (
|
||||
currentModelOverride === modelOverride &&
|
||||
currentReasoningEffortOverride === reasoningEffortOverride &&
|
||||
currentPromptOverride === promptOverride
|
||||
) {
|
||||
return { result: currentControls, changed: false };
|
||||
}
|
||||
|
||||
const nextControls = {
|
||||
modelOverride,
|
||||
reasoningEffortOverride,
|
||||
promptOverride,
|
||||
updatedAt: nowIso(),
|
||||
} satisfies ProjectAgentControls;
|
||||
|
||||
@@ -3496,6 +3641,423 @@ export async function upsertAttachmentStorageConfig(config: UserAttachmentStorag
|
||||
});
|
||||
}
|
||||
|
||||
export async function getMasterAgentPromptPolicy() {
|
||||
const state = await readState();
|
||||
return state.masterAgentPromptPolicy ?? null;
|
||||
}
|
||||
|
||||
export async function updateMasterAgentPromptPolicy(input: {
|
||||
globalPrompt: string;
|
||||
updatedBy?: string;
|
||||
}) {
|
||||
const globalPrompt = input.globalPrompt.trim();
|
||||
if (!globalPrompt) {
|
||||
throw new Error("MASTER_AGENT_PROMPT_REQUIRED");
|
||||
}
|
||||
|
||||
return mutateState((state) => {
|
||||
const policy: MasterAgentPromptPolicy = {
|
||||
globalPrompt,
|
||||
updatedBy: input.updatedBy?.trim() || undefined,
|
||||
updatedAt: nowIso(),
|
||||
};
|
||||
state.masterAgentPromptPolicy = policy;
|
||||
return policy;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getUserMasterPrompt(account: string) {
|
||||
const state = await readState();
|
||||
return state.userMasterPrompts.find((item) => item.account === account) ?? null;
|
||||
}
|
||||
|
||||
export async function updateUserMasterPrompt(account: string, content: string) {
|
||||
const trimmedContent = content.trim();
|
||||
if (!trimmedContent) {
|
||||
throw new Error("USER_MASTER_PROMPT_REQUIRED");
|
||||
}
|
||||
|
||||
return mutateState((state) => {
|
||||
const next: UserMasterPrompt = {
|
||||
account,
|
||||
content: trimmedContent,
|
||||
updatedAt: nowIso(),
|
||||
};
|
||||
const existing = state.userMasterPrompts.find((item) => item.account === account);
|
||||
if (existing) {
|
||||
Object.assign(existing, next);
|
||||
} else {
|
||||
state.userMasterPrompts.unshift(next);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
export async function clearUserMasterPrompt(account: string) {
|
||||
return mutateState((state) => {
|
||||
const before = state.userMasterPrompts.length;
|
||||
state.userMasterPrompts = state.userMasterPrompts.filter((item) => item.account !== account);
|
||||
return { cleared: before !== state.userMasterPrompts.length };
|
||||
});
|
||||
}
|
||||
|
||||
export async function listUserMasterMemories(
|
||||
account: string,
|
||||
options?: { includeArchived?: boolean; scope?: MasterMemoryScope; projectId?: string },
|
||||
) {
|
||||
const state = await readState();
|
||||
const includeArchived = options?.includeArchived ?? false;
|
||||
return [...state.masterAgentMemories]
|
||||
.filter((memory) => {
|
||||
if (memory.account !== account) return false;
|
||||
if (!includeArchived && memory.archived) return false;
|
||||
if (options?.scope && memory.scope !== options.scope) return false;
|
||||
if (options?.projectId && memory.projectId !== options.projectId) return false;
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const timeDiff =
|
||||
messageTimeValue(b.lastUsedAt ?? b.updatedAt ?? b.createdAt) -
|
||||
messageTimeValue(a.lastUsedAt ?? a.updatedAt ?? a.createdAt);
|
||||
if (timeDiff !== 0) return timeDiff;
|
||||
return b.memoryId.localeCompare(a.memoryId);
|
||||
});
|
||||
}
|
||||
|
||||
export async function createUserMasterMemory(input: {
|
||||
account: string;
|
||||
scope: MasterMemoryScope;
|
||||
projectId?: string;
|
||||
title: string;
|
||||
content: string;
|
||||
memoryType: MasterMemoryType;
|
||||
tags?: string[];
|
||||
sourceMessageId?: string;
|
||||
}) {
|
||||
const title = input.title.trim();
|
||||
const content = input.content.trim();
|
||||
if (!title) {
|
||||
throw new Error("USER_MASTER_MEMORY_TITLE_REQUIRED");
|
||||
}
|
||||
if (!content) {
|
||||
throw new Error("USER_MASTER_MEMORY_CONTENT_REQUIRED");
|
||||
}
|
||||
if (input.scope === "project" && !input.projectId?.trim()) {
|
||||
throw new Error("USER_MASTER_MEMORY_PROJECT_ID_REQUIRED");
|
||||
}
|
||||
|
||||
return mutateState((state) => {
|
||||
const now = nowIso();
|
||||
const memory: MasterAgentMemory = {
|
||||
memoryId: randomToken("memory"),
|
||||
account: input.account,
|
||||
scope: input.scope,
|
||||
projectId: input.scope === "project" ? input.projectId?.trim() : undefined,
|
||||
title,
|
||||
content,
|
||||
memoryType: input.memoryType,
|
||||
tags: normalizeMasterMemoryTags(input.tags),
|
||||
sourceMessageId: input.sourceMessageId,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
lastUsedAt: now,
|
||||
archived: false,
|
||||
};
|
||||
state.masterAgentMemories.unshift(memory);
|
||||
return memory;
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateUserMasterMemory(
|
||||
memoryId: string,
|
||||
account: string,
|
||||
patch: Partial<
|
||||
Pick<
|
||||
MasterAgentMemory,
|
||||
"scope" | "projectId" | "title" | "content" | "memoryType" | "tags" | "sourceMessageId" | "lastUsedAt"
|
||||
>
|
||||
>,
|
||||
) {
|
||||
return mutateState((state) => {
|
||||
const memory = state.masterAgentMemories.find(
|
||||
(item) => item.memoryId === memoryId && item.account === account,
|
||||
);
|
||||
if (!memory) {
|
||||
throw new Error("USER_MASTER_MEMORY_NOT_FOUND");
|
||||
}
|
||||
|
||||
if (patch.scope) {
|
||||
memory.scope = patch.scope;
|
||||
}
|
||||
if (memory.scope === "project" && patch.projectId !== undefined) {
|
||||
memory.projectId = patch.projectId.trim() || undefined;
|
||||
}
|
||||
if (memory.scope !== "project") {
|
||||
memory.projectId = undefined;
|
||||
}
|
||||
if (patch.title !== undefined) {
|
||||
const title = patch.title.trim();
|
||||
if (!title) throw new Error("USER_MASTER_MEMORY_TITLE_REQUIRED");
|
||||
memory.title = title;
|
||||
}
|
||||
if (patch.content !== undefined) {
|
||||
const content = patch.content.trim();
|
||||
if (!content) throw new Error("USER_MASTER_MEMORY_CONTENT_REQUIRED");
|
||||
memory.content = content;
|
||||
}
|
||||
if (patch.memoryType) {
|
||||
memory.memoryType = patch.memoryType;
|
||||
}
|
||||
if (patch.tags) {
|
||||
memory.tags = normalizeMasterMemoryTags(patch.tags);
|
||||
}
|
||||
if (patch.sourceMessageId !== undefined) {
|
||||
memory.sourceMessageId = patch.sourceMessageId;
|
||||
}
|
||||
if (patch.lastUsedAt !== undefined) {
|
||||
memory.lastUsedAt = patch.lastUsedAt;
|
||||
}
|
||||
memory.updatedAt = nowIso();
|
||||
return memory;
|
||||
});
|
||||
}
|
||||
|
||||
export async function archiveUserMasterMemory(memoryId: string, account: string) {
|
||||
return mutateState((state) => {
|
||||
const memory = state.masterAgentMemories.find(
|
||||
(item) => item.memoryId === memoryId && item.account === account,
|
||||
);
|
||||
if (!memory) {
|
||||
throw new Error("USER_MASTER_MEMORY_NOT_FOUND");
|
||||
}
|
||||
|
||||
memory.archived = true;
|
||||
memory.updatedAt = nowIso();
|
||||
return memory;
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeAutoMemoryText(value: string | undefined) {
|
||||
return (value ?? "")
|
||||
.replace(/\s+/g, " ")
|
||||
.replace(/[。;;!!]+$/g, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function inferAutoMemoryType(text: string): MasterMemoryType | null {
|
||||
if (!text.trim()) return null;
|
||||
if (/(微信|wechat|中文回复|中文沟通|UI风格|交互风格|偏好|习惯|默认)/i.test(text)) {
|
||||
return "user_preference";
|
||||
}
|
||||
if (/(规则|约束|优先|先.*再|必须|不要|需要|流程|逻辑)/i.test(text)) {
|
||||
return "workflow_rule";
|
||||
}
|
||||
if (/(阻塞|卡住|失败|异常|报错|问题|bug|未打通)/i.test(text)) {
|
||||
return "blocking_issue";
|
||||
}
|
||||
if (/(风险|隐患|告警)/i.test(text)) {
|
||||
return "risk";
|
||||
}
|
||||
if (/(决定|改成|采用|统一|确定|方案)/i.test(text)) {
|
||||
return "decision";
|
||||
}
|
||||
if (/(调研|研究|结论)/i.test(text)) {
|
||||
return "research_note";
|
||||
}
|
||||
if (/(进度|完成|已接通|已打通|上线|当前.*状态|回归|发布)/i.test(text)) {
|
||||
return "project_progress";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function inferProjectAutoMemoryType(text: string): Exclude<MasterMemoryType, "user_preference"> | null {
|
||||
if (!text.trim()) return null;
|
||||
if (/(阻塞|卡住|失败|异常|报错|问题|bug|未打通)/i.test(text)) {
|
||||
return "blocking_issue";
|
||||
}
|
||||
if (/(风险|隐患|告警)/i.test(text)) {
|
||||
return "risk";
|
||||
}
|
||||
if (/(决定|改成|采用|统一|确定|方案)/i.test(text)) {
|
||||
return "decision";
|
||||
}
|
||||
if (/(调研|研究|结论)/i.test(text)) {
|
||||
return "research_note";
|
||||
}
|
||||
if (/(进度|完成|已接通|已打通|上线|当前.*状态|回归|发布)/i.test(text)) {
|
||||
return "project_progress";
|
||||
}
|
||||
if (/(规则|约束|优先|先.*再|必须|不要|需要|流程|逻辑)/i.test(text)) {
|
||||
return "workflow_rule";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildAutoMemoryTitle(memoryType: MasterMemoryType, label?: string) {
|
||||
const typeLabel =
|
||||
memoryType === "user_preference"
|
||||
? "偏好"
|
||||
: memoryType === "workflow_rule"
|
||||
? "工作规则"
|
||||
: memoryType === "blocking_issue"
|
||||
? "阻塞"
|
||||
: memoryType === "risk"
|
||||
? "风险"
|
||||
: memoryType === "decision"
|
||||
? "决策"
|
||||
: memoryType === "research_note"
|
||||
? "调研结论"
|
||||
: "项目进度";
|
||||
return label ? `${label} · ${typeLabel}` : typeLabel;
|
||||
}
|
||||
|
||||
function detectReferencedProjectForMemory(state: BossState, text: string) {
|
||||
const lowered = text.toLowerCase();
|
||||
const candidates = state.projects
|
||||
.filter((project) => project.id !== "master-agent")
|
||||
.flatMap((project) => {
|
||||
const rawAliases = [
|
||||
project.id,
|
||||
project.name,
|
||||
project.threadMeta.folderName,
|
||||
project.threadMeta.threadDisplayName,
|
||||
]
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
const aliases = Array.from(
|
||||
new Set(
|
||||
rawAliases.flatMap((alias) => {
|
||||
const normalized = alias.trim();
|
||||
if (!normalized) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const tokenCandidates = normalized
|
||||
.split(/[\s\-_/]+/)
|
||||
.map((token) => token.trim())
|
||||
.filter((token) => token.length >= 3);
|
||||
|
||||
return [normalized, ...tokenCandidates];
|
||||
}),
|
||||
),
|
||||
);
|
||||
return aliases.map((alias) => ({
|
||||
projectId: project.id,
|
||||
projectName: project.name,
|
||||
alias,
|
||||
}));
|
||||
})
|
||||
.sort((left, right) => right.alias.length - left.alias.length);
|
||||
|
||||
return candidates.find((candidate) => lowered.includes(candidate.alias.toLowerCase())) ?? null;
|
||||
}
|
||||
|
||||
function upsertAutoMasterMemoryInState(
|
||||
state: BossState,
|
||||
input: {
|
||||
account: string;
|
||||
scope: MasterMemoryScope;
|
||||
projectId?: string;
|
||||
title: string;
|
||||
content: string;
|
||||
memoryType: MasterMemoryType;
|
||||
tags: string[];
|
||||
sourceMessageId?: string;
|
||||
},
|
||||
) {
|
||||
const now = nowIso();
|
||||
const existing = state.masterAgentMemories.find(
|
||||
(memory) =>
|
||||
memory.account === input.account &&
|
||||
memory.scope === input.scope &&
|
||||
(memory.projectId ?? undefined) === (input.projectId ?? undefined) &&
|
||||
memory.title === input.title,
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
existing.content = input.content;
|
||||
existing.memoryType = input.memoryType;
|
||||
existing.tags = normalizeMasterMemoryTags(input.tags);
|
||||
existing.sourceMessageId = input.sourceMessageId ?? existing.sourceMessageId;
|
||||
existing.archived = false;
|
||||
existing.updatedAt = now;
|
||||
existing.lastUsedAt = now;
|
||||
return existing;
|
||||
}
|
||||
|
||||
const memory: MasterAgentMemory = {
|
||||
memoryId: randomToken("memory"),
|
||||
account: input.account,
|
||||
scope: input.scope,
|
||||
projectId: input.scope === "project" ? input.projectId : undefined,
|
||||
title: input.title,
|
||||
content: input.content,
|
||||
memoryType: input.memoryType,
|
||||
tags: normalizeMasterMemoryTags(input.tags),
|
||||
sourceMessageId: input.sourceMessageId,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
lastUsedAt: now,
|
||||
archived: false,
|
||||
};
|
||||
state.masterAgentMemories.unshift(memory);
|
||||
return memory;
|
||||
}
|
||||
|
||||
function autoCaptureMasterAgentMemoriesInState(
|
||||
state: BossState,
|
||||
input: {
|
||||
account: string;
|
||||
requestText: string;
|
||||
replyText: string;
|
||||
sourceMessageId?: string;
|
||||
},
|
||||
) {
|
||||
const requestText = normalizeAutoMemoryText(input.requestText);
|
||||
const replyText = normalizeAutoMemoryText(input.replyText);
|
||||
if (!requestText && !replyText) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const createdOrUpdated: MasterAgentMemory[] = [];
|
||||
const combined = [requestText, replyText].filter(Boolean).join(" ");
|
||||
const preferenceType = inferAutoMemoryType(requestText);
|
||||
|
||||
if (preferenceType === "user_preference" || preferenceType === "workflow_rule") {
|
||||
createdOrUpdated.push(
|
||||
upsertAutoMasterMemoryInState(state, {
|
||||
account: input.account,
|
||||
scope: "global",
|
||||
title: buildAutoMemoryTitle(preferenceType),
|
||||
content: requestText,
|
||||
memoryType: preferenceType,
|
||||
tags: preferenceType === "user_preference" ? ["用户偏好"] : ["工作方式"],
|
||||
sourceMessageId: input.sourceMessageId,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const referencedProject = detectReferencedProjectForMemory(state, combined);
|
||||
const projectType = inferProjectAutoMemoryType(replyText) ?? inferProjectAutoMemoryType(combined);
|
||||
if (referencedProject && projectType) {
|
||||
createdOrUpdated.push(
|
||||
upsertAutoMasterMemoryInState(state, {
|
||||
account: input.account,
|
||||
scope: "project",
|
||||
projectId: referencedProject.projectId,
|
||||
title: buildAutoMemoryTitle(projectType, referencedProject.projectName),
|
||||
content: replyText || requestText,
|
||||
memoryType: projectType,
|
||||
tags: [referencedProject.projectName, referencedProject.alias],
|
||||
sourceMessageId: input.sourceMessageId,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return createdOrUpdated;
|
||||
}
|
||||
|
||||
function preferredDeviceForAccount(
|
||||
state: BossState,
|
||||
account: string,
|
||||
@@ -5078,6 +5640,12 @@ export async function completeMasterAgentTask(payload: {
|
||||
body: task.replyBody,
|
||||
kind: "text",
|
||||
});
|
||||
autoCaptureMasterAgentMemoriesInState(state, {
|
||||
account: task.requestedByAccount,
|
||||
requestText: task.requestText,
|
||||
replyText: task.replyBody,
|
||||
sourceMessageId: task.requestMessageId,
|
||||
});
|
||||
}
|
||||
} else if (!attachmentProjectId && payload.status === "failed") {
|
||||
const isThreadConversationReply =
|
||||
|
||||
Reference in New Issue
Block a user