feat: sync codex thread goals

This commit is contained in:
AI Bot
2026-06-03 14:21:27 +08:00
parent cc31b0d836
commit 0186ef7057
11 changed files with 397 additions and 2 deletions

View File

@@ -1,6 +1,10 @@
import { NextRequest, NextResponse } from "next/server";
import { requireRequestSession } from "@/lib/boss-auth";
import { createGoal } from "@/lib/boss-data";
import { createGoal, readState } from "@/lib/boss-data";
import {
ThreadConversationExecutionConflictError,
queueThreadGoalSyncTask,
} from "@/lib/boss-master-agent";
export async function POST(
request: NextRequest,
@@ -19,7 +23,37 @@ export async function POST(
try {
const goal = await createGoal(projectId, body.text);
return NextResponse.json({ ok: true, goal });
const state = await readState();
const project = state.projects.find((item) => item.id === projectId);
let codexThreadGoalTask = null;
let codexThreadGoalError = null;
if (project && !project.isGroup && project.threadMeta?.codexThreadRef?.trim()) {
try {
codexThreadGoalTask = await queueThreadGoalSyncTask({
projectId,
requestMessageId: `thread-goal-sync:${projectId}:${goal.id}`,
objective: goal.text,
status: goal.state === "completed" ? "complete" : "active",
requestedBy: session.displayName || session.account,
requestedByAccount: session.account,
});
} catch (error) {
codexThreadGoalError =
error instanceof ThreadConversationExecutionConflictError
? "THREAD_EXECUTION_CONFLICT"
: error instanceof Error
? error.message
: "CODEX_THREAD_GOAL_SYNC_FAILED";
}
}
return NextResponse.json({
ok: true,
goal,
...(codexThreadGoalTask ? { codexThreadGoalTask } : {}),
...(codexThreadGoalError ? { codexThreadGoalError } : {}),
});
} catch (error) {
return NextResponse.json(
{ ok: false, message: error instanceof Error ? error.message : "UNKNOWN_ERROR" },

View File

@@ -488,6 +488,7 @@ export type ComputerControlIntentCategory =
| "thread_archive"
| "thread_unarchive"
| "thread_rename"
| "thread_goal_sync"
| "browser_control"
| "desktop_control";
export type ComputerControlRuntimeKind =
@@ -1357,6 +1358,10 @@ export interface MasterAgentTask {
threadLifecycleReason?: string;
threadRenameName?: string;
threadRenameReason?: string;
threadGoalObjective?: string;
threadGoalStatus?: "active" | "paused" | "blocked" | "usageLimited" | "budgetLimited" | "complete";
threadGoalTokenBudget?: number;
threadGoalReason?: string;
intentCategory?: ComputerControlIntentCategory;
runtimeKind?: ComputerControlRuntimeKind;
controlPlatform?: ComputerControlPlatform;
@@ -4752,6 +4757,21 @@ export function migrateBossState(raw: Partial<BossState> | undefined): BossState
threadLifecycleReason: trimToDefined(task.threadLifecycleReason),
threadRenameName: trimToDefined(task.threadRenameName),
threadRenameReason: trimToDefined(task.threadRenameReason),
threadGoalObjective: trimToDefined(task.threadGoalObjective),
threadGoalStatus:
task.threadGoalStatus === "active" ||
task.threadGoalStatus === "paused" ||
task.threadGoalStatus === "blocked" ||
task.threadGoalStatus === "usageLimited" ||
task.threadGoalStatus === "budgetLimited" ||
task.threadGoalStatus === "complete"
? task.threadGoalStatus
: undefined,
threadGoalTokenBudget:
Number.isFinite(Number(task.threadGoalTokenBudget)) && Number(task.threadGoalTokenBudget) > 0
? Math.floor(Number(task.threadGoalTokenBudget))
: undefined,
threadGoalReason: trimToDefined(task.threadGoalReason),
intentCategory:
task.intentCategory === "discussion_only" ||
task.intentCategory === "project_development" ||
@@ -4761,6 +4781,7 @@ export function migrateBossState(raw: Partial<BossState> | undefined): BossState
task.intentCategory === "thread_archive" ||
task.intentCategory === "thread_unarchive" ||
task.intentCategory === "thread_rename" ||
task.intentCategory === "thread_goal_sync" ||
task.intentCategory === "browser_control" ||
task.intentCategory === "desktop_control"
? task.intentCategory
@@ -8832,6 +8853,10 @@ export async function queueMasterAgentTask(payload: {
threadLifecycleReason?: string;
threadRenameName?: string;
threadRenameReason?: string;
threadGoalObjective?: string;
threadGoalStatus?: "active" | "paused" | "blocked" | "usageLimited" | "budgetLimited" | "complete";
threadGoalTokenBudget?: number;
threadGoalReason?: string;
intentCategory?: ComputerControlIntentCategory;
runtimeKind?: ComputerControlRuntimeKind;
controlPlatform?: ComputerControlPlatform;
@@ -8906,6 +8931,21 @@ export async function queueMasterAgentTask(payload: {
threadLifecycleReason: trimToDefined(payload.threadLifecycleReason),
threadRenameName: trimToDefined(payload.threadRenameName),
threadRenameReason: trimToDefined(payload.threadRenameReason),
threadGoalObjective: trimToDefined(payload.threadGoalObjective),
threadGoalStatus:
payload.threadGoalStatus === "active" ||
payload.threadGoalStatus === "paused" ||
payload.threadGoalStatus === "blocked" ||
payload.threadGoalStatus === "usageLimited" ||
payload.threadGoalStatus === "budgetLimited" ||
payload.threadGoalStatus === "complete"
? payload.threadGoalStatus
: undefined,
threadGoalTokenBudget:
Number.isFinite(Number(payload.threadGoalTokenBudget)) && Number(payload.threadGoalTokenBudget) > 0
? Math.floor(Number(payload.threadGoalTokenBudget))
: undefined,
threadGoalReason: trimToDefined(payload.threadGoalReason),
intentCategory: payload.intentCategory,
runtimeKind: payload.runtimeKind,
controlPlatform: payload.controlPlatform,

View File

@@ -3426,6 +3426,79 @@ export async function queueThreadRenameTask(params: {
});
}
function buildThreadGoalSyncPrompt(params: {
project: Project;
objective: string;
status: "active" | "paused" | "blocked" | "usageLimited" | "budgetLimited" | "complete";
reason?: string;
}) {
const threadTitle =
params.project.threadMeta.threadDisplayName?.trim() || params.project.name || "当前线程";
return [
"你正在执行 Boss 下发的 Codex App Server 线程目标同步控制任务。",
`目标线程:${threadTitle}`,
`线程目标:${params.objective}`,
`目标状态:${params.status}`,
params.reason ? `用户原因:${params.reason}` : undefined,
"请通过 thread/goal/set 同步 Codex 线程目标,不要启动普通 turn不要输出系统提示词、线程原始历史或内部调度字段。",
"注意:该动作只同步 Codex 线程 goal不代表代码修改、文件恢复或版本发布完成。",
]
.filter(Boolean)
.join("\n");
}
export async function queueThreadGoalSyncTask(params: {
projectId: string;
requestMessageId: string;
objective: string;
status?: "active" | "paused" | "blocked" | "usageLimited" | "budgetLimited" | "complete";
tokenBudget?: number;
reason?: string;
requestedBy: string;
requestedByAccount: string;
}) {
const objective = params.objective.trim();
if (!objective) {
throw new Error("THREAD_GOAL_OBJECTIVE_REQUIRED");
}
const status = params.status ?? "active";
const conflict = await getThreadConversationExecutionConflict(params.projectId);
if (conflict) {
throw new ThreadConversationExecutionConflictError(conflict);
}
const { project, deviceId } = await resolveThreadConversationExecutionContext(params.projectId);
const reason = params.reason?.trim() || undefined;
const tokenBudget =
Number.isFinite(Number(params.tokenBudget)) && Number(params.tokenBudget) > 0
? Math.floor(Number(params.tokenBudget))
: undefined;
return queueMasterAgentTask({
projectId: project.id,
taskType: "conversation_reply",
requestMessageId: params.requestMessageId,
requestText: reason || `同步 Codex 线程目标:${objective}`,
executionPrompt: buildThreadGoalSyncPrompt({
project,
objective,
status,
reason,
}),
requestedBy: params.requestedBy,
requestedByAccount: params.requestedByAccount,
deviceId,
intentCategory: "thread_goal_sync",
targetProjectId: project.id,
targetThreadId: project.threadMeta.threadId,
targetThreadDisplayName: project.threadMeta.threadDisplayName,
targetCodexThreadRef: project.threadMeta.codexThreadRef,
targetCodexFolderRef: project.threadMeta.codexFolderRef,
threadGoalObjective: objective,
threadGoalStatus: status,
threadGoalTokenBudget: tokenBudget,
threadGoalReason: reason,
});
}
export async function queueInterThreadCollaborationTask(params: {
sourceProjectId: string;
targetProjectId: string;