| import { feature } from 'bun:bundle' |
| import { z } from 'zod/v4' |
| import { getSessionId } from '../../bootstrap/state.js' |
| import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js' |
| import { buildTool, type ToolDef } from '../../Tool.js' |
| import { lazySchema } from '../../utils/lazySchema.js' |
| import { isTodoV2Enabled } from '../../utils/tasks.js' |
| import { TodoListSchema } from '../../utils/todo/types.js' |
| import { VERIFICATION_AGENT_TYPE } from '../AgentTool/constants.js' |
| import { TODO_WRITE_TOOL_NAME } from './constants.js' |
| import { DESCRIPTION, PROMPT } from './prompt.js' |
|
|
| const inputSchema = lazySchema(() => |
| z.strictObject({ |
| todos: TodoListSchema().describe('The updated todo list'), |
| }), |
| ) |
| type InputSchema = ReturnType<typeof inputSchema> |
|
|
| const outputSchema = lazySchema(() => |
| z.object({ |
| oldTodos: TodoListSchema().describe('The todo list before the update'), |
| newTodos: TodoListSchema().describe('The todo list after the update'), |
| verificationNudgeNeeded: z.boolean().optional(), |
| }), |
| ) |
| type OutputSchema = ReturnType<typeof outputSchema> |
|
|
| export type Output = z.infer<OutputSchema> |
|
|
| export const TodoWriteTool = buildTool({ |
| name: TODO_WRITE_TOOL_NAME, |
| searchHint: 'manage the session task checklist', |
| maxResultSizeChars: 100_000, |
| strict: true, |
| async description() { |
| return DESCRIPTION |
| }, |
| async prompt() { |
| return PROMPT |
| }, |
| get inputSchema(): InputSchema { |
| return inputSchema() |
| }, |
| get outputSchema(): OutputSchema { |
| return outputSchema() |
| }, |
| userFacingName() { |
| return '' |
| }, |
| shouldDefer: true, |
| isEnabled() { |
| return !isTodoV2Enabled() |
| }, |
| toAutoClassifierInput(input) { |
| return `${input.todos.length} items` |
| }, |
| async checkPermissions(input) { |
| |
| return { behavior: 'allow', updatedInput: input } |
| }, |
| renderToolUseMessage() { |
| return null |
| }, |
| async call({ todos }, context) { |
| const appState = context.getAppState() |
| const todoKey = context.agentId ?? getSessionId() |
| const oldTodos = appState.todos[todoKey] ?? [] |
| const allDone = todos.every(_ => _.status === 'completed') |
| const newTodos = allDone ? [] : todos |
|
|
| |
| |
| |
| |
| let verificationNudgeNeeded = false |
| if ( |
| feature('VERIFICATION_AGENT') && |
| getFeatureValue_CACHED_MAY_BE_STALE('tengu_hive_evidence', false) && |
| !context.agentId && |
| allDone && |
| todos.length >= 3 && |
| !todos.some(t => /verif/i.test(t.content)) |
| ) { |
| verificationNudgeNeeded = true |
| } |
|
|
| context.setAppState(prev => ({ |
| ...prev, |
| todos: { |
| ...prev.todos, |
| [todoKey]: newTodos, |
| }, |
| })) |
|
|
| return { |
| data: { |
| oldTodos, |
| newTodos: todos, |
| verificationNudgeNeeded, |
| }, |
| } |
| }, |
| mapToolResultToToolResultBlockParam({ verificationNudgeNeeded }, toolUseID) { |
| const base = `Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable` |
| const nudge = verificationNudgeNeeded |
| ? `\n\nNOTE: You just closed out 3+ tasks and none of them was a verification step. Before writing your final summary, spawn the verification agent (subagent_type="${VERIFICATION_AGENT_TYPE}"). You cannot self-assign PARTIAL by listing caveats in your summary \u2014 only the verifier issues a verdict.` |
| : '' |
| return { |
| tool_use_id: toolUseID, |
| type: 'tool_result', |
| content: base + nudge, |
| } |
| }, |
| } satisfies ToolDef<InputSchema, Output>) |
|
|