regcode 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- regcode/__init__.py +5 -0
- regcode/cli.py +180 -0
- regcode/config.py +153 -0
- regcode/conversation_manager.py +154 -0
- regcode/main.py +893 -0
- regcode/monty_sandbox.py +415 -0
- regcode/permissions.py +27 -0
- regcode/sandbox.py +382 -0
- regcode/tools/__init__.py +13 -0
- regcode/tools/base.py +125 -0
- regcode/tools/builtins.py +947 -0
- regcode/tools/registry.py +78 -0
- regcode/tools/review_notes.py +122 -0
- regcode/tui.py +331 -0
- regcode-0.1.0.dist-info/METADATA +163 -0
- regcode-0.1.0.dist-info/RECORD +18 -0
- regcode-0.1.0.dist-info/WHEEL +4 -0
- regcode-0.1.0.dist-info/entry_points.txt +2 -0
regcode/__init__.py
ADDED
regcode/cli.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
import regcode
|
|
7
|
+
from regcode.permissions import AgentPermission
|
|
8
|
+
from regcode.tui import ChatUI
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _confirm(prompt_text):
|
|
12
|
+
"""Ask yes/no question, accepting y/n (case insensitive)."""
|
|
13
|
+
answer = click.prompt(
|
|
14
|
+
prompt_text, type=click.Choice(["y", "n"], case_sensitive=False), default="y"
|
|
15
|
+
)
|
|
16
|
+
return answer == "y"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.group(invoke_without_command=True)
|
|
20
|
+
@click.pass_context
|
|
21
|
+
def cli(ctx):
|
|
22
|
+
"""RegCode - Minimalistic Coding Agent"""
|
|
23
|
+
if ctx.invoked_subcommand is None:
|
|
24
|
+
ctx.invoke(chat, full=True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@cli.command()
|
|
28
|
+
@click.option("--model", default=None, help="Model to use (e.g. openai/gpt-4o)")
|
|
29
|
+
@click.option(
|
|
30
|
+
"--config", "config_path", default="config.yaml", help="Path to config.yaml"
|
|
31
|
+
)
|
|
32
|
+
@click.option("--stream", is_flag=True, help="Stream response")
|
|
33
|
+
@click.option(
|
|
34
|
+
"--full",
|
|
35
|
+
is_flag=True,
|
|
36
|
+
help="Enable full agent mode (read, write, execute permissions)",
|
|
37
|
+
)
|
|
38
|
+
@click.option(
|
|
39
|
+
"--no-color",
|
|
40
|
+
is_flag=True,
|
|
41
|
+
help="Disable colored output",
|
|
42
|
+
)
|
|
43
|
+
def chat(model, config_path, stream, full, no_color):
|
|
44
|
+
"""Chat with the agent."""
|
|
45
|
+
config = regcode.Config.load(config_path)
|
|
46
|
+
if model:
|
|
47
|
+
config.provider.model = model
|
|
48
|
+
if full:
|
|
49
|
+
config.permissions.append(AgentPermission.WRITE)
|
|
50
|
+
|
|
51
|
+
# Initialize the TUI
|
|
52
|
+
force_color = not no_color and sys.stdout.isatty()
|
|
53
|
+
ui = ChatUI(force_color=force_color)
|
|
54
|
+
|
|
55
|
+
agent = regcode.Agent(
|
|
56
|
+
config=config,
|
|
57
|
+
status_callback=ui.handle_status,
|
|
58
|
+
result_callback=ui.handle_result,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
ui.print_welcome(regcode.__version__)
|
|
62
|
+
|
|
63
|
+
while True:
|
|
64
|
+
try:
|
|
65
|
+
msg = ui.get_user_input()
|
|
66
|
+
if msg is None:
|
|
67
|
+
break # EOF
|
|
68
|
+
if msg.lower() in ("exit", "quit", "q"):
|
|
69
|
+
ui.print_exit()
|
|
70
|
+
break
|
|
71
|
+
if not msg:
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
# Ensure agent response starts on a fresh line
|
|
75
|
+
click.echo()
|
|
76
|
+
|
|
77
|
+
# Always stream in TUI so text appears progressively rather than
|
|
78
|
+
# all at once after the full tool loop completes.
|
|
79
|
+
ui.start_streaming()
|
|
80
|
+
agent.chat(msg, stream=True, on_text_chunk=ui.print_text_chunk)
|
|
81
|
+
ui.end_streaming()
|
|
82
|
+
|
|
83
|
+
ui.print_round_complete()
|
|
84
|
+
|
|
85
|
+
except KeyboardInterrupt:
|
|
86
|
+
ui.print_interrupted()
|
|
87
|
+
break
|
|
88
|
+
except EOFError:
|
|
89
|
+
ui.print_exit()
|
|
90
|
+
break
|
|
91
|
+
except Exception as e:
|
|
92
|
+
ui.formatter.error(str(e))
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@cli.command()
|
|
97
|
+
@click.option(
|
|
98
|
+
"--output", "output_path", default="config.yaml", help="Path for config.yaml"
|
|
99
|
+
)
|
|
100
|
+
def configure(output_path):
|
|
101
|
+
"""Interactive config generator for config.yaml."""
|
|
102
|
+
config = regcode.Config()
|
|
103
|
+
|
|
104
|
+
# --- Agent config ---
|
|
105
|
+
yes = _confirm("Configure agent settings?")
|
|
106
|
+
if yes:
|
|
107
|
+
context_window = click.prompt(
|
|
108
|
+
"Context window size",
|
|
109
|
+
type=int,
|
|
110
|
+
default=config.agent.context_window,
|
|
111
|
+
)
|
|
112
|
+
compaction_threshold = click.prompt(
|
|
113
|
+
"Compaction threshold",
|
|
114
|
+
type=float,
|
|
115
|
+
default=config.agent.compaction_threshold,
|
|
116
|
+
)
|
|
117
|
+
enable_compaction = _confirm("Enable compaction")
|
|
118
|
+
|
|
119
|
+
config.agent.context_window = context_window
|
|
120
|
+
config.agent.compaction_threshold = compaction_threshold
|
|
121
|
+
config.agent.enable_compaction = enable_compaction
|
|
122
|
+
else:
|
|
123
|
+
click.echo(" Using default agent settings.")
|
|
124
|
+
|
|
125
|
+
# --- Provider config ---
|
|
126
|
+
yes = _confirm("Configure API provider?")
|
|
127
|
+
if yes:
|
|
128
|
+
model = click.prompt("Model", default=config.provider.model)
|
|
129
|
+
base_url = click.prompt("Base URL", default=config.provider.base_url)
|
|
130
|
+
api_key = click.prompt(
|
|
131
|
+
"API key",
|
|
132
|
+
default=config.provider.api_key,
|
|
133
|
+
hide_input=True,
|
|
134
|
+
)
|
|
135
|
+
temperature = click.prompt(
|
|
136
|
+
"Temperature", type=float, default=config.provider.temperature
|
|
137
|
+
)
|
|
138
|
+
max_tokens = click.prompt(
|
|
139
|
+
"Max tokens", type=int, default=config.provider.max_tokens
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
config.provider.model = model
|
|
143
|
+
config.provider.base_url = base_url
|
|
144
|
+
config.provider.api_key = api_key
|
|
145
|
+
config.provider.temperature = temperature
|
|
146
|
+
config.provider.max_tokens = max_tokens
|
|
147
|
+
else:
|
|
148
|
+
click.echo(" Using default provider settings.")
|
|
149
|
+
|
|
150
|
+
# --- Write config.yaml ---
|
|
151
|
+
data = {
|
|
152
|
+
"agent": {
|
|
153
|
+
"context_window": config.agent.context_window,
|
|
154
|
+
"compaction_threshold": config.agent.compaction_threshold,
|
|
155
|
+
"enable_compaction": config.agent.enable_compaction,
|
|
156
|
+
},
|
|
157
|
+
"provider": {
|
|
158
|
+
"base_url": config.provider.base_url,
|
|
159
|
+
"api_key": config.provider.api_key,
|
|
160
|
+
"model": config.provider.model,
|
|
161
|
+
"temperature": config.provider.temperature,
|
|
162
|
+
"max_tokens": config.provider.max_tokens,
|
|
163
|
+
},
|
|
164
|
+
"permissions": [p.value for p in config.permissions],
|
|
165
|
+
"tool_budget": config.tool_budget,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
with open(output_path, "w") as f:
|
|
169
|
+
yaml.dump(data, f, default_flow_style=False)
|
|
170
|
+
click.echo(f"\nconfig.yaml written to {output_path}")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@cli.command()
|
|
174
|
+
def version():
|
|
175
|
+
"""Print version."""
|
|
176
|
+
click.echo(f"RegCode v{regcode.__version__}")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
cli(prog_name="regcode")
|
regcode/config.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from textwrap import dedent
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
from pydantic import BaseModel, ConfigDict
|
|
8
|
+
|
|
9
|
+
from regcode.permissions import AgentPermission
|
|
10
|
+
|
|
11
|
+
default_system_prompt = dedent(
|
|
12
|
+
"""
|
|
13
|
+
# RegCode coding agent
|
|
14
|
+
|
|
15
|
+
## Instructions
|
|
16
|
+
You are a coding agent. You have access to tools for reading and writing files,
|
|
17
|
+
executing code, and running tests. You can also use a sandboxed environment for
|
|
18
|
+
running untrusted code. You should use these tools to help you understand and
|
|
19
|
+
modify the codebase in the current directory.
|
|
20
|
+
|
|
21
|
+
When you need to read or write files, execute code, or run tests, you should use
|
|
22
|
+
the appropriate tool. You should not attempt to read or write files directly, or
|
|
23
|
+
execute code directly. You should also not attempt to run tests directly.
|
|
24
|
+
|
|
25
|
+
You should always be careful when executing code, as it may have side effects.
|
|
26
|
+
You should also be careful when reading or writing files, as it may affect the
|
|
27
|
+
state of the codebase.
|
|
28
|
+
|
|
29
|
+
Your goal is to help the user understand and modify the codebase in the current
|
|
30
|
+
directory. You should provide clear and concise explanations of the code and
|
|
31
|
+
suggest modifications that will improve the codebase.
|
|
32
|
+
|
|
33
|
+
Make sure to provide thorough explanations of your reasoning and the steps you
|
|
34
|
+
are taking.
|
|
35
|
+
|
|
36
|
+
## Review Format
|
|
37
|
+
Start directly with your review content. Do not address user or
|
|
38
|
+
use fillers like "Now I have enough information to provide a review." or
|
|
39
|
+
"Here is my review:"
|
|
40
|
+
"""
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class AgentConfig(BaseModel):
|
|
45
|
+
model_config = ConfigDict(extra="allow")
|
|
46
|
+
context_window: int = 128000
|
|
47
|
+
compaction_threshold: float = 0.8 # Compact when context > 80% of window
|
|
48
|
+
enable_compaction: bool = True
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ProviderConfig(BaseModel):
|
|
52
|
+
model_config = ConfigDict(extra="allow")
|
|
53
|
+
base_url: str = "https://api.openai.com/v1"
|
|
54
|
+
api_key: str = ""
|
|
55
|
+
temperature: float = 1
|
|
56
|
+
model: str = "openai/gpt-4o"
|
|
57
|
+
max_tokens: int = 4096
|
|
58
|
+
# Optional litellm completion kwargs (passed through to litellm.completion)
|
|
59
|
+
# See https://docs.litellm.ai/docs/completion/input for full list
|
|
60
|
+
extra_headers: dict | None = None
|
|
61
|
+
stop: list[str] | None = None
|
|
62
|
+
top_p: float | None = None
|
|
63
|
+
frequency_penalty: float | None = None
|
|
64
|
+
presence_penalty: float | None = None
|
|
65
|
+
logit_bias: dict | None = None
|
|
66
|
+
response_format: type[BaseModel] | None = None
|
|
67
|
+
seed: int | None = None
|
|
68
|
+
service_tier: str | None = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class SandboxConfig(BaseModel):
|
|
72
|
+
"""Configuration for the Monty sandbox."""
|
|
73
|
+
|
|
74
|
+
model_config = ConfigDict(extra="allow")
|
|
75
|
+
use_monty: bool = True
|
|
76
|
+
max_duration_secs: float = 10.0
|
|
77
|
+
max_memory_mb: int = 64
|
|
78
|
+
max_recursion_depth: int = 100
|
|
79
|
+
type_check: bool = True
|
|
80
|
+
allowed_imports: list[str] = []
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Config(BaseModel):
|
|
84
|
+
model_config = ConfigDict(extra="allow")
|
|
85
|
+
|
|
86
|
+
agent: AgentConfig = AgentConfig()
|
|
87
|
+
provider: ProviderConfig = ProviderConfig()
|
|
88
|
+
system_prompt: str = default_system_prompt
|
|
89
|
+
tools: dict = {}
|
|
90
|
+
sandbox: SandboxConfig = SandboxConfig()
|
|
91
|
+
tool_budget: int = 20
|
|
92
|
+
permissions: list[AgentPermission] = [AgentPermission.READ, AgentPermission.EXECUTE]
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def load(cls, path: str | None = None) -> "Config":
|
|
96
|
+
# get default home dir for user first
|
|
97
|
+
if path is None:
|
|
98
|
+
alt_config_path = Path.home() / ".regcode" / "config.yaml"
|
|
99
|
+
if alt_config_path.exists():
|
|
100
|
+
_path = alt_config_path
|
|
101
|
+
else:
|
|
102
|
+
_path = Path("config.yaml")
|
|
103
|
+
else:
|
|
104
|
+
_path = Path(path)
|
|
105
|
+
if not _path.exists():
|
|
106
|
+
return cls()
|
|
107
|
+
with open(_path) as f:
|
|
108
|
+
data = yaml.safe_load(f)
|
|
109
|
+
# Expand env vars in values
|
|
110
|
+
data = _expand_env_vars(data)
|
|
111
|
+
return cls(**data)
|
|
112
|
+
|
|
113
|
+
def get_litellm_completion_kwargs(self) -> dict[str, Any]:
|
|
114
|
+
"""Build litellm.completion kwargs from this config.
|
|
115
|
+
|
|
116
|
+
Combines model, provider, and agent config into a single dict
|
|
117
|
+
ready to pass to litellm.completion().
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
extra_kwargs: Additional kwargs to merge on top of the
|
|
121
|
+
config-derived values (e.g. 'messages', 'tools', 'stream').
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Dict of litellm.completion keyword arguments.
|
|
125
|
+
"""
|
|
126
|
+
extra_kwargs = {}
|
|
127
|
+
if hasattr(self.provider, "model_extra"):
|
|
128
|
+
extra_kwargs = self.provider.model_extra
|
|
129
|
+
result = self.provider.model_dump()
|
|
130
|
+
if extra_kwargs:
|
|
131
|
+
result.update(extra_kwargs)
|
|
132
|
+
# Agent-level config overrides provider defaults for known keys
|
|
133
|
+
if self.agent.context_window is not None:
|
|
134
|
+
result["context_window"] = self.agent.context_window
|
|
135
|
+
if hasattr(self.agent, "compaction_threshold"):
|
|
136
|
+
result["compaction_threshold"] = self.agent.compaction_threshold
|
|
137
|
+
if hasattr(self.agent, "enable_compaction"):
|
|
138
|
+
result["enable_compaction"] = self.agent.enable_compaction
|
|
139
|
+
# Apply agent-level extra fields (e.g. model, max_tokens, temperature
|
|
140
|
+
# from YAML agent section) as overrides over provider defaults
|
|
141
|
+
if hasattr(self.agent, "model_extra") and self.agent.model_extra:
|
|
142
|
+
result.update(self.agent.model_extra)
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _expand_env_vars(obj):
|
|
147
|
+
if isinstance(obj, str):
|
|
148
|
+
return os.path.expandvars(obj)
|
|
149
|
+
if isinstance(obj, dict):
|
|
150
|
+
return {k: _expand_env_vars(v) for k, v in obj.items()}
|
|
151
|
+
if isinstance(obj, list):
|
|
152
|
+
return [_expand_env_vars(v) for v in obj]
|
|
153
|
+
return obj
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Context window management with automatic compaction and review notes.
|
|
2
|
+
|
|
3
|
+
Handles context window limits by triggering compaction when the context
|
|
4
|
+
approaches the maximum size. Maintains review notes across compaction
|
|
5
|
+
for persistent findings about large repos.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import litellm
|
|
13
|
+
|
|
14
|
+
from regcode.config import Config
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConversationManager:
|
|
18
|
+
"""Manages conversation history with automatic compaction and review notes."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
) -> None:
|
|
23
|
+
c = Config.load()
|
|
24
|
+
self.c = c
|
|
25
|
+
self.context_window = c.agent.context_window
|
|
26
|
+
self.max_tokens = c.provider.max_tokens
|
|
27
|
+
self.compaction_threshold = c.agent.compaction_threshold
|
|
28
|
+
|
|
29
|
+
# Conversation messages
|
|
30
|
+
self.messages: list[dict[str, Any]] = []
|
|
31
|
+
|
|
32
|
+
# Review notes persist across compaction
|
|
33
|
+
self._review_notes: list[dict[str, Any]] = []
|
|
34
|
+
|
|
35
|
+
# Cached token estimate (invalidate on message/compaction changes)
|
|
36
|
+
self._cached_token_estimate: int | None = None
|
|
37
|
+
|
|
38
|
+
def add_message(self, message: dict[str, Any]) -> None:
|
|
39
|
+
"""Add a message to the context."""
|
|
40
|
+
self.messages.append(message)
|
|
41
|
+
self._cached_token_estimate = None
|
|
42
|
+
|
|
43
|
+
def should_compact(self) -> bool:
|
|
44
|
+
"""Check if context needs compaction."""
|
|
45
|
+
if self._cached_token_estimate is None:
|
|
46
|
+
self._cached_token_estimate = self._estimate_tokens()
|
|
47
|
+
# Ensure available space is always at least 1 to avoid edge cases
|
|
48
|
+
# where max_tokens >= context_window would make threshold zero.
|
|
49
|
+
available_space = max(1, self.context_window - self.max_tokens)
|
|
50
|
+
return self._cached_token_estimate > available_space * self.compaction_threshold
|
|
51
|
+
|
|
52
|
+
def compact(self) -> None:
|
|
53
|
+
"""Compact the context by keeping the first message, review notes,
|
|
54
|
+
and the most recent messages.
|
|
55
|
+
|
|
56
|
+
This preserves user instructions and accumulated review notes while
|
|
57
|
+
discarding older conversation history to stay within token limits.
|
|
58
|
+
"""
|
|
59
|
+
if not self.messages:
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
# Keep the first message (system prompt / user instruction),
|
|
63
|
+
# but strip any previously embedded review notes section so they
|
|
64
|
+
# don't appear twice when _build_system_prompt() re-appends them.
|
|
65
|
+
first_msg = self.messages[0]
|
|
66
|
+
if first_msg.get("role") == "system" and isinstance(
|
|
67
|
+
first_msg.get("content"), str
|
|
68
|
+
):
|
|
69
|
+
first_msg = {
|
|
70
|
+
"role": first_msg["role"],
|
|
71
|
+
"content": self._strip_review_notes(first_msg["content"]),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Keep the last N messages instead of just 1.
|
|
75
|
+
# Cap at total_remaining so we never include more than available,
|
|
76
|
+
# and use min with total_remaining to handle small histories safely.
|
|
77
|
+
total_remaining = len(self.messages) - 1
|
|
78
|
+
if total_remaining <= 0:
|
|
79
|
+
recent_count = 0
|
|
80
|
+
else:
|
|
81
|
+
# Retain at least 5 recent messages, or 1/3 of history, whichever
|
|
82
|
+
# is larger, capped at total_remaining.
|
|
83
|
+
recent_count = min(max(5, total_remaining // 3), total_remaining)
|
|
84
|
+
# Get the last N messages (these don't include index 0 since we
|
|
85
|
+
# already extracted first_msg)
|
|
86
|
+
recent_msgs = self.messages[-recent_count:] if recent_count > 0 else []
|
|
87
|
+
|
|
88
|
+
# Build compacted list: first message + recent messages
|
|
89
|
+
self.messages = [first_msg]
|
|
90
|
+
self.messages.extend(recent_msgs)
|
|
91
|
+
|
|
92
|
+
# Invalidate cached token estimate since message count changed
|
|
93
|
+
self._cached_token_estimate = None
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _strip_review_notes(text: str) -> str:
|
|
97
|
+
"""Remove the 'Prior Review Notes' section from a system prompt.
|
|
98
|
+
|
|
99
|
+
This prevents review notes from being duplicated after compaction
|
|
100
|
+
since they will be re-appended by _build_system_prompt() on the
|
|
101
|
+
next chat call via get_review_notes().
|
|
102
|
+
"""
|
|
103
|
+
marker = "\n\n### Prior Review Notes\n"
|
|
104
|
+
if marker not in text:
|
|
105
|
+
return text
|
|
106
|
+
# Find the marker and truncate the text at that point,
|
|
107
|
+
# then strip any trailing whitespace left behind
|
|
108
|
+
idx = text.index(marker)
|
|
109
|
+
return text[:idx].rstrip()
|
|
110
|
+
|
|
111
|
+
def _estimate_tokens(self) -> int:
|
|
112
|
+
"""Estimate token count from messages using litellm token counter.
|
|
113
|
+
|
|
114
|
+
Uses litellm's built-in token counting which accurately counts tokens
|
|
115
|
+
for the specific model, including system prompt and tool definitions.
|
|
116
|
+
"""
|
|
117
|
+
return litellm.token_counter(
|
|
118
|
+
model=self.c.provider.model,
|
|
119
|
+
messages=self.messages,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def add_review_note(
|
|
123
|
+
self,
|
|
124
|
+
title: str,
|
|
125
|
+
content: str,
|
|
126
|
+
importance: str = "medium",
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Add a review note (persists across compaction)."""
|
|
129
|
+
self._review_notes.append({
|
|
130
|
+
"title": title,
|
|
131
|
+
"content": content,
|
|
132
|
+
"importance": importance,
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
def get_review_notes(self) -> list[dict[str, Any]]:
|
|
136
|
+
"""Get all review notes."""
|
|
137
|
+
return list(self._review_notes)
|
|
138
|
+
|
|
139
|
+
def clear_review_notes(self) -> None:
|
|
140
|
+
"""Clear all review notes."""
|
|
141
|
+
self._review_notes = []
|
|
142
|
+
|
|
143
|
+
def reset(self) -> None:
|
|
144
|
+
"""Clear all messages and review notes."""
|
|
145
|
+
self.messages = []
|
|
146
|
+
self._review_notes = []
|
|
147
|
+
|
|
148
|
+
def get_messages(self) -> list[dict[str, Any]]:
|
|
149
|
+
"""Get current messages."""
|
|
150
|
+
return list(self.messages)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# Backwards-compatible alias for import paths that use 'context_manager'
|
|
154
|
+
ContextManager = ConversationManager
|