@@ -134,7 +134,9 @@ export type MasterAgentTaskStatus = "queued" | "running" | "completed" | "failed
export type MasterAgentTaskType =
| "conversation_reply"
| "attachment_analysis"
| "group_dispatch_plan" ;
| "group_dispatch_plan"
| "dispatch_execution"
| "device_import_resolution" ;
export type DispatchPlanStatus =
| "pending_user_confirmation"
| "approved"
@@ -312,6 +314,54 @@ export interface DispatchExecution {
completedByDeviceId? : string ;
}
export interface DeviceImportCandidate {
candidateId : string ;
deviceId : string ;
folderName : string ;
folderRef? : string ;
threadId : string ;
threadDisplayName : string ;
codexFolderRef? : string ;
codexThreadRef? : string ;
lastActiveAt : string ;
suggestedImport : boolean ;
}
export interface DeviceImportDraft {
draftId : string ;
deviceId : string ;
enrollmentId? : string ;
status : "pending_candidates" | "pending_selection" | "pending_resolution" | "resolved" | "applied" ;
candidates : DeviceImportCandidate [ ] ;
selectedCandidateIds : string [ ] ;
createdAt : string ;
updatedAt : string ;
reviewedAt? : string ;
reviewedBy? : string ;
resolutionId? : string ;
}
export interface DeviceImportResolutionItem {
candidateId : string ;
action : "create_thread_conversation" | "attach_existing" | "skip" ;
threadDisplayName : string ;
folderName : string ;
targetProjectId? : string ;
reason : string ;
}
export interface DeviceImportResolution {
resolutionId : string ;
draftId : string ;
deviceId : string ;
status : "ready" | "applied" ;
summary : string ;
items : DeviceImportResolutionItem [ ] ;
createdAt : string ;
appliedAt? : string ;
appliedBy? : string ;
}
export interface VerificationCode {
id : string ;
account : string ;
@@ -467,6 +517,11 @@ export interface MasterAgentTask {
attachmentDownloadExpiresAt? : string ;
attachmentDownloadUrl? : string ;
attachmentTextExcerpt? : string ;
dispatchExecutionId? : string ;
targetProjectId? : string ;
targetThreadId? : string ;
targetThreadDisplayName? : string ;
deviceImportDraftId? : string ;
status : MasterAgentTaskStatus ;
requestedAt : string ;
claimedAt? : string ;
@@ -718,6 +773,8 @@ export interface BossState {
masterAgentTasks : MasterAgentTask [ ] ;
dispatchPlans : DispatchPlan [ ] ;
dispatchExecutions : DispatchExecution [ ] ;
deviceImportDrafts : DeviceImportDraft [ ] ;
deviceImportResolutions : DeviceImportResolution [ ] ;
otaUpdates : OtaUpdate [ ] ;
otaUpdateLogs : OtaUpdateLog [ ] ;
deviceSkills : DeviceSkill [ ] ;
@@ -1140,6 +1197,8 @@ const initialState: BossState = {
masterAgentTasks : [ ] ,
dispatchPlans : [ ] ,
dispatchExecutions : [ ] ,
deviceImportDrafts : [ ] ,
deviceImportResolutions : [ ] ,
otaUpdates : [
{
releaseId : "ota_140_to_141" ,
@@ -1760,6 +1819,125 @@ function normalizeDispatchExecution(
} ;
}
function buildDeviceImportCandidateId ( input : {
deviceId : string ;
folderName : string ;
threadId : string ;
codexFolderRef? : string ;
codexThreadRef? : string ;
} ) {
const signature = [
input . deviceId ,
input . codexFolderRef ? . trim ( ) || input . folderName . trim ( ) ,
input . codexThreadRef ? . trim ( ) || input . threadId . trim ( ) ,
]
. filter ( Boolean )
. join ( "-" ) ;
return ` import- ${ slugify ( signature ) } ` ;
}
function normalizeDeviceImportCandidate (
raw : Partial < DeviceImportCandidate > ,
fallback? : DeviceImportCandidate ,
) : DeviceImportCandidate {
const deviceId = raw . deviceId ? ? fallback ? . deviceId ? ? "" ;
const folderName = raw . folderName ? ? fallback ? . folderName ? ? "" ;
const threadId = raw . threadId ? ? fallback ? . threadId ? ? "" ;
return {
candidateId :
raw.candidateId ? ?
fallback ? . candidateId ? ?
buildDeviceImportCandidateId ( {
deviceId ,
folderName ,
threadId ,
codexFolderRef : raw.codexFolderRef ? ? fallback ? . codexFolderRef ,
codexThreadRef : raw.codexThreadRef ? ? fallback ? . codexThreadRef ,
} ) ,
deviceId ,
folderName ,
folderRef : raw.folderRef ? ? fallback ? . folderRef ,
threadId ,
threadDisplayName : raw.threadDisplayName ? ? fallback ? . threadDisplayName ? ? threadId ,
codexFolderRef : raw.codexFolderRef ? ? fallback ? . codexFolderRef ,
codexThreadRef : raw.codexThreadRef ? ? fallback ? . codexThreadRef ,
lastActiveAt : raw.lastActiveAt ? ? fallback ? . lastActiveAt ? ? nowIso ( ) ,
suggestedImport : raw.suggestedImport ? ? fallback ? . suggestedImport ? ? true ,
} ;
}
function normalizeDeviceImportDraft (
raw : Partial < DeviceImportDraft > ,
fallback? : DeviceImportDraft ,
) : DeviceImportDraft {
const fallbackCandidates = fallback ? . candidates ? ? [ ] ;
return {
draftId : raw.draftId ? ? fallback ? . draftId ? ? randomToken ( "import-draft" ) ,
deviceId : raw.deviceId ? ? fallback ? . deviceId ? ? "" ,
enrollmentId : raw.enrollmentId ? ? fallback ? . enrollmentId ,
status : raw.status ? ? fallback ? . status ? ? "pending_candidates" ,
candidates : ensureArray (
raw . candidates as Partial < DeviceImportCandidate > [ ] | undefined ,
fallbackCandidates ,
) . map ( ( candidate , index ) = >
normalizeDeviceImportCandidate (
candidate ,
fallbackCandidates [ index % Math . max ( 1 , fallbackCandidates . length ) ] ,
) ,
) ,
selectedCandidateIds : dedupeStrings (
ensureArray ( raw . selectedCandidateIds , fallback ? . selectedCandidateIds ? ? [ ] ) ,
) ,
createdAt : raw.createdAt ? ? fallback ? . createdAt ? ? nowIso ( ) ,
updatedAt : raw.updatedAt ? ? fallback ? . updatedAt ? ? nowIso ( ) ,
reviewedAt : raw.reviewedAt ? ? fallback ? . reviewedAt ,
reviewedBy : raw.reviewedBy ? ? fallback ? . reviewedBy ,
resolutionId : raw.resolutionId ? ? fallback ? . resolutionId ,
} ;
}
function normalizeDeviceImportResolution (
raw : Partial < DeviceImportResolution > ,
fallback? : DeviceImportResolution ,
) : DeviceImportResolution {
const fallbackItems = fallback ? . items ? ? [ ] ;
return {
resolutionId : raw.resolutionId ? ? fallback ? . resolutionId ? ? randomToken ( "import-resolution" ) ,
draftId : raw.draftId ? ? fallback ? . draftId ? ? "" ,
deviceId : raw.deviceId ? ? fallback ? . deviceId ? ? "" ,
status : raw.status ? ? fallback ? . status ? ? "ready" ,
summary : raw.summary ? ? fallback ? . summary ? ? "" ,
items : ensureArray (
raw . items as Partial < DeviceImportResolutionItem > [ ] | undefined ,
fallbackItems ,
) . map ( ( item , index ) = > ( {
candidateId : item.candidateId ? ? fallbackItems [ index % Math . max ( 1 , fallbackItems . length ) ] ? . candidateId ? ? "" ,
action :
item.action ? ?
fallbackItems [ index % Math . max ( 1 , fallbackItems . length ) ] ? . action ? ?
"skip" ,
threadDisplayName :
item.threadDisplayName ? ?
fallbackItems [ index % Math . max ( 1 , fallbackItems . length ) ] ? . threadDisplayName ? ?
"" ,
folderName :
item.folderName ? ?
fallbackItems [ index % Math . max ( 1 , fallbackItems . length ) ] ? . folderName ? ?
"" ,
targetProjectId :
item.targetProjectId ? ?
fallbackItems [ index % Math . max ( 1 , fallbackItems . length ) ] ? . targetProjectId ,
reason :
item.reason ? ?
fallbackItems [ index % Math . max ( 1 , fallbackItems . length ) ] ? . reason ? ?
"" ,
} ) ) ,
createdAt : raw.createdAt ? ? fallback ? . createdAt ? ? nowIso ( ) ,
appliedAt : raw.appliedAt ? ? fallback ? . appliedAt ,
appliedBy : raw.appliedBy ? ? fallback ? . appliedBy ,
} ;
}
function dedupeStrings ( values : string [ ] ) {
return [ . . . new Set ( values . filter ( ( value ) = > Boolean ( value ) ) ) ] ;
}
@@ -2424,6 +2602,11 @@ function normalizeState(raw: Partial<BossState> | undefined): BossState {
attachmentDownloadExpiresAt : task.attachmentDownloadExpiresAt ,
attachmentDownloadUrl : task.attachmentDownloadUrl ,
attachmentTextExcerpt : task.attachmentTextExcerpt ,
dispatchExecutionId : task.dispatchExecutionId ,
targetProjectId : task.targetProjectId ,
targetThreadId : task.targetThreadId ,
targetThreadDisplayName : task.targetThreadDisplayName ,
deviceImportDraftId : task.deviceImportDraftId ,
status : task.status ? ? "queued" ,
requestedAt : task.requestedAt ? ? nowIso ( ) ,
claimedAt : task.claimedAt ,
@@ -2441,6 +2624,21 @@ function normalizeState(raw: Partial<BossState> | undefined): BossState {
base . dispatchExecutions [ index % Math . max ( 1 , base . dispatchExecutions . length ) ] ,
) ,
) ,
deviceImportDrafts : ensureArray ( raw . deviceImportDrafts , base . deviceImportDrafts ) . map ( ( draft , index ) = >
normalizeDeviceImportDraft (
draft ,
base . deviceImportDrafts [ index % Math . max ( 1 , base . deviceImportDrafts . length ) ] ,
) ,
) ,
deviceImportResolutions : ensureArray (
raw . deviceImportResolutions ,
base . deviceImportResolutions ,
) . map ( ( resolution , index ) = >
normalizeDeviceImportResolution (
resolution ,
base . deviceImportResolutions [ index % Math . max ( 1 , base . deviceImportResolutions . length ) ] ,
) ,
) ,
otaUpdates : ensureArray ( raw . otaUpdates , base . otaUpdates ) . map ( ( update , index ) = > ( {
. . . base . otaUpdates [ index % base . otaUpdates . length ] ,
. . . update ,
@@ -2928,6 +3126,12 @@ function syncDerivedState(input: BossState) {
state . dispatchExecutions = state . dispatchExecutions
. sort ( ( a , b ) = > b . createdAt . localeCompare ( a . createdAt ) )
. slice ( 0 , 160 ) ;
state . deviceImportDrafts = state . deviceImportDrafts
. sort ( ( a , b ) = > b . updatedAt . localeCompare ( a . updatedAt ) )
. slice ( 0 , 40 ) ;
state . deviceImportResolutions = state . deviceImportResolutions
. sort ( ( a , b ) = > b . createdAt . localeCompare ( a . createdAt ) )
. slice ( 0 , 80 ) ;
state . devices = state . devices . filter ( isProductionDevice ) ;
const visibleDeviceIds = new Set ( state . devices . map ( ( device ) = > device . id ) ) ;
@@ -2942,6 +3146,13 @@ function syncDerivedState(input: BossState) {
visibleThreadIds . has ( item . threadId ) ,
) ;
state . deviceEnrollments = state . deviceEnrollments . filter ( ( item ) = > visibleDeviceIds . has ( item . deviceId ) ) ;
state . deviceImportDrafts = state . deviceImportDrafts . filter ( ( item ) = >
visibleDeviceIds . has ( item . deviceId ) ,
) ;
const visibleImportDraftIds = new Set ( state . deviceImportDrafts . map ( ( item ) = > item . draftId ) ) ;
state . deviceImportResolutions = state . deviceImportResolutions . filter (
( item ) = > visibleDeviceIds . has ( item . deviceId ) && visibleImportDraftIds . has ( item . draftId ) ,
) ;
state . deviceSkills = state . deviceSkills
. filter ( ( skill ) = > visibleDeviceIds . has ( skill . deviceId ) )
. sort ( ( a , b ) = > b . updatedAt . localeCompare ( a . updatedAt ) ) ;
@@ -3773,6 +3984,10 @@ export async function queueMasterAgentTask(payload: {
attachmentDownloadExpiresAt? : string ;
attachmentDownloadUrl? : string ;
attachmentTextExcerpt? : string ;
dispatchExecutionId? : string ;
targetProjectId? : string ;
targetThreadId? : string ;
targetThreadDisplayName? : string ;
} ) {
const task = await mutateState ( ( state ) = > {
const task : MasterAgentTask = {
@@ -3793,6 +4008,10 @@ export async function queueMasterAgentTask(payload: {
attachmentDownloadExpiresAt : payload.attachmentDownloadExpiresAt ,
attachmentDownloadUrl : payload.attachmentDownloadUrl ,
attachmentTextExcerpt : payload.attachmentTextExcerpt ,
dispatchExecutionId : payload.dispatchExecutionId ,
targetProjectId : payload.targetProjectId ,
targetThreadId : payload.targetThreadId ,
targetThreadDisplayName : payload.targetThreadDisplayName ,
status : "queued" ,
requestedAt : nowIso ( ) ,
} ;
@@ -3953,6 +4172,7 @@ export async function createDispatchExecutionsFromPlan(input: {
if ( plan . status !== "dispatched" ) {
plan . status = "dispatched" ;
}
ensureDispatchExecutionTasksInState ( state , plan , existingExecutions ) ;
return existingExecutions ;
}
@@ -3977,11 +4197,116 @@ export async function createDispatchExecutionsFromPlan(input: {
state . dispatchExecutions . unshift ( execution ) ;
return execution ;
} ) ;
ensureDispatchExecutionTasksInState ( state , plan , executions ) ;
plan . status = "dispatched" ;
return executions ;
} ) ;
}
function buildDispatchExecutionPrompt ( input : {
groupProject : Project ;
plan : DispatchPlan ;
target : DispatchPlanTarget ;
} ) {
const requestMessage = input . groupProject . messages . find (
( message ) = > message . id === input . plan . requestMessageId ,
) ;
const requestText = requestMessage ? . body ? ? input . plan . summary ;
return [
"你正在执行 Boss 控制台的线程分发任务。" ,
"你的输出必须是 JSON, 并且只能包含两个字符串字段: rawThreadReply、replyBody。" ,
"rawThreadReply: 写成目标线程直接回到群里的原始结果, 不要冒充主 Agent。" ,
"replyBody: 写成主 Agent 给群里的简短汇总,必须保留“主 Agent 汇总:”前缀。" ,
"不要输出 Markdown 代码块,不要输出额外解释。" ,
` groupProjectId: ${ input . groupProject . id } ` ,
` groupProjectName: ${ input . groupProject . name } ` ,
` threadProjectId: ${ input . target . projectId } ` ,
` threadId: ${ input . target . threadId } ` ,
` threadTitle: ${ input . target . threadDisplayName } ` ,
` folderName: ${ input . target . folderName } ` ,
` requestText: ${ requestText } ` ,
` dispatchSummary: ${ input . plan . summary } ` ,
] . join ( "\n" ) ;
}
function ensureDispatchExecutionTaskInState (
state : BossState ,
plan : DispatchPlan ,
execution : DispatchExecution ,
) {
const groupProject = state . projects . find ( ( item ) = > item . id === execution . groupProjectId ) ;
if ( ! groupProject ) {
throw new Error ( "DISPATCH_EXECUTION_GROUP_PROJECT_NOT_FOUND" ) ;
}
const target = plan . targets . find (
( item ) = >
item . projectId === execution . targetProjectId &&
item . threadId === execution . targetThreadId &&
item . deviceId === execution . deviceId ,
) ;
if ( ! target ) {
throw new Error ( "DISPATCH_EXECUTION_TARGET_NOT_FOUND" ) ;
}
const existing = state . masterAgentTasks . find (
( task ) = >
task . taskType === "dispatch_execution" &&
( task . dispatchExecutionId === execution . executionId ||
( task . projectId === execution . groupProjectId &&
task . requestMessageId === plan . planId &&
task . targetProjectId === execution . targetProjectId &&
task . targetThreadId === execution . targetThreadId ) ) ,
) ;
if ( existing ) {
existing . dispatchExecutionId = existing . dispatchExecutionId ? ? execution . executionId ;
existing . targetProjectId = existing . targetProjectId ? ? execution . targetProjectId ;
existing . targetThreadId = existing . targetThreadId ? ? execution . targetThreadId ;
existing . targetThreadDisplayName = existing . targetThreadDisplayName ? ? target . threadDisplayName ;
existing . executionPrompt =
existing . executionPrompt ||
buildDispatchExecutionPrompt ( {
groupProject ,
plan ,
target ,
} ) ;
return existing ;
}
const requestedBy = plan . confirmedBy ? ? plan . requestedBy ;
const requestMessage = groupProject . messages . find ( ( message ) = > message . id === plan . requestMessageId ) ;
const task : MasterAgentTask = {
taskId : randomToken ( "mastertask" ) ,
projectId : execution.groupProjectId ,
taskType : "dispatch_execution" ,
requestMessageId : plan.planId ,
requestText : requestMessage?.body ? ? plan . summary ,
executionPrompt : buildDispatchExecutionPrompt ( {
groupProject ,
plan ,
target ,
} ) ,
requestedBy ,
requestedByAccount : requestedBy ,
deviceId : execution.deviceId ,
dispatchExecutionId : execution.executionId ,
targetProjectId : execution.targetProjectId ,
targetThreadId : execution.targetThreadId ,
targetThreadDisplayName : target.threadDisplayName ,
status : "queued" ,
requestedAt : nowIso ( ) ,
} ;
state . masterAgentTasks . unshift ( task ) ;
return task ;
}
function ensureDispatchExecutionTasksInState (
state : BossState ,
plan : DispatchPlan ,
executions : DispatchExecution [ ] ,
) {
return executions . map ( ( execution ) = > ensureDispatchExecutionTaskInState ( state , plan , execution ) ) ;
}
export async function confirmDispatchPlanAndCreateExecutions ( input : {
groupProjectId : string ;
planId : string ;
@@ -4055,6 +4380,8 @@ export async function confirmDispatchPlanAndCreateExecutions(input: {
} ) ;
}
ensureDispatchExecutionTasksInState ( state , plan , executions ) ;
return {
plan : { . . . plan } ,
executions : executions.map ( ( execution ) = > ( { . . . execution } ) ) ,
@@ -4109,6 +4436,115 @@ export async function completeDispatchExecution(payload: {
} ) ;
}
function summarizeDispatchExecutionReply ( rawThreadReply : string , threadTitle : string ) {
const compact = rawThreadReply . trim ( ) . replace ( /\s+/g , " " ) ;
if ( ! compact ) {
return ` 主 Agent 汇总: ${ threadTitle } 已返回执行结果。 ` ;
}
if ( compact . length <= 72 ) {
return ` 主 Agent 汇总: ${ threadTitle } 已返回执行结果: ${ compact } ` ;
}
return ` 主 Agent 汇总: ${ threadTitle } 已返回执行结果: ${ compact . slice ( 0 , 69 ) } ... ` ;
}
function appendDispatchExecutionResultInState (
state : BossState ,
payload : {
dispatchExecutionId : string ;
completedByDeviceId : string ;
status : "completed" | "failed" ;
groupProjectId : string ;
targetProjectId : string ;
targetThreadId : string ;
targetThreadDisplayName? : string ;
rawThreadReply? : string ;
masterSummary? : string ;
} ,
) {
const execution = state . dispatchExecutions . find (
( item ) = > item . executionId === payload . dispatchExecutionId ,
) ;
if ( ! execution ) throw new Error ( "DISPATCH_EXECUTION_NOT_FOUND" ) ;
if ( execution . groupProjectId !== payload . groupProjectId ) {
throw new Error ( "DISPATCH_EXECUTION_GROUP_PROJECT_MISMATCH" ) ;
}
if ( execution . targetProjectId !== payload . targetProjectId ) {
throw new Error ( "DISPATCH_EXECUTION_TARGET_PROJECT_MISMATCH" ) ;
}
if ( execution . targetThreadId !== payload . targetThreadId ) {
throw new Error ( "DISPATCH_EXECUTION_TARGET_THREAD_MISMATCH" ) ;
}
if ( execution . deviceId !== payload . completedByDeviceId ) {
throw new Error ( "DISPATCH_EXECUTION_DEVICE_MISMATCH" ) ;
}
const device = state . devices . find ( ( item ) = > item . id === payload . completedByDeviceId ) ;
const threadTitle =
payload . targetThreadDisplayName ? . trim ( ) ||
state . projects . find ( ( item ) = > item . id === payload . targetProjectId ) ? . threadMeta . threadDisplayName ||
payload . targetThreadId ;
let mirroredResult : Message | null = null ;
let masterSummary : Message | null = null ;
if ( payload . status === "completed" ) {
if ( ! payload . rawThreadReply ? . trim ( ) ) {
throw new Error ( "DISPATCH_EXECUTION_RAW_REPLY_REQUIRED" ) ;
}
mirroredResult = pushProjectLedgerMessage ( state , payload . groupProjectId , {
sender : "device" ,
senderLabel : ` ${ threadTitle } · ${ device ? . name ? ? payload . completedByDeviceId } ` ,
body : payload.rawThreadReply.trim ( ) ,
kind : "text" ,
} ) ;
masterSummary = pushProjectLedgerMessage ( state , payload . groupProjectId , {
sender : "master" ,
senderLabel : "主 Agent" ,
body :
payload.masterSummary?.trim ( ) ||
summarizeDispatchExecutionReply ( payload . rawThreadReply , threadTitle ) ,
kind : "text" ,
} ) ;
} else {
masterSummary = pushProjectLedgerMessage ( state , payload . groupProjectId , {
sender : "ops" ,
senderLabel : "主 Agent Relay" ,
body : ` ${ threadTitle } 执行失败,请稍后重试。 ` ,
kind : "text" ,
} ) ;
}
execution . status = payload . status ;
execution . completedAt = nowIso ( ) ;
execution . completedByDeviceId = payload . completedByDeviceId ;
execution . resultMessageId = mirroredResult ? . id ? ? execution . resultMessageId ;
return {
execution : { . . . execution } ,
mirroredResult ,
masterSummary ,
} ;
}
export async function appendDispatchExecutionResult ( payload : {
dispatchExecutionId : string ;
completedByDeviceId : string ;
status : "completed" | "failed" ;
groupProjectId : string ;
targetProjectId : string ;
targetThreadId : string ;
targetThreadDisplayName? : string ;
rawThreadReply? : string ;
masterSummary? : string ;
} ) {
const result = await mutateState ( ( state ) = >
appendDispatchExecutionResultInState ( state , payload ) ,
) ;
publishBossEvent ( "project.messages.updated" , { projectId : payload.groupProjectId } ) ;
publishBossEvent ( "conversation.updated" , { projectId : payload.groupProjectId } ) ;
return result ;
}
export async function getMasterAgentTask ( taskId : string ) {
const state = await readState ( ) ;
return state . masterAgentTasks . find ( ( item ) = > item . taskId === taskId ) ? ? null ;
@@ -4116,6 +4552,7 @@ export async function getMasterAgentTask(taskId: string) {
export async function claimNextMasterAgentTask ( deviceId : string ) {
let attachmentProjectId : string | undefined ;
let dispatchExecutionProjectId : string | undefined ;
const task = await mutateState ( ( state ) = > {
const next = state . masterAgentTasks . find (
( item ) = > item . deviceId === deviceId && item . status === "queued" ,
@@ -4133,6 +4570,15 @@ export async function claimNextMasterAgentTask(deviceId: string) {
attachmentProjectId = next . projectId ;
}
}
if ( next . taskType === "dispatch_execution" && next . dispatchExecutionId ) {
const execution = state . dispatchExecutions . find (
( item ) = > item . executionId === next . dispatchExecutionId ,
) ;
if ( execution && execution . status === "queued" ) {
execution . status = "running" ;
dispatchExecutionProjectId = execution . groupProjectId ;
}
}
return { . . . next } ;
} ) ;
if ( task ) {
@@ -4145,6 +4591,9 @@ export async function claimNextMasterAgentTask(deviceId: string) {
publishBossEvent ( "project.messages.updated" , { projectId : attachmentProjectId } ) ;
publishBossEvent ( "conversation.updated" , { projectId : attachmentProjectId } ) ;
}
if ( dispatchExecutionProjectId ) {
publishBossEvent ( "conversation.updated" , { projectId : dispatchExecutionProjectId } ) ;
}
}
return task ;
}
@@ -4156,6 +4605,10 @@ export async function completeMasterAgentTask(payload: {
replyBody? : string ;
errorMessage? : string ;
requestId? : string ;
dispatchExecutionId? : string ;
targetProjectId? : string ;
targetThreadId? : string ;
rawThreadReply? : string ;
dispatchPlan ? : {
summary? : string ;
targets : DispatchPlanTarget [ ] ;
@@ -4197,6 +4650,9 @@ export async function completeMasterAgentTask(payload: {
let attachmentProjectId : string | undefined ;
let createdDispatchPlan : DispatchPlan | undefined ;
let dispatchExecutionResult :
| ReturnType < typeof appendDispatchExecutionResultInState >
| undefined ;
if ( task . taskType === "attachment_analysis" && task . attachmentId ) {
const project = state . projects . find ( ( item ) = > item . id === task . projectId ) ;
const match = project ? findProjectAttachment ( project , task . attachmentId ) : null ;
@@ -4250,6 +4706,21 @@ export async function completeMasterAgentTask(payload: {
targets : payload.dispatchPlan.targets ,
} ) ;
}
} else if ( task . taskType === "dispatch_execution" ) {
if ( ! task . dispatchExecutionId || ! task . targetProjectId || ! task . targetThreadId ) {
throw new Error ( "MASTER_AGENT_DISPATCH_EXECUTION_CONTEXT_REQUIRED" ) ;
}
dispatchExecutionResult = appendDispatchExecutionResultInState ( state , {
dispatchExecutionId : payload.dispatchExecutionId?.trim ( ) || task . dispatchExecutionId ,
completedByDeviceId : payload.deviceId ,
status : payload.status ,
groupProjectId : task.projectId ,
targetProjectId : payload.targetProjectId?.trim ( ) || task . targetProjectId ,
targetThreadId : payload.targetThreadId?.trim ( ) || task . targetThreadId ,
targetThreadDisplayName : task.targetThreadDisplayName ,
rawThreadReply : payload.rawThreadReply?.trim ( ) || task . replyBody ,
masterSummary : payload.replyBody?.trim ( ) ,
} ) ;
} else if ( ! attachmentProjectId && payload . status === "completed" && task . replyBody ) {
pushProjectLedgerMessage ( state , task . projectId , {
sender : "master" ,
@@ -4269,6 +4740,7 @@ export async function completeMasterAgentTask(payload: {
return {
. . . task ,
dispatchPlan : createdDispatchPlan ? { . . . createdDispatchPlan } : undefined ,
dispatchExecution : dispatchExecutionResult?.execution ,
} ;
} ) ;
@@ -4724,6 +5196,51 @@ export async function verifyDeviceToken(deviceId: string, token?: string) {
return hasAuthorizedDeviceToken ( state , deviceId , token ) ;
}
function upsertDeviceImportDraftFromHeartbeat (
state : BossState ,
payload : {
deviceId : string ;
enrollmentId? : string ;
candidates : DeviceImportCandidate [ ] ;
} ,
) {
if ( payload . candidates . length === 0 ) {
return null ;
}
const existing = state . deviceImportDrafts . find ( ( item ) = > item . deviceId === payload . deviceId ) ;
const selectedCandidateIds = dedupeStrings (
( existing ? . selectedCandidateIds ? ? [ ] ) . filter ( ( candidateId ) = >
payload . candidates . some ( ( candidate ) = > candidate . candidateId === candidateId ) ,
) ,
) ;
const nextDraft = normalizeDeviceImportDraft ( {
draftId : existing?.draftId ? ? randomToken ( "import-draft" ) ,
deviceId : payload.deviceId ,
enrollmentId : payload.enrollmentId ? ? existing ? . enrollmentId ,
status :
selectedCandidateIds.length > 0
? existing ? . resolutionId
? "resolved"
: "pending_resolution"
: "pending_selection" ,
candidates : payload.candidates ,
selectedCandidateIds ,
createdAt : existing?.createdAt ? ? nowIso ( ) ,
updatedAt : nowIso ( ) ,
reviewedAt : existing?.reviewedAt ,
reviewedBy : existing?.reviewedBy ,
resolutionId : existing?.resolutionId ,
} , existing ) ;
state . deviceImportDrafts = [
nextDraft ,
. . . state . deviceImportDrafts . filter ( ( item ) = > item . draftId !== nextDraft . draftId ) ,
] ;
return nextDraft ;
}
export async function upsertDeviceHeartbeat ( payload : {
deviceId : string ;
token? : string ;
@@ -4736,6 +5253,16 @@ export async function upsertDeviceHeartbeat(payload: {
quota7d : number ;
projects : string [ ] ;
endpoint? : string ;
projectCandidates? : Array < {
folderName : string ;
folderRef? : string ;
threadId : string ;
threadDisplayName : string ;
codexFolderRef? : string ;
codexThreadRef? : string ;
lastActiveAt? : string ;
suggestedImport? : boolean ;
} > ;
} ) {
const result = await mutateState ( ( state ) = > {
const claimedEnrollment = claimEnrollment (
@@ -4817,10 +5344,30 @@ export async function upsertDeviceHeartbeat(payload: {
}
}
const normalizedCandidates = ensureArray ( payload . projectCandidates , [ ] ) . map ( ( candidate ) = >
normalizeDeviceImportCandidate ( {
deviceId : payload.deviceId ,
folderName : candidate.folderName ,
folderRef : candidate.folderRef ,
threadId : candidate.threadId ,
threadDisplayName : candidate.threadDisplayName ,
codexFolderRef : candidate.codexFolderRef ,
codexThreadRef : candidate.codexThreadRef ,
lastActiveAt : candidate.lastActiveAt ? ? nowIso ( ) ,
suggestedImport : candidate.suggestedImport ? ? true ,
} ) ,
) ;
const draft = upsertDeviceImportDraftFromHeartbeat ( state , {
deviceId : payload.deviceId ,
enrollmentId : claimedEnrollment?.enrollmentId ,
candidates : normalizedCandidates ,
} ) ;
return {
device ,
token : claimedEnrollment?.token ? ? device . token ,
pairingStatus : claimedEnrollment?.status ,
importDraft : draft ,
} ;
} ) ;
publishBossEvent ( "devices.updated" , { deviceId : payload.deviceId } ) ;
@@ -4828,6 +5375,269 @@ export async function upsertDeviceHeartbeat(payload: {
return result ;
}
function resolveDeviceImportAction (
state : BossState ,
deviceId : string ,
candidate : DeviceImportCandidate ,
) : DeviceImportResolutionItem {
const directMatch = state . projects . find (
( project ) = >
! project . isGroup &&
( ( candidate . codexThreadRef && project . threadMeta . codexThreadRef === candidate . codexThreadRef ) ||
project . threadMeta . threadId === candidate . threadId ) ,
) ;
if ( directMatch ) {
return {
candidateId : candidate.candidateId ,
action : "attach_existing" ,
threadDisplayName : candidate.threadDisplayName ,
folderName : candidate.folderName ,
targetProjectId : directMatch.id ,
reason : ` 已匹配到现有会话《 ${ directMatch . name } 》,直接补充设备与线程映射。 ` ,
} ;
}
const similarByFolder = state . projects . find (
( project ) = >
! project . isGroup &&
project . deviceIds . includes ( deviceId ) &&
project . threadMeta . folderName === candidate . folderName &&
project . threadMeta . threadDisplayName === candidate . threadDisplayName ,
) ;
if ( similarByFolder ) {
return {
candidateId : candidate.candidateId ,
action : "attach_existing" ,
threadDisplayName : candidate.threadDisplayName ,
folderName : candidate.folderName ,
targetProjectId : similarByFolder.id ,
reason : ` 同设备下已有同名线程《 ${ similarByFolder . name } 》,避免重复导入。 ` ,
} ;
}
return {
candidateId : candidate.candidateId ,
action : "create_thread_conversation" ,
threadDisplayName : candidate.threadDisplayName ,
folderName : candidate.folderName ,
reason : ` 建议把 ${ candidate . threadDisplayName } 作为独立聊天窗口导入。 ` ,
} ;
}
function summarizeDeviceImportResolution (
deviceName : string ,
items : DeviceImportResolutionItem [ ] ,
) {
const createCount = items . filter ( ( item ) = > item . action === "create_thread_conversation" ) . length ;
const attachCount = items . filter ( ( item ) = > item . action === "attach_existing" ) . length ;
const skipCount = items . filter ( ( item ) = > item . action === "skip" ) . length ;
return ` ${ deviceName } 导入建议:新建 ${ createCount } 个会话,关联 ${ attachCount } 个现有会话 ${ skipCount > 0 ? ` ,跳过 ${ skipCount } 项 ` : "" } 。 ` ;
}
export async function getLatestDeviceImportDraft ( deviceId : string ) {
const state = await readState ( ) ;
const draft = state . deviceImportDrafts . find ( ( item ) = > item . deviceId === deviceId ) ? ? null ;
const resolution = draft ? . resolutionId
? state . deviceImportResolutions . find ( ( item ) = > item . resolutionId === draft . resolutionId ) ? ? null
: state . deviceImportResolutions . find ( ( item ) = > item . deviceId === deviceId ) ? ? null ;
return { draft , resolution } ;
}
export async function selectDeviceImportCandidates ( input : {
deviceId : string ;
selectedCandidateIds : string [ ] ;
selectedBy : string ;
} ) {
const draft = await mutateState ( ( state ) = > {
const draft = state . deviceImportDrafts . find ( ( item ) = > item . deviceId === input . deviceId ) ;
if ( ! draft ) throw new Error ( "DEVICE_IMPORT_DRAFT_NOT_FOUND" ) ;
const availableCandidateIds = new Set ( draft . candidates . map ( ( item ) = > item . candidateId ) ) ;
const nextSelected = dedupeStrings ( input . selectedCandidateIds ) . filter ( ( candidateId ) = >
availableCandidateIds . has ( candidateId ) ,
) ;
if ( nextSelected . length === 0 ) {
throw new Error ( "DEVICE_IMPORT_SELECTION_REQUIRED" ) ;
}
draft . selectedCandidateIds = nextSelected ;
draft . status = "pending_resolution" ;
draft . updatedAt = nowIso ( ) ;
draft . reviewedBy = input . selectedBy ;
draft . reviewedAt = undefined ;
draft . resolutionId = undefined ;
state . deviceImportResolutions = state . deviceImportResolutions . filter (
( item ) = > item . draftId !== draft . draftId ,
) ;
return { . . . draft } ;
} ) ;
publishBossEvent ( "devices.updated" , { deviceId : input.deviceId } ) ;
return draft ;
}
export async function resolveDeviceImportDraft ( input : {
deviceId : string ;
reviewedBy : string ;
} ) {
const result = await mutateState ( ( state ) = > {
const draft = state . deviceImportDrafts . find ( ( item ) = > item . deviceId === input . deviceId ) ;
if ( ! draft ) throw new Error ( "DEVICE_IMPORT_DRAFT_NOT_FOUND" ) ;
if ( draft . selectedCandidateIds . length === 0 ) {
throw new Error ( "DEVICE_IMPORT_SELECTION_REQUIRED" ) ;
}
const device = state . devices . find ( ( item ) = > item . id === input . deviceId ) ;
if ( ! device ) throw new Error ( "DEVICE_NOT_FOUND" ) ;
const selectedCandidates = draft . candidates . filter ( ( candidate ) = >
draft . selectedCandidateIds . includes ( candidate . candidateId ) ,
) ;
const items = selectedCandidates . map ( ( candidate ) = >
resolveDeviceImportAction ( state , input . deviceId , candidate ) ,
) ;
const resolution = normalizeDeviceImportResolution ( {
resolutionId : randomToken ( "import-resolution" ) ,
draftId : draft.draftId ,
deviceId : input.deviceId ,
status : "ready" ,
summary : summarizeDeviceImportResolution ( device . name , items ) ,
items ,
createdAt : nowIso ( ) ,
} ) ;
draft . status = "resolved" ;
draft . updatedAt = nowIso ( ) ;
draft . reviewedAt = nowIso ( ) ;
draft . reviewedBy = input . reviewedBy ;
draft . resolutionId = resolution . resolutionId ;
state . deviceImportResolutions = [
resolution ,
. . . state . deviceImportResolutions . filter ( ( item ) = > item . draftId !== draft . draftId ) ,
] ;
return { draft : { . . . draft } , resolution } ;
} ) ;
publishBossEvent ( "devices.updated" , { deviceId : input.deviceId } ) ;
publishBossEvent ( "conversation.updated" , { deviceId : input.deviceId } ) ;
return result ;
}
function buildImportedThreadProject ( device : Device , candidate : DeviceImportCandidate ) {
const projectId =
candidate . codexThreadRef ? . trim ( ) && candidate . codexFolderRef ? . trim ( )
? slugify ( ` ${ device . id } - ${ candidate . codexFolderRef } - ${ candidate . codexThreadRef } ` )
: slugify ( ` ${ device . id } - ${ candidate . folderName } - ${ candidate . threadId } ` ) ;
const now = nowIso ( ) ;
return normalizeProject ( {
id : projectId ,
name : candidate.threadDisplayName ,
pinned : false ,
systemPinned : false ,
deviceIds : [ device . id ] ,
preview : ` 已从 ${ device . name } 导入线程 ${ candidate . threadDisplayName } ` ,
updatedAt : now ,
lastMessageAt : now ,
isGroup : false ,
unreadCount : 0 ,
riskLevel : "low" ,
threadMeta : {
projectId ,
threadId : candidate.threadId ,
threadDisplayName : candidate.threadDisplayName ,
folderName : candidate.folderName ,
activityIconCount : 1 ,
updatedAt : candidate.lastActiveAt || now ,
codexFolderRef : candidate.codexFolderRef ? ? candidate . folderRef ,
codexThreadRef : candidate.codexThreadRef ,
} ,
groupMembers : [ ] ,
createdByAgent : true ,
collaborationMode : "development" ,
approvalState : "not_required" ,
messages : [
{
id : randomToken ( "msg" ) ,
sender : "master" ,
senderLabel : "主 Agent" ,
body : ` 已从设备 ${ device . name } 导入线程《 ${ candidate . threadDisplayName } 》。 ` ,
sentAt : now ,
kind : "text" ,
} ,
] ,
goals : [ ] ,
versions : [ ] ,
} ) ;
}
export async function applyDeviceImportResolution ( input : {
deviceId : string ;
appliedBy : string ;
} ) {
const result = await mutateState ( ( state ) = > {
const draft = state . deviceImportDrafts . find ( ( item ) = > item . deviceId === input . deviceId ) ;
if ( ! draft || ! draft . resolutionId ) throw new Error ( "DEVICE_IMPORT_RESOLUTION_NOT_FOUND" ) ;
const resolution = state . deviceImportResolutions . find (
( item ) = > item . resolutionId === draft . resolutionId ,
) ;
if ( ! resolution ) throw new Error ( "DEVICE_IMPORT_RESOLUTION_NOT_FOUND" ) ;
const device = state . devices . find ( ( item ) = > item . id === input . deviceId ) ;
if ( ! device ) throw new Error ( "DEVICE_NOT_FOUND" ) ;
const importedProjects : Project [ ] = [ ] ;
for ( const item of resolution . items ) {
const candidate = draft . candidates . find ( ( entry ) = > entry . candidateId === item . candidateId ) ;
if ( ! candidate || item . action === "skip" ) {
continue ;
}
let targetProject = item . targetProjectId
? state . projects . find ( ( project ) = > project . id === item . targetProjectId )
: undefined ;
if ( item . action === "create_thread_conversation" && ! targetProject ) {
targetProject = buildImportedThreadProject ( device , candidate ) ;
state . projects . unshift ( targetProject ) ;
} else if ( item . action === "attach_existing" && ! targetProject ) {
continue ;
}
if ( ! targetProject ) continue ;
if ( ! targetProject . deviceIds . includes ( device . id ) ) {
targetProject . deviceIds . push ( device . id ) ;
}
targetProject . threadMeta . threadDisplayName = candidate . threadDisplayName ;
targetProject . threadMeta . folderName = candidate . folderName ;
targetProject . threadMeta . threadId = candidate . threadId ;
targetProject . threadMeta . codexFolderRef = candidate . codexFolderRef ? ? candidate . folderRef ;
targetProject . threadMeta . codexThreadRef = candidate . codexThreadRef ;
targetProject . threadMeta . updatedAt = candidate . lastActiveAt ;
targetProject . preview = ` 已导入 ${ candidate . threadDisplayName } ` ;
targetProject . updatedAt = nowIso ( ) ;
targetProject . lastMessageAt = targetProject . updatedAt ;
importedProjects . push ( { . . . targetProject } ) ;
}
device . projects = dedupeStrings (
draft . candidates
. filter ( ( candidate ) = > draft . selectedCandidateIds . includes ( candidate . candidateId ) )
. map ( ( candidate ) = > candidate . folderName ) ,
) ;
resolution . status = "applied" ;
resolution . appliedAt = nowIso ( ) ;
resolution . appliedBy = input . appliedBy ;
draft . status = "applied" ;
draft . updatedAt = nowIso ( ) ;
return {
draft : { . . . draft } ,
resolution : { . . . resolution } ,
importedProjects ,
} ;
} ) ;
publishBossEvent ( "devices.updated" , { deviceId : input.deviceId } ) ;
publishBossEvent ( "conversation.updated" , { deviceId : input.deviceId } ) ;
return result ;
}
export async function upsertDeviceSkills ( payload : {
deviceId : string ;
skills : Array < {