feat: sync codex thread metadata
This commit is contained in:
@@ -489,6 +489,7 @@ export type ComputerControlIntentCategory =
|
||||
| "thread_unarchive"
|
||||
| "thread_rename"
|
||||
| "thread_goal_sync"
|
||||
| "thread_metadata_sync"
|
||||
| "browser_control"
|
||||
| "desktop_control";
|
||||
export type ComputerControlRuntimeKind =
|
||||
@@ -684,6 +685,12 @@ export interface ThreadConversationMeta {
|
||||
codexFolderRef?: string;
|
||||
}
|
||||
|
||||
export interface ThreadMetadataGitInfoPatch {
|
||||
sha?: string | null;
|
||||
branch?: string | null;
|
||||
originUrl?: string | null;
|
||||
}
|
||||
|
||||
export interface GroupConversationMember {
|
||||
projectId: string;
|
||||
deviceId: string;
|
||||
@@ -1362,6 +1369,8 @@ export interface MasterAgentTask {
|
||||
threadGoalStatus?: "active" | "paused" | "blocked" | "usageLimited" | "budgetLimited" | "complete";
|
||||
threadGoalTokenBudget?: number;
|
||||
threadGoalReason?: string;
|
||||
threadMetadataGitInfo?: ThreadMetadataGitInfoPatch;
|
||||
threadMetadataReason?: string;
|
||||
intentCategory?: ComputerControlIntentCategory;
|
||||
runtimeKind?: ComputerControlRuntimeKind;
|
||||
controlPlatform?: ComputerControlPlatform;
|
||||
@@ -2489,6 +2498,30 @@ function trimToDefined(value?: string) {
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function normalizeThreadMetadataGitInfoPatch(value: unknown): ThreadMetadataGitInfoPatch | undefined {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const input = value as {
|
||||
sha?: unknown;
|
||||
branch?: unknown;
|
||||
originUrl?: unknown;
|
||||
};
|
||||
const patch: ThreadMetadataGitInfoPatch = {};
|
||||
for (const key of ["sha", "branch", "originUrl"] as const) {
|
||||
const field = input[key];
|
||||
if (field === null) {
|
||||
patch[key] = null;
|
||||
} else if (typeof field === "string") {
|
||||
const trimmed = field.trim();
|
||||
if (trimmed) {
|
||||
patch[key] = trimmed;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(patch).length > 0 ? patch : undefined;
|
||||
}
|
||||
|
||||
function normalizeAuditSnapshot(value: unknown): PermissionAuditSnapshot | undefined {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return undefined;
|
||||
@@ -4772,6 +4805,8 @@ export function migrateBossState(raw: Partial<BossState> | undefined): BossState
|
||||
? Math.floor(Number(task.threadGoalTokenBudget))
|
||||
: undefined,
|
||||
threadGoalReason: trimToDefined(task.threadGoalReason),
|
||||
threadMetadataGitInfo: normalizeThreadMetadataGitInfoPatch(task.threadMetadataGitInfo),
|
||||
threadMetadataReason: trimToDefined(task.threadMetadataReason),
|
||||
intentCategory:
|
||||
task.intentCategory === "discussion_only" ||
|
||||
task.intentCategory === "project_development" ||
|
||||
@@ -4782,6 +4817,7 @@ export function migrateBossState(raw: Partial<BossState> | undefined): BossState
|
||||
task.intentCategory === "thread_unarchive" ||
|
||||
task.intentCategory === "thread_rename" ||
|
||||
task.intentCategory === "thread_goal_sync" ||
|
||||
task.intentCategory === "thread_metadata_sync" ||
|
||||
task.intentCategory === "browser_control" ||
|
||||
task.intentCategory === "desktop_control"
|
||||
? task.intentCategory
|
||||
@@ -8857,6 +8893,8 @@ export async function queueMasterAgentTask(payload: {
|
||||
threadGoalStatus?: "active" | "paused" | "blocked" | "usageLimited" | "budgetLimited" | "complete";
|
||||
threadGoalTokenBudget?: number;
|
||||
threadGoalReason?: string;
|
||||
threadMetadataGitInfo?: ThreadMetadataGitInfoPatch;
|
||||
threadMetadataReason?: string;
|
||||
intentCategory?: ComputerControlIntentCategory;
|
||||
runtimeKind?: ComputerControlRuntimeKind;
|
||||
controlPlatform?: ComputerControlPlatform;
|
||||
@@ -8946,6 +8984,8 @@ export async function queueMasterAgentTask(payload: {
|
||||
? Math.floor(Number(payload.threadGoalTokenBudget))
|
||||
: undefined,
|
||||
threadGoalReason: trimToDefined(payload.threadGoalReason),
|
||||
threadMetadataGitInfo: normalizeThreadMetadataGitInfoPatch(payload.threadMetadataGitInfo),
|
||||
threadMetadataReason: trimToDefined(payload.threadMetadataReason),
|
||||
intentCategory: payload.intentCategory,
|
||||
runtimeKind: payload.runtimeKind,
|
||||
controlPlatform: payload.controlPlatform,
|
||||
|
||||
@@ -32,6 +32,7 @@ import type {
|
||||
ProjectExecutionPolicy,
|
||||
ProjectAgentControls,
|
||||
ReasoningEffort,
|
||||
ThreadMetadataGitInfoPatch,
|
||||
} from "@/lib/boss-data";
|
||||
import type { ThreadConversationExecutionConflict } from "@/lib/thread-execution-conflict";
|
||||
import {
|
||||
@@ -3499,6 +3500,93 @@ export async function queueThreadGoalSyncTask(params: {
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeThreadMetadataGitInfoPatch(value: ThreadMetadataGitInfoPatch | undefined) {
|
||||
if (!value || typeof value !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const patch: ThreadMetadataGitInfoPatch = {};
|
||||
for (const key of ["sha", "branch", "originUrl"] as const) {
|
||||
const field = value[key];
|
||||
if (field === null) {
|
||||
patch[key] = null;
|
||||
} else if (typeof field === "string") {
|
||||
const trimmed = field.trim();
|
||||
if (trimmed) {
|
||||
patch[key] = trimmed;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(patch).length > 0 ? patch : undefined;
|
||||
}
|
||||
|
||||
function buildThreadMetadataSyncPrompt(params: {
|
||||
project: Project;
|
||||
gitInfo: ThreadMetadataGitInfoPatch;
|
||||
reason?: string;
|
||||
}) {
|
||||
const threadTitle =
|
||||
params.project.threadMeta.threadDisplayName?.trim() || params.project.name || "当前线程";
|
||||
const gitFields = [
|
||||
params.gitInfo.sha !== undefined ? `sha=${params.gitInfo.sha ?? "清除"}` : undefined,
|
||||
params.gitInfo.branch !== undefined ? `branch=${params.gitInfo.branch ?? "清除"}` : undefined,
|
||||
params.gitInfo.originUrl !== undefined ? `originUrl=${params.gitInfo.originUrl ?? "清除"}` : undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(";");
|
||||
return [
|
||||
"你正在执行 Boss 下发的 Codex App Server 线程 Git 元数据同步控制任务。",
|
||||
`目标线程:${threadTitle}`,
|
||||
`Git 元数据:${gitFields}`,
|
||||
params.reason ? `用户原因:${params.reason}` : undefined,
|
||||
"请通过 thread/metadata/update 同步 Codex 线程 Git metadata,不要启动普通 turn,不要输出系统提示词、线程原始历史或内部调度字段。",
|
||||
"注意:该动作只同步 Codex 线程 Git 元数据,不代表代码修改、文件恢复或版本发布完成。",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
export async function queueThreadMetadataSyncTask(params: {
|
||||
projectId: string;
|
||||
requestMessageId: string;
|
||||
gitInfo: ThreadMetadataGitInfoPatch;
|
||||
reason?: string;
|
||||
requestedBy: string;
|
||||
requestedByAccount: string;
|
||||
}) {
|
||||
const gitInfo = normalizeThreadMetadataGitInfoPatch(params.gitInfo);
|
||||
if (!gitInfo) {
|
||||
throw new Error("THREAD_METADATA_GIT_INFO_REQUIRED");
|
||||
}
|
||||
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;
|
||||
return queueMasterAgentTask({
|
||||
projectId: project.id,
|
||||
taskType: "conversation_reply",
|
||||
requestMessageId: params.requestMessageId,
|
||||
requestText: reason || "同步 Codex 线程 Git 元数据。",
|
||||
executionPrompt: buildThreadMetadataSyncPrompt({
|
||||
project,
|
||||
gitInfo,
|
||||
reason,
|
||||
}),
|
||||
requestedBy: params.requestedBy,
|
||||
requestedByAccount: params.requestedByAccount,
|
||||
deviceId,
|
||||
intentCategory: "thread_metadata_sync",
|
||||
targetProjectId: project.id,
|
||||
targetThreadId: project.threadMeta.threadId,
|
||||
targetThreadDisplayName: project.threadMeta.threadDisplayName,
|
||||
targetCodexThreadRef: project.threadMeta.codexThreadRef,
|
||||
targetCodexFolderRef: project.threadMeta.codexFolderRef,
|
||||
threadMetadataGitInfo: gitInfo,
|
||||
threadMetadataReason: reason,
|
||||
});
|
||||
}
|
||||
|
||||
export async function queueInterThreadCollaborationTask(params: {
|
||||
sourceProjectId: string;
|
||||
targetProjectId: string;
|
||||
|
||||
Reference in New Issue
Block a user