feat: add plan mode with restricted tool boundary (#24236)

> This PR was authored by Mux on behalf of Mike.

## Summary
- add persistent plan mode for chats and the chat-specific plan file
flow
- add structured planning tools such as `ask_user_question` and
`propose_plan`
- keep `write_file` and `edit_files` constrained to the chat-specific
plan file during plan turns
- allow shell exploration in plan mode, including subagents, via
`execute` and `process_output`
- block implementation-oriented, provider-native, MCP, dynamic, and
computer-use tools during plan turns
- update the chat UI, tests, and docs for the new planning flow
This commit is contained in:
Michael Suchacz
2026-04-16 11:12:01 +02:00
committed by GitHub
parent e996f6d44b
commit 1cf0354f72
76 changed files with 6398 additions and 889 deletions
+56
View File
@@ -1287,6 +1287,61 @@ func AllChatModeValues() []ChatMode {
}
}
type ChatPlanMode string
const (
ChatPlanModePlan ChatPlanMode = "plan"
)
func (e *ChatPlanMode) Scan(src interface{}) error {
switch s := src.(type) {
case []byte:
*e = ChatPlanMode(s)
case string:
*e = ChatPlanMode(s)
default:
return fmt.Errorf("unsupported scan type for ChatPlanMode: %T", src)
}
return nil
}
type NullChatPlanMode struct {
ChatPlanMode ChatPlanMode `json:"chat_plan_mode"`
Valid bool `json:"valid"` // Valid is true if ChatPlanMode is not NULL
}
// Scan implements the Scanner interface.
func (ns *NullChatPlanMode) Scan(value interface{}) error {
if value == nil {
ns.ChatPlanMode, ns.Valid = "", false
return nil
}
ns.Valid = true
return ns.ChatPlanMode.Scan(value)
}
// Value implements the driver Valuer interface.
func (ns NullChatPlanMode) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return string(ns.ChatPlanMode), nil
}
func (e ChatPlanMode) Valid() bool {
switch e {
case ChatPlanModePlan:
return true
}
return false
}
func AllChatPlanModeValues() []ChatPlanMode {
return []ChatPlanMode{
ChatPlanModePlan,
}
}
type ChatStatus string
const (
@@ -4247,6 +4302,7 @@ type Chat struct {
LastInjectedContext pqtype.NullRawMessage `db:"last_injected_context" json:"last_injected_context"`
DynamicTools pqtype.NullRawMessage `db:"dynamic_tools" json:"dynamic_tools"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
PlanMode NullChatPlanMode `db:"plan_mode" json:"plan_mode"`
}
type ChatDebugRun struct {