11import type { DelegatedPrincipal , OrganizationDelegatedPrincipal } from '@sim/auth/principal'
2- import { copilotChats , copilotRuns } from '@sim/db/schema'
3- import { and , eq , isNull } from 'drizzle-orm'
2+ import { copilotRuns } from '@sim/db/schema'
3+ import { eq } from 'drizzle-orm'
44import type { z } from 'zod'
55import { defineWorkspaceOperation } from '@/lib/core/application'
66import { defineOrganizationOperation } from '@/lib/core/application/organization-operation'
77import { OrchestrationError } from '@/lib/core/orchestration/types'
88import { insertRunSegment , withRunAdmissionLock } from '@/lib/mothership/async-runs/repository'
9+ import { moveChatToTurn } from '@/lib/mothership/chat/application/admit-turn'
910import { defineAuthorizedChatUseCase } from '@/lib/mothership/chat/application/authorized-chat-use-case'
1011import { resolveOwnedWorkspaceChatContext } from '@/lib/mothership/chat/application/context'
1112import { appendCopilotChatMessages } from '@/lib/mothership/chat/messages-store'
@@ -21,19 +22,13 @@ import type { TurnSecretAuthority } from '@/lib/mothership/secret-mount-policy'
2122export const UNATTENDED_TURN_AUDIENCE = 'sim:copilot-jobs'
2223
2324interface AdmitUnattendedTurnInput {
24- chatId : string
25- /** The occurrence's id: the run, its stream and its user message all carry it. */
26- messageId : string
27- executionId : string
28- requestId : string
29- message : string
30- origin : 'job'
25+ /** The turn's request is its identity: its message id names the run, its stream and its user message. */
3126 recovery : z . input < typeof StreamRecoveryConfigSchema >
3227 lease : ChatStreamLease
3328 secretAuthority : TurnSecretAuthority
3429}
3530
36- export type UnattendedAdmission =
31+ type UnattendedAdmission =
3732 | { kind : 'admitted' ; run : Awaited < ReturnType < typeof insertRunSegment > > }
3833 | { kind : 'duplicate' }
3934
@@ -69,7 +64,7 @@ export const admitUnattendedTurn = defineAuthorizedChatUseCase({
6964 } : {
7065 principal : DelegatedPrincipal | OrganizationDelegatedPrincipal
7166 input : AdmitUnattendedTurnInput
72- } ) => resolveOwnedWorkspaceChatContext ( principal , input . chatId ) ,
67+ } ) => resolveOwnedWorkspaceChatContext ( principal , input . recovery . request . chatId ) ,
7368 authorizationOptions : {
7469 delegation : {
7570 audience : UNATTENDED_TURN_AUDIENCE ,
@@ -85,61 +80,46 @@ export const admitUnattendedTurn = defineAuthorizedChatUseCase({
8580 const request = recovery . request
8681 if (
8782 recovery . kind !== 'unattended_stream' ||
83+ ! request . origin ||
8884 request . userId !== userId ||
8985 request . workspaceId !== workspaceId ||
9086 request . organizationId !== undefined ||
91- request . chatId !== chatId ||
92- request . messageId !== input . messageId ||
93- request . message !== input . message ||
94- request . origin !== input . origin
87+ request . chatId !== chatId
9588 ) {
9689 throw new OrchestrationError ( 'validation' , 'Turn identity does not match its chat' )
9790 }
91+ const { messageId, message, origin } = request
9892 await assertChatStreamLease ( input . lease )
99- return withRunAdmissionLock ( userId , input . messageId , async ( tx ) => {
93+ return withRunAdmissionLock ( userId , messageId , async ( tx ) => {
10094 const [ existing ] = await tx
10195 . select ( { id : copilotRuns . id } )
10296 . from ( copilotRuns )
103- . where ( eq ( copilotRuns . streamId , input . messageId ) )
97+ . where ( eq ( copilotRuns . streamId , messageId ) )
10498 . limit ( 1 )
10599 if ( existing ) return { kind : 'duplicate' } as const
106- const [ chat ] = await tx
107- . update ( copilotChats )
108- . set ( { conversationId : input . messageId , updatedAt : new Date ( ) } )
109- . where (
110- and (
111- eq ( copilotChats . id , chatId ) ,
112- eq ( copilotChats . userId , userId ) ,
113- workspaceId
114- ? eq ( copilotChats . workspaceId , workspaceId )
115- : isNull ( copilotChats . workspaceId ) ,
116- isNull ( copilotChats . deletedAt )
117- )
118- )
119- . returning ( { model : copilotChats . model } )
120- if ( ! chat ) throw new OrchestrationError ( 'not_found' , 'Chat not found' )
100+ const chat = await moveChatToTurn ( tx , context , request )
121101 await appendCopilotChatMessages (
122102 chatId ,
123103 [
124104 buildPersistedUserMessage ( {
125- id : input . messageId ,
126- content : input . message ,
127- origin : input . origin ,
105+ id : messageId ,
106+ content : message ,
107+ origin,
128108 requestMode : 'agent' ,
129109 } ) ,
130110 ] ,
131- { streamId : input . messageId , chatModel : chat . model } ,
111+ { streamId : messageId , chatModel : chat . model } ,
132112 tx
133113 )
134114 const run = await insertRunSegment ( tx , {
135- id : input . messageId ,
136- executionId : input . executionId ,
115+ id : messageId ,
116+ executionId : messageId ,
137117 chatId,
138118 userId,
139119 workspaceId,
140- streamId : input . messageId ,
120+ streamId : messageId ,
141121 requestContext : {
142- requestId : input . requestId ,
122+ requestId : messageId ,
143123 controllerToken : input . lease . value ,
144124 recovery,
145125 secretAuthority : input . secretAuthority ,
0 commit comments