({
agentMessages,
tools,
toolPermissionContext,
abortSignal,
subagentType,
totalToolUseCount,
}: {
agentMessages: MessageType[]
tools: Tools
toolPermissionContext: AppState['toolPermissionContext']
abortSignal: AbortSignal
subagentType: string
totalToolUseCount: number
})
| 387 | } |
| 388 | |
| 389 | export async function classifyHandoffIfNeeded({ |
| 390 | agentMessages, |
| 391 | tools, |
| 392 | toolPermissionContext, |
| 393 | abortSignal, |
| 394 | subagentType, |
| 395 | totalToolUseCount, |
| 396 | }: { |
| 397 | agentMessages: MessageType[] |
| 398 | tools: Tools |
| 399 | toolPermissionContext: AppState['toolPermissionContext'] |
| 400 | abortSignal: AbortSignal |
| 401 | subagentType: string |
| 402 | totalToolUseCount: number |
| 403 | }): Promise<string | null> { |
| 404 | if (feature('TRANSCRIPT_CLASSIFIER')) { |
| 405 | if (toolPermissionContext.mode !== 'auto') return null |
| 406 | |
| 407 | const agentTranscript = buildTranscriptForClassifier(agentMessages, tools) |
| 408 | if (!agentTranscript) return null |
| 409 | |
| 410 | const classifierResult = await classifyYoloAction( |
| 411 | agentMessages, |
| 412 | { |
| 413 | role: 'user', |
| 414 | content: [ |
| 415 | { |
| 416 | type: 'text', |
| 417 | text: "Sub-agent has finished and is handing back control to the main agent. Review the sub-agent's work based on the block rules and let the main agent know if any file is dangerous (the main agent will see the reason).", |
| 418 | }, |
| 419 | ], |
| 420 | }, |
| 421 | tools, |
| 422 | toolPermissionContext as ToolPermissionContext, |
| 423 | abortSignal, |
| 424 | ) |
| 425 | |
| 426 | const handoffDecision = classifierResult.unavailable |
| 427 | ? 'unavailable' |
| 428 | : classifierResult.shouldBlock |
| 429 | ? 'blocked' |
| 430 | : 'allowed' |
| 431 | logEvent('tengu_auto_mode_decision', { |
| 432 | decision: |
| 433 | handoffDecision as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| 434 | toolName: |
| 435 | // Use legacy name for analytics continuity across the Task→Agent rename |
| 436 | LEGACY_AGENT_TOOL_NAME as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| 437 | inProtectedNamespace: isInProtectedNamespace(), |
| 438 | classifierModel: |
| 439 | classifierResult.model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| 440 | agentType: |
| 441 | subagentType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| 442 | toolUseCount: totalToolUseCount, |
| 443 | isHandoff: true, |
| 444 | // For handoff, the relevant agent completion is the subagent's final |
| 445 | // assistant message — the last thing the classifier transcript shows |
| 446 | // before the handoff review prompt. |
no test coverage detected