@@ -10,11 +10,15 @@ import {
1010 ASYNC_TOOL_STATUS ,
1111 type AsyncCompletionData ,
1212 type AsyncConfirmationStatus ,
13+ isDeliveredAsyncStatus ,
14+ isTerminalAsyncStatus ,
15+ isWorkflowToolExecutionClaimable ,
1316} from '@/lib/copilot/async-runs/lifecycle'
1417import {
1518 completeAsyncToolCall ,
1619 detachAsyncToolCall ,
1720 getAsyncToolCall ,
21+ getClaimedWorkflowExecutionId ,
1822 getRunSegment ,
1923} from '@/lib/copilot/async-runs/repository'
2024import { CopilotConfirmOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
@@ -35,11 +39,14 @@ import {
3539} from '@/lib/copilot/request/tools/client-completion-seal.server'
3640import {
3741 createStructuralWorkflowToolCompletionData ,
42+ getWorkflowToolCompletionExecutionId ,
3843 getWorkflowToolCompletionMessage ,
44+ getWorkflowToolConfirmationStatus ,
3945 isWorkflowToolName ,
4046 resolveWorkflowToolTargetId ,
4147} from '@/lib/copilot/tools/workflow-tools'
4248import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
49+ import { getTrustedWorkflowToolExecution } from '@/lib/workflows/executor/execution-state'
4350
4451const logger = createLogger ( 'CopilotConfirmAPI' )
4552
@@ -50,6 +57,14 @@ function getClientToolCompletionMessage(status: AsyncConfirmationStatus): string
5057 return 'Tool failed'
5158}
5259
60+ function createConfirmationResponse (
61+ toolCallId : string ,
62+ status : AsyncConfirmationStatus ,
63+ message : string
64+ ) : NextResponse {
65+ return NextResponse . json ( { success : true , message, toolCallId, status } )
66+ }
67+
5368/** Atomically finalize or detach a client tool before publishing its wakeup event. */
5469async function updateToolCallStatus (
5570 existing : NonNullable < Awaited < ReturnType < typeof getAsyncToolCall > > > ,
@@ -61,7 +76,9 @@ async function updateToolCallStatus(
6176 const toolCallId = existing . toolCallId
6277 try {
6378 if ( status === ASYNC_TOOL_CONFIRMATION_STATUS . background ) {
64- const detached = await detachAsyncToolCall ( toolCallId )
79+ const detached = executionId
80+ ? await detachAsyncToolCall ( toolCallId , { preserveClaim : true } )
81+ : await detachAsyncToolCall ( toolCallId )
6582 if ( ! detached ) return false
6683 publishToolConfirmation ( {
6784 toolCallId,
@@ -143,7 +160,13 @@ export const POST = withRouteHandler((req: NextRequest) => {
143160 }
144161 )
145162 if ( ! parsed . success ) return parsed . response
146- const { toolCallId, executionId, status, message, data } = parsed . data . body
163+ const {
164+ toolCallId,
165+ executionId : submittedExecutionId ,
166+ status,
167+ message,
168+ data,
169+ } = parsed . data . body
147170 span . setAttributes ( {
148171 [ TraceAttr . ToolCallId ] : toolCallId ,
149172 [ TraceAttr . ToolConfirmationStatus ] : status ,
@@ -181,22 +204,142 @@ export const POST = withRouteHandler((req: NextRequest) => {
181204 return NextResponse . json ( { error : 'Forbidden' } , { status : 403 } )
182205 }
183206
207+ const isWorkflowTool = isWorkflowToolName ( existing . toolName || '' )
208+ const workflowId = isWorkflowTool
209+ ? resolveWorkflowToolTargetId ( existing . args , run . workflowId )
210+ : undefined
211+
212+ if ( isWorkflowTool && isTerminalAsyncStatus ( existing . status ) ) {
213+ const executionId = getWorkflowToolCompletionExecutionId ( existing . result )
214+ if (
215+ executionId &&
216+ submittedExecutionId !== undefined &&
217+ submittedExecutionId !== executionId
218+ ) {
219+ span . setAttribute (
220+ TraceAttr . CopilotConfirmOutcome ,
221+ CopilotConfirmOutcome . ToolCallNotFound
222+ )
223+ return createNotFoundResponse ( 'Completed workflow execution not found' )
224+ }
225+
226+ const terminalStatus = getWorkflowToolConfirmationStatus ( existing . status )
227+ span . setAttributes ( {
228+ [ TraceAttr . ToolConfirmationStatus ] : terminalStatus ,
229+ [ TraceAttr . CopilotConfirmOutcome ] : CopilotConfirmOutcome . Delivered ,
230+ } )
231+ return createConfirmationResponse (
232+ toolCallId ,
233+ terminalStatus ,
234+ getWorkflowToolCompletionMessage ( terminalStatus )
235+ )
236+ }
237+
238+ if ( isWorkflowTool && isDeliveredAsyncStatus ( existing . status ) ) {
239+ const claimedExecutionId = getClaimedWorkflowExecutionId ( existing . claimedBy )
240+ if (
241+ claimedExecutionId &&
242+ submittedExecutionId !== undefined &&
243+ submittedExecutionId !== claimedExecutionId
244+ ) {
245+ span . setAttribute (
246+ TraceAttr . CopilotConfirmOutcome ,
247+ CopilotConfirmOutcome . ToolCallNotFound
248+ )
249+ return createNotFoundResponse ( 'Bound workflow tool call not found' )
250+ }
251+
252+ span . setAttributes ( {
253+ [ TraceAttr . ToolConfirmationStatus ] : ASYNC_TOOL_CONFIRMATION_STATUS . background ,
254+ [ TraceAttr . CopilotConfirmOutcome ] : CopilotConfirmOutcome . Delivered ,
255+ } )
256+ return createConfirmationResponse (
257+ toolCallId ,
258+ ASYNC_TOOL_CONFIRMATION_STATUS . background ,
259+ getWorkflowToolCompletionMessage ( ASYNC_TOOL_CONFIRMATION_STATUS . background )
260+ )
261+ }
262+
263+ const isUnboundTerminalWorkflowOutcome =
264+ status === ASYNC_TOOL_CONFIRMATION_STATUS . error ||
265+ status === ASYNC_TOOL_CONFIRMATION_STATUS . cancelled
266+ const isMutableClientToolCall = isWorkflowTool
267+ ? isWorkflowToolExecutionClaimable ( existing . status , existing . permissionDecision )
268+ : existing . status === ASYNC_TOOL_STATUS . running
184269 if (
185- ( isBrowserToolName ( existing . toolName ) || isTerminalToolName ( existing . toolName ) ) &&
186- existing . status !== ASYNC_TOOL_STATUS . running
270+ ( isBrowserToolName ( existing . toolName ) ||
271+ isTerminalToolName ( existing . toolName ) ||
272+ isWorkflowTool ) &&
273+ ! isMutableClientToolCall
187274 ) {
188275 span . setAttribute ( TraceAttr . CopilotConfirmOutcome , CopilotConfirmOutcome . ToolCallNotFound )
189276 return createNotFoundResponse ( 'Running client tool call not found' )
190277 }
191278
192- const isWorkflowTool = isWorkflowToolName ( existing . toolName || '' )
193- const workflowId = isWorkflowTool
194- ? resolveWorkflowToolTargetId ( existing . args , run . workflowId )
195- : undefined
279+ let effectiveStatus = status
280+ let executionId = submittedExecutionId
281+
282+ if ( isWorkflowTool ) {
283+ const claimedExecutionId = getClaimedWorkflowExecutionId ( existing . claimedBy )
284+ const hasForeignClaim =
285+ existing . claimedBy !== null && existing . claimedBy !== undefined && ! claimedExecutionId
286+
287+ if (
288+ hasForeignClaim ||
289+ ( claimedExecutionId &&
290+ submittedExecutionId !== undefined &&
291+ submittedExecutionId !== claimedExecutionId )
292+ ) {
293+ span . setAttribute (
294+ TraceAttr . CopilotConfirmOutcome ,
295+ CopilotConfirmOutcome . ToolCallNotFound
296+ )
297+ return createNotFoundResponse ( 'Bound workflow tool call not found' )
298+ }
299+
300+ const candidateExecutionId = claimedExecutionId ?? submittedExecutionId
301+ const trustedExecution =
302+ status !== ASYNC_TOOL_CONFIRMATION_STATUS . background &&
303+ candidateExecutionId &&
304+ workflowId
305+ ? await getTrustedWorkflowToolExecution ( candidateExecutionId , workflowId , toolCallId )
306+ : null
307+
308+ if ( claimedExecutionId ) {
309+ executionId = claimedExecutionId
310+ if ( status !== ASYNC_TOOL_CONFIRMATION_STATUS . background ) {
311+ if ( trustedExecution ) {
312+ effectiveStatus = getWorkflowToolConfirmationStatus ( trustedExecution . status )
313+ } else if ( ! isUnboundTerminalWorkflowOutcome ) {
314+ span . setAttribute (
315+ TraceAttr . CopilotConfirmOutcome ,
316+ CopilotConfirmOutcome . ToolCallNotFound
317+ )
318+ return createNotFoundResponse ( 'Completed workflow execution not found' )
319+ }
320+ }
321+ } else if ( status === ASYNC_TOOL_CONFIRMATION_STATUS . background ) {
322+ executionId = submittedExecutionId
323+ } else if ( trustedExecution ) {
324+ executionId = trustedExecution . executionId
325+ effectiveStatus = getWorkflowToolConfirmationStatus ( trustedExecution . status )
326+ } else if ( ! isUnboundTerminalWorkflowOutcome ) {
327+ effectiveStatus = ASYNC_TOOL_CONFIRMATION_STATUS . error
328+ executionId = undefined
329+ } else {
330+ executionId = undefined
331+ }
332+ }
333+
334+ span . setAttribute ( TraceAttr . ToolConfirmationStatus , effectiveStatus )
196335 const projected = isWorkflowTool
197336 ? {
198- message : getWorkflowToolCompletionMessage ( status ) ,
199- data : createStructuralWorkflowToolCompletionData ( status , workflowId , executionId ) ,
337+ message : getWorkflowToolCompletionMessage ( effectiveStatus ) ,
338+ data : createStructuralWorkflowToolCompletionData (
339+ effectiveStatus ,
340+ workflowId ,
341+ executionId
342+ ) ,
200343 }
201344 : {
202345 message : getClientToolCompletionMessage ( status ) ,
@@ -214,7 +357,7 @@ export const POST = withRouteHandler((req: NextRequest) => {
214357
215358 const updated = await updateToolCallStatus (
216359 existing ,
217- status ,
360+ effectiveStatus ,
218361 projected . message ,
219362 projected . data ,
220363 isWorkflowTool ? executionId : undefined
@@ -224,8 +367,8 @@ export const POST = withRouteHandler((req: NextRequest) => {
224367 logger . error ( `[${ tracker . requestId } ] Failed to update tool call status` , {
225368 userId : authenticatedUserId ,
226369 toolCallId,
227- status,
228- internalStatus : status ,
370+ status : effectiveStatus ,
371+ internalStatus : effectiveStatus ,
229372 message : projected . message ,
230373 } )
231374 span . setAttribute ( TraceAttr . CopilotConfirmOutcome , CopilotConfirmOutcome . UpdateFailed )
@@ -234,12 +377,11 @@ export const POST = withRouteHandler((req: NextRequest) => {
234377 }
235378
236379 span . setAttribute ( TraceAttr . CopilotConfirmOutcome , CopilotConfirmOutcome . Delivered )
237- return NextResponse . json ( {
238- success : true ,
239- message : projected . message || `Tool call ${ toolCallId } has been ${ status . toLowerCase ( ) } ` ,
380+ return createConfirmationResponse (
240381 toolCallId ,
241- status,
242- } )
382+ effectiveStatus ,
383+ projected . message || `Tool call ${ toolCallId } has been ${ effectiveStatus . toLowerCase ( ) } `
384+ )
243385 } catch ( error ) {
244386 const duration = tracker . getDuration ( )
245387
0 commit comments