Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ All notable changes to MandoCode will be documented in this file.
- **Startup recovery for unavailable cloud models.** A missing or retired configured cloud model opens the picker automatically. One compact warning explains what to do; a valid replacement clears it.

### Changed
- **Plan mode is explicit in the CLI.** Only /plan starts structured plan generation and approval. Ordinary prompts run directly without automatic complexity detection or model-initiated plan proposals. Existing plan review and resume flows remain available; Desktop behavior is unchanged.
- **Organize service files.** Group command output, project directory state, session recovery, token tracking, and update/version services by responsibility. Remove obsolete tip-progress counters and configuration writes when switching agents.
- **Prioritize everyday startup commands.** Show planning, new agents with the Alt+N shortcut, and the keyboard reference alongside help, model selection, configuration, clear, music, and exit. Connection recovery commands remain in failure guidance; learning and compaction remain available in help and autocomplete.
- **Keep the project directory visible.** Each agent shows a folder icon and its project directory in purple beneath the input area, outside the scrolling conversation, including during menus and approvals. A dedicated single row keeps the directory visible in split panes, showing the full path when it fits and otherwise shortening the middle while preserving the drive and trailing folders. Click the folder icon or path to open the full project directory in the system file browser without changing agent focus. Startup no longer repeats the directory.
Expand Down
5 changes: 5 additions & 0 deletions docs/cli-agent-panes.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,8 @@ existing agents when the grid expands or a closed slot is reused. Incremental-re
checks exercise closing Agent 1 with two, three, or four agents, including App initialization
and disposal. Live terminal testing is still needed for focus, mouse controls, terminal
shortcuts, resizing, and concurrent tool approvals.


## Explicit plan mode

Use /plan <request> to generate a structured plan for review. Regular prompts run directly, even when they include multiple steps or ask for an outline in prose. The model cannot activate plan mode during an ordinary CLI turn. Existing plan editing, approval, execution, and /plan-resume remain available.
15 changes: 2 additions & 13 deletions src/MandoCode/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@
private bool _gitChangesOpen;
private TaskCompletionSource<string?>? _directorySelection;
private MandoCodeConfig Config { get; set; } = default!;
private TaskPlannerService TaskPlanner { get; set; } = default!;
private PlanRunnerSelector PlanRunners { get; set; } = default!;
private PlanHandoff PlanHandoff { get; set; } = default!;
private FileAutocompleteProvider FileProvider { get; set; } = default!;
Expand Down Expand Up @@ -368,8 +367,8 @@
{
var agentServices = Pane?.Services ?? Services;
AI = agentServices.GetRequiredService<AIService>();
AI.UseExplicitPlanningOnly();
Config = agentServices.GetRequiredService<MandoCodeConfig>();
TaskPlanner = agentServices.GetRequiredService<TaskPlannerService>();
PlanRunners = agentServices.GetRequiredService<PlanRunnerSelector>();
PlanHandoff = agentServices.GetRequiredService<PlanHandoff>();
FileProvider = agentServices.GetRequiredService<FileAutocompleteProvider>();
Expand Down Expand Up @@ -1554,23 +1553,13 @@
PlayMatrixMoment(input);
}

// Check if this request needs task planning BEFORE injecting file content,
// so that attached file size doesn't inflate the complexity heuristics
var planning = TaskPlanner.GetPlanningDecision(input);

// Process @file references and inject file content
var processedInput = ProcessFileReferences(input);

// Add user message to chat history
_messages.Add(new ChatMsg { Role = "user", Text = input });

if (planning.Required)
{
AnsiConsole.MarkupLine($"[dim]Planning automatically: {Spectre.Console.Markup.Escape(planning.Reason ?? "multi-step request")}.[/]");
await ForcePlanAsync(processedInput, input);
}
else
await ProcessDirectRequestAsync(processedInput);
await ProcessDirectRequestAsync(processedInput);
}
}

