refactor: keep imported project understanding sync automatic

This commit is contained in:
kris
2026-04-04 08:43:53 +08:00
parent 432cf97541
commit cf57b5058f
9 changed files with 25 additions and 502 deletions

View File

@@ -677,7 +677,7 @@ export interface MasterAgentTask {
deviceImportCandidateId?: string;
deviceImportCandidateFolderName?: string;
projectUnderstandingTargetProjectId?: string;
projectUnderstandingReason?: "heartbeat_activity" | "thread_reply" | "manual_device_sync";
projectUnderstandingReason?: "heartbeat_activity" | "thread_reply";
status: MasterAgentTaskStatus;
requestedAt: string;
claimedAt?: string;
@@ -2995,9 +2995,7 @@ function normalizeState(raw: Partial<BossState> | undefined): BossState {
deviceImportCandidateFolderName: task.deviceImportCandidateFolderName,
projectUnderstandingTargetProjectId: task.projectUnderstandingTargetProjectId,
projectUnderstandingReason:
task.projectUnderstandingReason === "heartbeat_activity" ||
task.projectUnderstandingReason === "thread_reply" ||
task.projectUnderstandingReason === "manual_device_sync"
task.projectUnderstandingReason === "heartbeat_activity" || task.projectUnderstandingReason === "thread_reply"
? task.projectUnderstandingReason
: undefined,
status: task.status ?? "queued",
@@ -5207,7 +5205,7 @@ export async function queueMasterAgentTask(payload: {
deviceImportCandidateId?: string;
deviceImportCandidateFolderName?: string;
projectUnderstandingTargetProjectId?: string;
projectUnderstandingReason?: "heartbeat_activity" | "thread_reply" | "manual_device_sync";
projectUnderstandingReason?: "heartbeat_activity" | "thread_reply";
}) {
const task = await mutateState((state) => {
const task: MasterAgentTask = {
@@ -7356,12 +7354,7 @@ function applyProjectUnderstandingSnapshotInState(
return snapshot;
}
function shouldQueueProjectUnderstandingSync(
project: Project,
observedActivityAt: string,
state: BossState,
options?: { force?: boolean },
) {
function shouldQueueProjectUnderstandingSync(project: Project, observedActivityAt: string, state: BossState) {
if (!isDispatchableThreadProject(project)) {
return false;
}
@@ -7369,16 +7362,14 @@ function shouldQueueProjectUnderstandingSync(
if (!Number.isFinite(observedTs)) {
return false;
}
if (!options?.force) {
const latestWatermark = Date.parse(
project.threadMeta.lastProjectUnderstandingRequestedAt ??
project.threadMeta.lastProjectUnderstandingSyncedAt ??
project.projectUnderstanding?.updatedAt ??
"1970-01-01T00:00:00.000Z",
);
if (Number.isFinite(latestWatermark) && observedTs <= latestWatermark) {
return false;
}
const latestWatermark = Date.parse(
project.threadMeta.lastProjectUnderstandingRequestedAt ??
project.threadMeta.lastProjectUnderstandingSyncedAt ??
project.projectUnderstanding?.updatedAt ??
"1970-01-01T00:00:00.000Z",
);
if (Number.isFinite(latestWatermark) && observedTs <= latestWatermark) {
return false;
}
return !state.masterAgentTasks.some(
(task) =>
@@ -7389,22 +7380,13 @@ function shouldQueueProjectUnderstandingSync(
);
}
function buildProjectUnderstandingSyncPrompt(
project: Project,
reason: "heartbeat_activity" | "thread_reply" | "manual_device_sync",
) {
function buildProjectUnderstandingSyncPrompt(project: Project, reason: "heartbeat_activity" | "thread_reply") {
return [
"你正在向主 Agent 同步当前项目状态。",
`项目名称:${project.name}`,
`线程名称:${project.threadMeta.threadDisplayName}`,
`文件夹:${project.threadMeta.folderName}`,
`同步原因:${
reason === "heartbeat_activity"
? "检测到线程有新活动"
: reason === "thread_reply"
? "线程刚刚产生了新的执行结果"
: "用户主动要求同步当前设备上的活跃项目理解"
}`,
`同步原因:${reason === "heartbeat_activity" ? "检测到线程有新活动" : "线程刚刚产生了新的执行结果"}`,
"",
"只输出 JSON不要输出解释性文字或 Markdown。",
"JSON 结构固定为:",
@@ -7420,17 +7402,14 @@ function buildProjectUnderstandingSyncPrompt(
async function queueProjectUnderstandingSyncTask(input: {
projectId: string;
observedActivityAt: string;
reason: "heartbeat_activity" | "thread_reply" | "manual_device_sync";
force?: boolean;
requestedByAccount?: string;
reason: "heartbeat_activity" | "thread_reply";
}) {
const state = await readState();
const project = state.projects.find((item) => item.id === input.projectId);
if (!project || !shouldQueueProjectUnderstandingSync(project, input.observedActivityAt, state, { force: input.force })) {
if (!project || !shouldQueueProjectUnderstandingSync(project, input.observedActivityAt, state)) {
return null;
}
const requestedByAccount =
input.requestedByAccount?.trim() || state.user.account || project.deviceIds[0] || "17600003315";
const requestedByAccount = state.user.account || project.deviceIds[0] || "17600003315";
const task = await queueMasterAgentTask({
projectId: "master-agent",
taskType: "conversation_reply",
@@ -7463,82 +7442,6 @@ async function queueProjectUnderstandingSyncTask(input: {
return task;
}
export async function syncDeviceProjectUnderstanding(input: {
deviceId: string;
requestedByAccount: string;
limit?: number;
}) {
const state = await readState();
const device = state.devices.find((item) => item.id === input.deviceId);
if (!device) {
throw new Error("DEVICE_NOT_FOUND");
}
const activeProjects = state.projects
.filter(
(project) =>
!project.isGroup &&
project.deviceIds.includes(input.deviceId) &&
isDispatchableThreadProject(project) &&
Boolean(project.threadMeta.codexThreadRef?.trim()),
)
.sort((left, right) =>
String(
right.threadMeta.lastObservedCodexActivityAt ??
right.projectUnderstanding?.updatedAt ??
right.lastMessageAt,
).localeCompare(
String(
left.threadMeta.lastObservedCodexActivityAt ??
left.projectUnderstanding?.updatedAt ??
left.lastMessageAt,
),
),
)
.slice(0, Math.max(1, input.limit ?? 3));
const queuedTasks = [];
for (const project of activeProjects) {
const observedActivityAt =
project.threadMeta.lastObservedCodexActivityAt ??
project.projectUnderstanding?.updatedAt ??
project.lastMessageAt ??
nowIso();
const task = await queueProjectUnderstandingSyncTask({
projectId: project.id,
observedActivityAt,
reason: "manual_device_sync",
force: true,
requestedByAccount: input.requestedByAccount,
});
if (task) {
queuedTasks.push({
taskId: task.taskId,
projectId: project.id,
projectName: project.name,
threadDisplayName: project.threadMeta.threadDisplayName,
});
}
}
return {
ok: true as const,
deviceId: device.id,
deviceName: device.name,
queuedTasks,
activeProjects: activeProjects.map((project) => ({
projectId: project.id,
projectName: project.name,
threadDisplayName: project.threadMeta.threadDisplayName,
lastObservedCodexActivityAt:
project.threadMeta.lastObservedCodexActivityAt ??
project.projectUnderstanding?.updatedAt ??
project.lastMessageAt,
currentProgress: project.projectUnderstanding?.currentProgress ?? "",
})),
};
}
export async function previewDeviceImportResolution(input: { deviceId: string }) {
const state = await readState();
const draft = state.deviceImportDrafts.find((item) => item.deviceId === input.deviceId);