mirror of
https://github.com/NCBM/plyngent.git
synced 2026-07-25 08:04:57 +08:00
core/cli: catalog select, session state, tag-aware yolo
This commit is contained in:
+10
-5
@@ -323,15 +323,20 @@ async def _run_chat( # noqa: C901, PLR0912, PLR0915 — chat orchestration
|
|||||||
return EXIT_OK
|
return EXIT_OK
|
||||||
finally:
|
finally:
|
||||||
await memory.close()
|
await memory.close()
|
||||||
from plyngent.tools.process.pty_session import PtyManager
|
|
||||||
from plyngent.tools.temp_workspace import cleanup_temporary_workspaces
|
|
||||||
|
|
||||||
PtyManager.close_all()
|
|
||||||
from plyngent.tools.workspace import clear_policy_allowed_commands, set_policy_confirm_hook
|
from plyngent.tools.workspace import clear_policy_allowed_commands, set_policy_confirm_hook
|
||||||
|
|
||||||
set_policy_confirm_hook(None)
|
set_policy_confirm_hook(None)
|
||||||
clear_policy_allowed_commands()
|
clear_policy_allowed_commands()
|
||||||
_ = cleanup_temporary_workspaces()
|
# PTY + temp workspace cleanup via instance shutdown when state exists.
|
||||||
|
state_obj = locals().get("state")
|
||||||
|
if isinstance(state_obj, ReplState):
|
||||||
|
await state_obj.instance_state.shutdown()
|
||||||
|
else:
|
||||||
|
from plyngent.tools.process.pty_session import PtyManager
|
||||||
|
from plyngent.tools.temp_workspace import cleanup_temporary_workspaces
|
||||||
|
|
||||||
|
PtyManager.close_all()
|
||||||
|
_ = cleanup_temporary_workspaces()
|
||||||
|
|
||||||
|
|
||||||
def _configure_logging(level: str) -> None:
|
def _configure_logging(level: str) -> None:
|
||||||
|
|||||||
+42
-12
@@ -18,7 +18,13 @@ from plyngent.cli.models_source import (
|
|||||||
)
|
)
|
||||||
from plyngent.memory.database.store import normalize_workspace
|
from plyngent.memory.database.store import normalize_workspace
|
||||||
from plyngent.runtime import create_client
|
from plyngent.runtime import create_client
|
||||||
from plyngent.tools import DEFAULT_TOOLS, set_todo_stack, set_workspace_root
|
from plyngent.tools import (
|
||||||
|
InstanceState,
|
||||||
|
SessionState,
|
||||||
|
default_tool_definitions,
|
||||||
|
set_todo_stack,
|
||||||
|
set_workspace_root,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
@@ -58,6 +64,8 @@ class ReplState:
|
|||||||
agent: ChatAgent = field(init=False)
|
agent: ChatAgent = field(init=False)
|
||||||
session_id: int | None = None
|
session_id: int | None = None
|
||||||
todo_stack: TodoStack = field(default_factory=TodoStack)
|
todo_stack: TodoStack = field(default_factory=TodoStack)
|
||||||
|
instance_state: InstanceState = field(default_factory=InstanceState)
|
||||||
|
session_state: SessionState = field(default_factory=SessionState)
|
||||||
_todo_persist_tasks: set[object] = field(default_factory=set, init=False, repr=False)
|
_todo_persist_tasks: set[object] = field(default_factory=set, init=False, repr=False)
|
||||||
# Session ids for Tab complete (updated when listing/creating/resuming).
|
# Session ids for Tab complete (updated when listing/creating/resuming).
|
||||||
_session_id_cache: list[int] = field(default_factory=list, init=False, repr=False)
|
_session_id_cache: list[int] = field(default_factory=list, init=False, repr=False)
|
||||||
@@ -71,6 +79,8 @@ class ReplState:
|
|||||||
# DeepSeek client uses a compatible but distinct param type; treat as ChatClient.
|
# DeepSeek client uses a compatible but distinct param type; treat as ChatClient.
|
||||||
self.client = cast("ChatClient", cast("object", create_client(self.provider)))
|
self.client = cast("ChatClient", cast("object", create_client(self.provider)))
|
||||||
self.workspace = Path(self.workspace).expanduser().resolve()
|
self.workspace = Path(self.workspace).expanduser().resolve()
|
||||||
|
self.instance_state.workspace_root = self.workspace
|
||||||
|
self.session_state.todo = self.todo_stack
|
||||||
self.agent = self._make_agent()
|
self.agent = self._make_agent()
|
||||||
self.sync_display_flags()
|
self.sync_display_flags()
|
||||||
self._bind_todo_tools()
|
self._bind_todo_tools()
|
||||||
@@ -99,11 +109,17 @@ class ReplState:
|
|||||||
return self.effective_yolo() == "off"
|
return self.effective_yolo() == "off"
|
||||||
|
|
||||||
def set_yolo(self, mode: YoloMode) -> None:
|
def set_yolo(self, mode: YoloMode) -> None:
|
||||||
"""Set YOLO mode; rebuild tool registry when soft-confirm hooks change."""
|
"""Set YOLO mode; update registry YOLO bit (and rebuild if needed)."""
|
||||||
prev = self.soft_confirm_enabled()
|
prev = self.effective_yolo()
|
||||||
self.yolo = mode
|
self.yolo = mode
|
||||||
if prev != self.soft_confirm_enabled():
|
if prev != mode:
|
||||||
self.rebuild_client()
|
# Tag-aware confirm: YOLO only auto-approves YOLO-tagged tools.
|
||||||
|
if hasattr(self, "agent") and self.agent.tools is not None:
|
||||||
|
self.agent.tools.set_yolo(enabled=mode != "off")
|
||||||
|
else:
|
||||||
|
self.rebuild_client()
|
||||||
|
if mode == "off":
|
||||||
|
self.session_state.clear_grants()
|
||||||
|
|
||||||
def expire_yolo_once(self, *, quiet: bool = False) -> None:
|
def expire_yolo_once(self, *, quiet: bool = False) -> None:
|
||||||
"""If mode is ``once``, drop back to ``off`` after a user turn."""
|
"""If mode is ``once``, drop back to ``off`` after a user turn."""
|
||||||
@@ -131,7 +147,12 @@ class ReplState:
|
|||||||
self._todo_persist_tasks.add(task)
|
self._todo_persist_tasks.add(task)
|
||||||
task.add_done_callback(self._todo_persist_tasks.discard)
|
task.add_done_callback(self._todo_persist_tasks.discard)
|
||||||
|
|
||||||
|
self.session_state.todo = self.todo_stack
|
||||||
|
self.session_state.session_id = self.session_id
|
||||||
set_todo_stack(self.todo_stack, on_change=on_change)
|
set_todo_stack(self.todo_stack, on_change=on_change)
|
||||||
|
if hasattr(self, "agent") and self.agent.tools is not None:
|
||||||
|
self.agent.tools.set_session_state(self.session_state)
|
||||||
|
self.agent.tools.set_instance_state(self.instance_state)
|
||||||
|
|
||||||
async def persist_todo_stack(self) -> None:
|
async def persist_todo_stack(self) -> None:
|
||||||
"""Write the in-memory todo stack to the active session row."""
|
"""Write the in-memory todo stack to the active session row."""
|
||||||
@@ -159,13 +180,18 @@ class ReplState:
|
|||||||
from plyngent.cli.limits import prompt_confirm_tool_async
|
from plyngent.cli.limits import prompt_confirm_tool_async
|
||||||
from plyngent.tools.danger import classify_danger
|
from plyngent.tools.danger import classify_danger
|
||||||
|
|
||||||
if self.soft_confirm_enabled():
|
tools = default_tool_definitions(surface="local")
|
||||||
return ToolRegistry(
|
yolo = self.effective_yolo() != "off"
|
||||||
list(DEFAULT_TOOLS),
|
# Always attach soft-confirm path so non-YOLO tools still prompt under YOLO mode.
|
||||||
danger=classify_danger,
|
return ToolRegistry(
|
||||||
on_confirm=prompt_confirm_tool_async,
|
tools,
|
||||||
)
|
danger=classify_danger,
|
||||||
return ToolRegistry(list(DEFAULT_TOOLS))
|
on_confirm=prompt_confirm_tool_async,
|
||||||
|
yolo=yolo,
|
||||||
|
auto_bind_state=True,
|
||||||
|
instance_state=self.instance_state,
|
||||||
|
session_state=self.session_state,
|
||||||
|
)
|
||||||
|
|
||||||
def _make_agent(self) -> ChatAgent:
|
def _make_agent(self) -> ChatAgent:
|
||||||
from plyngent.cli.limits import prompt_continue_limit_async
|
from plyngent.cli.limits import prompt_continue_limit_async
|
||||||
@@ -370,7 +396,10 @@ class ReplState:
|
|||||||
msg = f"workspace is not a directory: {resolved}"
|
msg = f"workspace is not a directory: {resolved}"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
self.workspace = resolved
|
self.workspace = resolved
|
||||||
|
self.instance_state.workspace_root = resolved
|
||||||
_ = set_workspace_root(resolved)
|
_ = set_workspace_root(resolved)
|
||||||
|
if hasattr(self, "agent") and self.agent.tools is not None:
|
||||||
|
self.agent.tools.set_instance_state(self.instance_state)
|
||||||
|
|
||||||
def _apply_session_workspace(self, row: SessionRow) -> None:
|
def _apply_session_workspace(self, row: SessionRow) -> None:
|
||||||
"""Bind tools/REPL workspace to the session's directory when set."""
|
"""Bind tools/REPL workspace to the session's directory when set."""
|
||||||
@@ -476,6 +505,7 @@ class ReplState:
|
|||||||
if session.sid not in self._session_id_cache:
|
if session.sid not in self._session_id_cache:
|
||||||
self._session_id_cache.insert(0, session.sid)
|
self._session_id_cache.insert(0, session.sid)
|
||||||
self.todo_stack = TodoStack()
|
self.todo_stack = TodoStack()
|
||||||
|
self.session_state = SessionState(session_id=session.sid, todo=self.todo_stack)
|
||||||
self.agent = self._make_agent()
|
self.agent = self._make_agent()
|
||||||
self._bind_todo_tools()
|
self._bind_todo_tools()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user