Expand Down
21 changes: 20 additions & 1 deletion src/MandoCode/Services/Ai/AIService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,15 @@ private void RebuildSystemPrompt()
{
var skillIndex = SystemPrompts.BuildSkillIndex(_skillLoader.GetAll());
_systemPrompt = SystemPrompts.BuildMandoCodeAssistant(_config.EnableWebSearch, _config.AgentName) + "\n\n" + ShellEnvironment.SystemPromptRules;
if (_explicitPlanningOnly)
{
var start = _systemPrompt.IndexOf("MULTI-STEP PLANNING:", StringComparison.Ordinal);
var end = _systemPrompt.IndexOf("FILE PATH RULES", start, StringComparison.Ordinal);
_systemPrompt = _systemPrompt.Remove(start, end - start).Insert(start,
"PLAN MODE:\nOnly the user's /plan command activates structured plan mode. For normal requests, carry out the work directly using available tools. You may explain an approach in prose, but do not call propose_plan or wait for plan approval.\n\n");
_systemPrompt = _systemPrompt.Replace("If the work spans several files or systems, use propose_plan (see MULTI-STEP PLANNING below) rather than narrating a plan in prose — a proposed plan is reviewable and gets executed for you; a described one is neither.",
"Carry out multi-step requests directly; structured plan mode is entered only through /plan.", StringComparison.Ordinal);
}
_systemPrompt += "\n\n" + VisionSupport.AgentInstruction();
if (!string.IsNullOrEmpty(skillIndex))
{
Expand Down Expand Up @@ -456,6 +465,16 @@ public async Task AttachMcpPluginsAsync(CancellationToken cancellationToken = de
/// Replaces the optional tools supplied by the host application and rebuilds the agent so
/// they participate in normal MAF function calling and fallback execution.
/// </summary>
private bool _explicitPlanningOnly;

/// <summary>CLI hosts reserve structured planning for /plan; explicit generation remains available.</summary>
public void UseExplicitPlanningOnly()
{
_explicitPlanningOnly = true;
RebuildSystemPrompt();
BuildAgent();
}

public void SetHostTools(IEnumerable<AIFunction>? tools)
{
_hostTools = tools?.ToArray() ?? Array.Empty<AIFunction>();
Expand Down Expand Up @@ -525,7 +544,7 @@ private void BuildAgent()
tools.Add(NamedTool(webSearchPlugin.FetchWebpage, "fetch_webpage"));
}

if (_config.EnableTaskPlanning)
if (_config.EnableTaskPlanning && !_explicitPlanningOnly)
{
var planningPlugin = new PlanningPlugin();
tools.Add(NamedTool(planningPlugin.ProposePlan, "propose_plan"));
Expand Down
42 changes: 42 additions & 0 deletions tests/MandoCode.Tests/ExplicitPlanningTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Reflection;
using MandoCode.Models;
using MandoCode.Services;
using Microsoft.Extensions.AI;
using Xunit;

namespace MandoCode.Tests;

public class ExplicitPlanningTests
{
private static AIService Create()
{
var config = new MandoCodeConfig { EnableTaskPlanning = true, AllowPersistence = false };
var root = new ProjectRootAccessor(Path.GetTempPath());
return new AIService(root, config, new TokenTrackingService(), new PlanHandoff(),
new SkillLoader(config, root), new McpClientManager(config), new McpApprovalGate(config), new SpinnerService());
}

private static IEnumerable<AIFunction> Tools(AIService ai) =>
(IEnumerable<AIFunction>)typeof(AIService).GetField("_agentFunctions", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(ai)!;

[Fact]
public void DesktopHostsRetainModelInitiatedPlanning()
{
var ai = Create();
Assert.Contains(Tools(ai), tool => tool.Name == "propose_plan");
}

[Fact]
public void CliNormalToolsAndPromptExcludeAutomaticPlanningAcrossRebuilds()
{
var ai = Create();
ai.UseExplicitPlanningOnly();
ai.SetHostTools([]); // Model/config/tool rebuilds must not re-enable planning.
Assert.DoesNotContain(Tools(ai), tool => tool.Name == "propose_plan");
Assert.Contains(Tools(ai), tool => tool.Name == "read_file_contents");
var prompt = (string)typeof(AIService).GetField("_systemPrompt", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(ai)!;
Assert.Contains("Only the user's /plan command", prompt);
Assert.DoesNotContain("call the propose_plan function BEFORE", prompt);
Assert.DoesNotContain("use propose_plan (see MULTI-STEP", prompt);
}
}