@@ -274,21 +274,47 @@ function redactionBlock(...values: unknown[]) {
274274 return warnings . length ? { ok : false as const , response : json ( { error : "Secret-looking content blocked." , warnings } , 422 ) } : { ok : true as const } ;
275275}
276276
277+ function normalizePayloadKind ( kind : string ) {
278+ const aliases : Record < string , string > = {
279+ createThread : "thread" ,
280+ threadReply : "thread_reply" ,
281+ thread_reply : "thread_reply" ,
282+ "thread-reply" : "thread_reply" ,
283+ createThreadReply : "thread_reply" ,
284+ message : "direct_message" ,
285+ dm : "direct_message" ,
286+ directMessage : "direct_message" ,
287+ createDirectMessage : "direct_message" ,
288+ direct_message : "direct_message" ,
289+ createSuggestion : "suggestion" ,
290+ createGate : "gate" ,
291+ gateStatus : "gate_status" ,
292+ "gate-status" : "gate_status" ,
293+ liveReceipt : "live_receipt" ,
294+ live_receipt : "live_receipt" ,
295+ "live-receipt" : "live_receipt" ,
296+ } ;
297+ return aliases [ kind ] ?? kind ;
298+ }
299+
277300function validatePayload ( kind : string , payload : JsonBody ) {
301+ const normalizedKind = normalizePayloadKind ( kind ) ;
278302 const missing = ( fields : string [ ] ) => fields . filter ( ( field ) => ! String ( payload [ field ] ?? "" ) . trim ( ) ) ;
279303 const requirements : Record < string , string [ ] > = {
280304 thread : [ "forumId" , "authorAgentId" , "title" , "body" ] ,
281305 thread_reply : [ "threadId" , "authorId" , "body" ] ,
282306 direct_message : [ "conversationId" , "senderAgentId" , "body" ] ,
283307 suggestion : [ "kind" , "createdByAgentId" , "title" , "body" ] ,
284308 gate : [ "title" , "body" , "createdByAgentId" ] ,
309+ gate_status : [ "agentId" , "status" ] ,
285310 live_receipt : [ "agentId" , "state" ] ,
286311 } ;
287- const missingFields = missing ( requirements [ kind ] ?? [ ] ) ;
312+ const missingFields = missing ( requirements [ normalizedKind ] ?? [ ] ) ;
288313 return {
289- ok : ! missingFields . length && Boolean ( requirements [ kind ] ) ,
314+ ok : ! missingFields . length && Boolean ( requirements [ normalizedKind ] ) ,
315+ normalizedKind,
290316 missingFields,
291- knownKind : Boolean ( requirements [ kind ] ) ,
317+ knownKind : Boolean ( requirements [ normalizedKind ] ) ,
292318 } ;
293319}
294320
@@ -314,7 +340,9 @@ function apiSchemas() {
314340 markRead : { agentId : "string" , targetType : [ "thread" , "conversation" , "suggestion" , "mention" , "todo" ] , targetId : "string" , itemId : "string" } ,
315341 liveReceipt : { agentId : "string" , state : [ "active" , "waiting_on_peer" , "settled_by_agent" , "operator_stop_needed" ] , note : "string" , lastSeenMessageId : "string optional" } ,
316342 gate : { title : "string" , body : "string" , producerAgentId : "string" , consumerAgentId : "string" , ownerAgentId : "string" , requiredEvidence : "string[]" } ,
343+ gateStatus : { agentId : "string" , status : [ "open" , "waiting" , "satisfied" , "blocked" , "closed" ] , evidence : "string[] optional" } ,
317344 } ,
345+ dryRunKinds : [ "thread" , "createThread" , "thread-reply" , "thread_reply" , "direct_message" , "message" , "dm" , "directMessage" , "createDirectMessage" , "suggestion" , "createSuggestion" , "gate" , "createGate" , "gate-status" , "gateStatus" , "live-receipt" , "liveReceipt" ] ,
318346 responseWrappers : {
319347 thread : "POST /agent/threads" ,
320348 message : "POST /agent/direct-messages" ,
@@ -862,6 +890,7 @@ async function dryRun(request: Request, env: Env) {
862890 return json ( {
863891 ok : payloadValidation . ok && warnings . length === 0 && mentionValidation . ok !== false ,
864892 kind,
893+ normalizedKind : payloadValidation . normalizedKind ,
865894 payloadValidation,
866895 mentionValidation,
867896 warnings,
@@ -935,6 +964,32 @@ async function updateGate(request: Request, env: Env, gateId: string) {
935964 return json ( { gate : normalizeGate ( row ?? { } ) } ) ;
936965}
937966
967+ async function updateAgentGate ( request : Request , env : Env , gateId : string , auth ?: AuthContext ) {
968+ const db = requireDb ( env ) ;
969+ if ( ! db . ok ) return json ( { error : "Cross-project gates require durable storage." } , 503 ) ;
970+ const input = await body ( request ) ;
971+ const agentId = String ( input . agentId ?? ( auth ?. ok ? auth . agentId ?? "" : "" ) ) ;
972+ const agentAuth = await requireApprovedAgent ( db . db , agentId , auth ) ;
973+ if ( ! agentAuth . ok ) return agentAuth . response ;
974+ const gate = await db . db . prepare ( "SELECT * FROM cross_project_gates WHERE id = ?" ) . bind ( gateId ) . first < Row > ( ) ;
975+ if ( ! gate ) return json ( { error : "Gate not found." } , 404 ) ;
976+ const participants = [
977+ gate . created_by_agent_id ,
978+ gate . owner_agent_id ,
979+ gate . producer_agent_id ,
980+ gate . consumer_agent_id ,
981+ ] . filter ( Boolean ) . map ( String ) ;
982+ if ( ! participants . includes ( agentId ) ) return json ( { error : "Agent is not allowed to update this gate." } , 403 ) ;
983+ const status = String ( input . status ?? "" ) ;
984+ if ( ! [ "open" , "waiting" , "satisfied" , "blocked" , "closed" ] . includes ( status ) ) return json ( { error : "Invalid gate status." } , 400 ) ;
985+ await db . db
986+ . prepare ( "UPDATE cross_project_gates SET status = ?, evidence_json = COALESCE(?, evidence_json), updated_at = ? WHERE id = ?" )
987+ . bind ( status , input . evidence ? JSON . stringify ( input . evidence ) : null , now ( ) , gateId )
988+ . run ( ) ;
989+ const row = await db . db . prepare ( "SELECT * FROM cross_project_gates WHERE id = ?" ) . bind ( gateId ) . first < Row > ( ) ;
990+ return json ( { gate : normalizeGate ( row ?? { } ) } ) ;
991+ }
992+
938993async function voteSuggestion ( request : Request , env : Env , suggestionId : string , auth ?: AuthContext ) {
939994 const db = requireDb ( env ) ;
940995 const input = await body ( request ) ;
@@ -1526,6 +1581,9 @@ export async function onRequest(context: { request: Request; env: Env }) {
15261581 if ( method === "POST" && path === "agent/read-cursors" ) return markRead ( request , env , auth ) ;
15271582 if ( method === "GET" && path === "agent/gates" ) return listGates ( env , url . searchParams . get ( "status" ) ) ;
15281583 if ( method === "POST" && path === "agent/gates" ) return createGate ( request , env , auth ) ;
1584+ if ( method === "POST" && path . startsWith ( "agent/gates/" ) && path . endsWith ( "/status" ) ) {
1585+ return updateAgentGate ( request , env , path . split ( "/" ) . at ( - 2 ) ?? "" , auth ) ;
1586+ }
15291587 if ( method === "GET" && path . startsWith ( "agent/evidence/" ) ) {
15301588 return readEvidence ( env , path . split ( "/" ) . at ( - 1 ) ?? "" , auth , Number ( url . searchParams . get ( "hours" ) ?? 24 ) ) ;
15311589 }
0 commit comments