open-swe/agent/tools/enter_plan_mode.py
Ramon Nogueira ae04b72b41
feat: publish plans from sandbox files (#1635)
* feat: publish plans from sandbox files

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>

* fix: avoid fixed plan filenames

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>

* fix: virtualize local sandbox file paths

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>

* fix: preserve plan_file_path across set_plan_status

set_plan_status was rewriting the content record with only markdown
and status, dropping plan_file_path. After a reject, the owner's
dashboard edit would mirror to a different file than the agent's
original, and the next save_plan could republish the stale file.
Preserve plan_file_path when updating status.

---------

Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-29 13:02:55 -07:00

63 lines
2.5 KiB
Python

"""Tool: ``enter_plan_mode``. Switch the run into read-only planning."""
from __future__ import annotations
import logging
from typing import Annotated
from langchain_core.messages import ToolMessage
from langchain_core.tools import InjectedToolCallId
from langgraph.config import get_config
from langgraph.types import Command
from ..dashboard.plan_store import PLAN_STATUS_PLANNING, set_plan_status
logger = logging.getLogger(__name__)
_ENTERED_MESSAGE = (
"Plan mode is active. Stay read-only for the target repo: research the codebase, "
"create or edit a dated, concise plan file under `/workspace/plans/`, then publish "
"it with the `save_plan` tool and share the plan-review link in the source channel. "
"Do not edit repo files, commit, push, or open a PR — wait for the user to approve "
"the plan."
)
async def enter_plan_mode(tool_call_id: Annotated[str, InjectedToolCallId]) -> Command:
"""Activate plan mode mid-run.
Call this when you believe the task would benefit from a structured
implementation plan before writing any code — e.g. when the request is
complex, touches many files, or has multiple valid approaches. This is
NOT triggered by the word "plan" appearing in the request; use your
judgment about whether planning is genuinely warranted.
Once activated, stay read-only for the target repo: research the codebase,
create or edit a dated, concise Markdown plan outside any repo (for example,
``/workspace/plans/YYYY-MM-DD-short-task-slug.md``), then publish it with
the ``save_plan`` tool and share the plan-review link with the user. Do not
edit repo files, commit, push, or open a PR — the user reviews the plan and
approves it before you implement.
"""
thread_id = _thread_id_from_config()
if thread_id:
try:
await set_plan_status(thread_id, PLAN_STATUS_PLANNING, plan_mode=True)
except Exception:
logger.warning("Failed to persist plan-mode entry for %s", thread_id, exc_info=True)
return Command(
update={
"plan_mode": True,
"messages": [ToolMessage(content=_ENTERED_MESSAGE, tool_call_id=tool_call_id)],
}
)
def _thread_id_from_config() -> str | None:
try:
config = get_config()
except Exception:
return None
configurable = config.get("configurable", {}) if isinstance(config, dict) else {}
thread_id = configurable.get("thread_id") if isinstance(configurable, dict) else None
return str(thread_id) if thread_id else None