Download packages/core/src/tools/exit-plan-mode.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 8.02 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/core/src/tools/exit-plan-mode.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/core/src/tools/exit-plan-mode.ts
-
curl -L -o exit-plan-mode.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/core/src/tools/exit-plan-mode.ts
8.02 kB
| /** | |
| * @license | |
| * Copyright 2026 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { | |
| BaseDeclarativeTool, | |
| BaseToolInvocation, | |
| Kind, | |
| ToolConfirmationOutcome, | |
| type ToolConfirmationPayload, | |
| type ToolExitPlanModeConfirmationDetails, | |
| type ToolExitPlanModeConfirmationPayload, | |
| type ToolResult, | |
| type ExecuteOptions, | |
| } from './tools.js'; | |
| import type { MessageBus } from '../confirmation-bus/message-bus.js'; | |
| import path from 'node:path'; | |
| import type { Config } from '../config/config.js'; | |
| import { EXIT_PLAN_MODE_TOOL_NAME } from './tool-names.js'; | |
| import { | |
| validatePlanPath, | |
| validatePlanContent, | |
| resolveAndValidatePlanPath, | |
| } from '../utils/planUtils.js'; | |
| import { ApprovalMode } from '../policy/types.js'; | |
| import { logPlanExecution } from '../telemetry/loggers.js'; | |
| import { PlanExecutionEvent } from '../telemetry/types.js'; | |
| import { getExitPlanModeDefinition } from './definitions/coreTools.js'; | |
| import { resolveToolDeclaration } from './definitions/resolver.js'; | |
| import { getPlanModeExitMessage } from '../utils/approvalModeUtils.js'; | |
| export interface ExitPlanModeParams { | |
| plan_filename: string; | |
| } | |
| export class ExitPlanModeTool extends BaseDeclarativeTool< | |
| ExitPlanModeParams, | |
| ToolResult | |
| > { | |
| static readonly Name = EXIT_PLAN_MODE_TOOL_NAME; | |
| constructor( | |
| private config: Config, | |
| messageBus: MessageBus, | |
| ) { | |
| const definition = getExitPlanModeDefinition(); | |
| super( | |
| ExitPlanModeTool.Name, | |
| 'Exit Plan Mode', | |
| definition.base.description!, | |
| Kind.Plan, | |
| definition.base.parametersJsonSchema, | |
| messageBus, | |
| ); | |
| } | |
| protected override validateToolParamValues( | |
| params: ExitPlanModeParams, | |
| ): string | null { | |
| if (!params.plan_filename || params.plan_filename.trim() === '') { | |
| return 'plan_filename is required.'; | |
| } | |
| try { | |
| resolveAndValidatePlanPath( | |
| params.plan_filename, | |
| this.config.storage.getPlansDir(), | |
| this.config.getProjectRoot(), | |
| ); | |
| } catch (e) { | |
| return e instanceof Error ? e.message : String(e); | |
| } | |
| return null; | |
| } | |
| protected createInvocation( | |
| params: ExitPlanModeParams, | |
| messageBus: MessageBus, | |
| toolName: string, | |
| toolDisplayName: string, | |
| ): ExitPlanModeInvocation { | |
| return new ExitPlanModeInvocation( | |
| params, | |
| messageBus, | |
| toolName, | |
| toolDisplayName, | |
| this.config, | |
| ); | |
| } | |
| override getSchema(modelId?: string) { | |
| return resolveToolDeclaration(getExitPlanModeDefinition(), modelId); | |
| } | |
| } | |
| export class ExitPlanModeInvocation extends BaseToolInvocation< | |
| ExitPlanModeParams, | |
| ToolResult | |
| > { | |
| private confirmationOutcome: ToolConfirmationOutcome | null = null; | |
| private approvalPayload: ToolExitPlanModeConfirmationPayload | null = null; | |
| private planValidationError: string | null = null; | |
| constructor( | |
| params: ExitPlanModeParams, | |
| messageBus: MessageBus, | |
| toolName: string, | |
| toolDisplayName: string, | |
| private config: Config, | |
| ) { | |
| super(params, messageBus, toolName, toolDisplayName); | |
| } | |
| override async shouldConfirmExecute( | |
| abortSignal: AbortSignal, | |
| ): Promise<ToolExitPlanModeConfirmationDetails | false> { | |
| const resolvedPlanPath = this.getResolvedPlanPath(); | |
| const pathError = await validatePlanPath( | |
| this.params.plan_filename, | |
| this.config.storage.getPlansDir(), | |
| this.config.getProjectRoot(), | |
| ); | |
| if (pathError) { | |
| this.planValidationError = pathError; | |
| return false; | |
| } | |
| const contentError = await validatePlanContent(resolvedPlanPath); | |
| if (contentError) { | |
| this.planValidationError = contentError; | |
| return false; | |
| } | |
| const decision = await this.getMessageBusDecision(abortSignal); | |
| if (decision === 'deny') { | |
| throw new Error( | |
| `Tool execution for "${ | |
| this._toolDisplayName || this._toolName | |
| }" denied by policy.`, | |
| ); | |
| } | |
| if (decision === 'allow') { | |
| // If policy is allow, auto-approve with default settings and execute. | |
| this.confirmationOutcome = ToolConfirmationOutcome.ProceedOnce; | |
| this.approvalPayload = { | |
| approved: true, | |
| approvalMode: this.getAllowApprovalMode(), | |
| }; | |
| return false; | |
| } | |
| // decision is 'ask_user' | |
| return { | |
| type: 'exit_plan_mode', | |
| title: 'Plan Approval', | |
| planPath: resolvedPlanPath, | |
| onConfirm: async ( | |
| outcome: ToolConfirmationOutcome, | |
| payload?: ToolConfirmationPayload, | |
| ) => { | |
| this.confirmationOutcome = outcome; | |
| if (payload && 'approved' in payload) { | |
| this.approvalPayload = payload; | |
| } | |
| }, | |
| }; | |
| } | |
| getDescription(): string { | |
| return `Requesting plan approval for: ${path.join(this.config.storage.getPlansDir(), this.params.plan_filename)}`; | |
| } | |
| /** | |
| * Returns the resolved plan path. | |
| * Note: Validation is done in validateToolParamValues, so this assumes the path is valid. | |
| */ | |
| private getResolvedPlanPath(): string { | |
| return resolveAndValidatePlanPath( | |
| this.params.plan_filename, | |
| this.config.storage.getPlansDir(), | |
| this.config.getProjectRoot(), | |
| ); | |
| } | |
| async execute({ abortSignal: _signal }: ExecuteOptions): Promise<ToolResult> { | |
| const resolvedPlanPath = this.getResolvedPlanPath(); | |
| if (this.planValidationError) { | |
| return { | |
| llmContent: this.planValidationError, | |
| returnDisplay: 'Error: Invalid plan', | |
| }; | |
| } | |
| if (this.confirmationOutcome === ToolConfirmationOutcome.Cancel) { | |
| return { | |
| llmContent: | |
| 'User cancelled the plan approval dialog. The plan was not approved and you are still in Plan Mode.', | |
| returnDisplay: 'Cancelled', | |
| }; | |
| } | |
| // When a user policy grants `allow` for exit_plan_mode, the scheduler | |
| // skips the confirmation phase entirely and shouldConfirmExecute is never | |
| // called, leaving approvalPayload null. | |
| const payload = this.approvalPayload ?? { | |
| approved: true, | |
| approvalMode: this.getAllowApprovalMode(), | |
| }; | |
| if (payload.approved) { | |
| const newMode = payload.approvalMode ?? ApprovalMode.DEFAULT; | |
| if (newMode === ApprovalMode.PLAN) { | |
| throw new Error(`Unexpected approval mode: ${newMode}`); | |
| } | |
| this.config.setApprovalMode(newMode); | |
| this.config.setApprovedPlanPath(resolvedPlanPath); | |
| logPlanExecution(this.config, new PlanExecutionEvent(newMode)); | |
| const exitMessage = getPlanModeExitMessage(newMode); | |
| return { | |
| llmContent: `${exitMessage} | |
| The approved implementation plan is stored at: ${resolvedPlanPath} | |
| Read and follow the plan strictly during implementation.`, | |
| returnDisplay: `Plan approved: ${resolvedPlanPath}`, | |
| }; | |
| } else { | |
| const feedback = payload?.feedback?.trim(); | |
| if (feedback) { | |
| return { | |
| llmContent: `Plan rejected. User feedback: ${feedback} | |
| The plan is stored at: ${resolvedPlanPath} | |
| Revise the plan based on the feedback.`, | |
| returnDisplay: `Feedback: ${feedback}`, | |
| }; | |
| } else { | |
| return { | |
| llmContent: `Plan rejected. No feedback provided. | |
| The plan is stored at: ${resolvedPlanPath} | |
| Ask the user for specific feedback on how to improve the plan.`, | |
| returnDisplay: 'Rejected (no feedback)', | |
| }; | |
| } | |
| } | |
| } | |
| /** | |
| * Determines the approval mode to switch to when plan mode is exited via a policy ALLOW. | |
| * In non-interactive environments, this defaults to YOLO to allow automated execution. | |
| */ | |
| private getAllowApprovalMode(): ApprovalMode { | |
| if (!this.config.isInteractive()) { | |
| // For non-interactive environment requires minimal user action, exit as YOLO mode for plan implementation. | |
| return ApprovalMode.YOLO; | |
| } | |
| // By default, YOLO mode in interactive environment cannot enter/exit plan mode. | |
| // Always exit plan mode and move to default approval mode if exit_plan_mode tool is configured with allow decision. | |
| return ApprovalMode.DEFAULT; | |
| } | |
| } | |