feat(copilot): implement 5-turn chat history & internal agent notes (<notes>) for CLI & Command Center (v6.2.0)
- Add multi-turn conversation memory (sliding window of last 5 turns / 10 messages) for Terminal Copilot.
- Introduce <notes> XML tag in Copilot response schema for tracking internal agent reasoning, tool usage, and key facts across turns.
- Instruct Copilot via System Prompt about the 5-turn pruning window and mandate summarizing critical facts in <notes>.
- Extend Command Center frontend (types.ts, useAISession.ts, AIPanel.tsx) with expandable "🧠 Internal Agent Notes / Memory" accordion and browser console response debug logging.
- Ensure 100% transparent serialization across local CLI execution, remote gRPC streams, and Web UI.
- Update documentation and bump version to v6.2.0.
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
# Connpy (v6.1.1)
|
# Connpy (v6.2.0)
|
||||||
[](https://pypi.org/pypi/connpy/)
|
[](https://pypi.org/pypi/connpy/)
|
||||||
[](https://pypi.org/pypi/connpy/)
|
[](https://pypi.org/pypi/connpy/)
|
||||||
[](https://pypi.org/pypi/connpy/)
|
[](https://pypi.org/pypi/connpy/)
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
# Connpy (v6.1.1)
|
# Connpy (v6.2.0)
|
||||||
[](https://pypi.org/pypi/connpy/)
|
[](https://pypi.org/pypi/connpy/)
|
||||||
[](https://pypi.org/pypi/connpy/)
|
[](https://pypi.org/pypi/connpy/)
|
||||||
[](https://pypi.org/pypi/connpy/)
|
[](https://pypi.org/pypi/connpy/)
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
__version__ = "6.1.1"
|
__version__ = "6.2.0"
|
||||||
|
|||||||
+26
-4
@@ -1394,6 +1394,9 @@ Rules:
|
|||||||
5. Do NOT provide commands to execute unless specifically requested. Instead, explain the consequences and best practices.
|
5. Do NOT provide commands to execute unless specifically requested. Instead, explain the consequences and best practices.
|
||||||
6. Keep your guide concise and authoritative.
|
6. Keep your guide concise and authoritative.
|
||||||
7. You MUST output your response in the following strict format:
|
7. You MUST output your response in the following strict format:
|
||||||
|
<notes>
|
||||||
|
Short internal notes for future context: tools used, key findings, IP addresses, or hypotheses.
|
||||||
|
</notes>
|
||||||
<guide>
|
<guide>
|
||||||
Your brief tactical guide in markdown.
|
Your brief tactical guide in markdown.
|
||||||
</guide>
|
</guide>
|
||||||
@@ -1402,7 +1405,8 @@ Your brief tactical guide in markdown.
|
|||||||
<risk>
|
<risk>
|
||||||
low
|
low
|
||||||
</risk>
|
</risk>
|
||||||
8. Risk level is usually "low" for read-only/no commands.
|
8. CRITICAL CONVERSATION MEMORY: The chat history window retains ONLY the last 5 interactions. Messages older than 5 turns are automatically pruned. You MUST use your <notes> tag in EVERY response to record all critical facts, IP addresses, discoveries, and hypotheses. Because your <notes> are preserved in the assistant history, summarizing key facts in <notes> ensures you never lose vital context when older turns expire.
|
||||||
|
9. Risk level is usually "low" for read-only/no commands.
|
||||||
|
|
||||||
Terminal Context:
|
Terminal Context:
|
||||||
{terminal_buffer}
|
{terminal_buffer}
|
||||||
@@ -1419,6 +1423,9 @@ Rules:
|
|||||||
5. If the user wants to execute an action, provide the required CLI commands inside a <commands> block, one command per line. If no commands are needed, leave it empty or omit the block.
|
5. If the user wants to execute an action, provide the required CLI commands inside a <commands> block, one command per line. If no commands are needed, leave it empty or omit the block.
|
||||||
6. ULTRA-CONCISE. Keep your guide to the point.
|
6. ULTRA-CONCISE. Keep your guide to the point.
|
||||||
7. You MUST output your response in the following strict format:
|
7. You MUST output your response in the following strict format:
|
||||||
|
<notes>
|
||||||
|
Short internal notes for future context: tools used, key findings, IP addresses, or hypotheses.
|
||||||
|
</notes>
|
||||||
<guide>
|
<guide>
|
||||||
Your brief tactical guide in markdown. 3-4 sentences max.
|
Your brief tactical guide in markdown. 3-4 sentences max.
|
||||||
</guide>
|
</guide>
|
||||||
@@ -1429,7 +1436,8 @@ command 2
|
|||||||
<risk>
|
<risk>
|
||||||
low, high, or destructive
|
low, high, or destructive
|
||||||
</risk>
|
</risk>
|
||||||
8. Risk level: "low" for read-only/no commands, "high" for config changes, "destructive" for potentially dangerous ops.
|
8. CRITICAL CONVERSATION MEMORY: The chat history window retains ONLY the last 5 interactions. Messages older than 5 turns are automatically pruned. You MUST use your <notes> tag in EVERY response to record all critical facts, IP addresses, discoveries, and hypotheses. Because your <notes> are preserved in the assistant history, summarizing key facts in <notes> ensures you never lose vital context when older turns expire.
|
||||||
|
9. Risk level: "low" for read-only/no commands, "high" for config changes, "destructive" for potentially dangerous ops.
|
||||||
|
|
||||||
Terminal Context:
|
Terminal Context:
|
||||||
{terminal_buffer}
|
{terminal_buffer}
|
||||||
@@ -1457,10 +1465,18 @@ Node: {node_name}"""
|
|||||||
system_prompt += "\nUse these tools to validate syntax or find exact commands if needed before providing the final guide."
|
system_prompt += "\nUse these tools to validate syntax or find exact commands if needed before providing the final guide."
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{"role": "system", "content": system_prompt},
|
{"role": "system", "content": system_prompt}
|
||||||
{"role": "user", "content": user_question}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
chat_history = node_info.get("chat_history", []) if node_info else []
|
||||||
|
if chat_history:
|
||||||
|
clean_history = chat_history[-10:]
|
||||||
|
for msg in clean_history:
|
||||||
|
if isinstance(msg, dict) and "role" in msg and "content" in msg:
|
||||||
|
messages.append({"role": msg["role"], "content": msg["content"]})
|
||||||
|
|
||||||
|
messages.append({"role": "user", "content": user_question})
|
||||||
|
|
||||||
iteration = 0
|
iteration = 0
|
||||||
max_iterations = 5 # Allow up to 5 iterations for tool usage
|
max_iterations = 5 # Allow up to 5 iterations for tool usage
|
||||||
|
|
||||||
@@ -1582,6 +1598,11 @@ Node: {node_name}"""
|
|||||||
chunk_callback(new_text)
|
chunk_callback(new_text)
|
||||||
streamed_guide += new_text
|
streamed_guide += new_text
|
||||||
|
|
||||||
|
notes = ""
|
||||||
|
notes_match = re.search(r"<notes>(.*?)</notes>", full_content, re.DOTALL)
|
||||||
|
if notes_match:
|
||||||
|
notes = notes_match.group(1).strip()
|
||||||
|
|
||||||
guide = ""
|
guide = ""
|
||||||
commands = []
|
commands = []
|
||||||
risk_level = "low"
|
risk_level = "low"
|
||||||
@@ -1606,6 +1627,7 @@ Node: {node_name}"""
|
|||||||
return {
|
return {
|
||||||
"commands": commands,
|
"commands": commands,
|
||||||
"guide": guide,
|
"guide": guide,
|
||||||
|
"notes": notes,
|
||||||
"risk_level": risk_level,
|
"risk_level": risk_level,
|
||||||
"error": None
|
"error": None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class CopilotInterface:
|
|||||||
self.session_state.setdefault('persona', 'engineer')
|
self.session_state.setdefault('persona', 'engineer')
|
||||||
self.session_state.setdefault('trust_mode', False)
|
self.session_state.setdefault('trust_mode', False)
|
||||||
self.session_state.setdefault('memories', [])
|
self.session_state.setdefault('memories', [])
|
||||||
|
self.session_state.setdefault('copilot_chat_history', [])
|
||||||
self.session_state.setdefault('os', None)
|
self.session_state.setdefault('os', None)
|
||||||
self.session_state.setdefault('prompt', None)
|
self.session_state.setdefault('prompt', None)
|
||||||
self.session_state.setdefault('context_mode', self.mode_range)
|
self.session_state.setdefault('context_mode', self.mode_range)
|
||||||
@@ -382,18 +383,11 @@ class CopilotInterface:
|
|||||||
merged_node_info['persona'] = self.session_state['persona']
|
merged_node_info['persona'] = self.session_state['persona']
|
||||||
merged_node_info['trust'] = self.session_state['trust_mode']
|
merged_node_info['trust'] = self.session_state['trust_mode']
|
||||||
merged_node_info['memories'] = list(self.session_state['memories'])
|
merged_node_info['memories'] = list(self.session_state['memories'])
|
||||||
|
merged_node_info['chat_history'] = list(self.session_state.get('copilot_chat_history', []))
|
||||||
|
|
||||||
for k, v in overrides.items():
|
for k, v in overrides.items():
|
||||||
merged_node_info[k] = v
|
merged_node_info[k] = v
|
||||||
|
|
||||||
# Enrich question
|
|
||||||
past = self.history.get_strings()
|
|
||||||
if len(past) > 1:
|
|
||||||
clean_past = [q for q in past[-6:-1] if not q.startswith('/')]
|
|
||||||
if clean_past:
|
|
||||||
history_text = "\n".join(f"- {q}" for q in clean_past)
|
|
||||||
clean_question = f"Previous questions:\n{history_text}\n\nCurrent Question:\n{clean_question}"
|
|
||||||
|
|
||||||
# 3. AI Execution
|
# 3. AI Execution
|
||||||
# Use persona from overrides (one-shot) or from session state
|
# Use persona from overrides (one-shot) or from session state
|
||||||
active_persona = merged_node_info.get('persona', self.session_state.get('persona', 'engineer'))
|
active_persona = merged_node_info.get('persona', self.session_state.get('persona', 'engineer'))
|
||||||
@@ -461,6 +455,17 @@ class CopilotInterface:
|
|||||||
from rich.markdown import Markdown
|
from rich.markdown import Markdown
|
||||||
self.console.print(Panel(Markdown(result["guide"]), title=f"[bold {persona_color}]{persona_title}[/bold {persona_color}]", border_style=persona_color))
|
self.console.print(Panel(Markdown(result["guide"]), title=f"[bold {persona_color}]{persona_title}[/bold {persona_color}]", border_style=persona_color))
|
||||||
|
|
||||||
|
# Update copilot_chat_history with clean Q&A turn
|
||||||
|
if result and not result.get("error"):
|
||||||
|
guide = result.get("guide", "")
|
||||||
|
notes = result.get("notes", "")
|
||||||
|
if guide:
|
||||||
|
asst_msg = f"Notes: {notes}\nGuide: {guide}" if notes else guide
|
||||||
|
hist = self.session_state.setdefault("copilot_chat_history", [])
|
||||||
|
hist.append({"role": "user", "content": clean_question})
|
||||||
|
hist.append({"role": "assistant", "content": asst_msg})
|
||||||
|
self.session_state["copilot_chat_history"] = hist[-10:]
|
||||||
|
|
||||||
commands = result.get("commands", [])
|
commands = result.get("commands", [])
|
||||||
if not commands:
|
if not commands:
|
||||||
self.console.print("")
|
self.console.print("")
|
||||||
|
|||||||
@@ -27,22 +27,21 @@ def mock_acompletion():
|
|||||||
with patch('litellm.acompletion') as mock:
|
with patch('litellm.acompletion') as mock:
|
||||||
yield mock
|
yield mock
|
||||||
|
|
||||||
def test_aask_copilot_tool_call(mock_acompletion):
|
class MockDelta:
|
||||||
agent = ai(DummyConfig())
|
|
||||||
|
|
||||||
# Setup mock response for streaming
|
|
||||||
class MockDelta:
|
|
||||||
def __init__(self, content):
|
def __init__(self, content):
|
||||||
self.content = content
|
self.content = content
|
||||||
|
|
||||||
class MockChoice:
|
class MockChoice:
|
||||||
def __init__(self, content):
|
def __init__(self, content):
|
||||||
self.delta = MockDelta(content)
|
self.delta = MockDelta(content)
|
||||||
|
|
||||||
class MockChunk:
|
class MockChunk:
|
||||||
def __init__(self, content):
|
def __init__(self, content):
|
||||||
self.choices = [MockChoice(content)]
|
self.choices = [MockChoice(content)]
|
||||||
|
|
||||||
|
def test_aask_copilot_tool_call(mock_acompletion):
|
||||||
|
agent = ai(DummyConfig())
|
||||||
|
|
||||||
# acompletion is awaited and returns an async iterator
|
# acompletion is awaited and returns an async iterator
|
||||||
async def mock_ac(*args, **kwargs):
|
async def mock_ac(*args, **kwargs):
|
||||||
return MockAsyncIterator([
|
return MockAsyncIterator([
|
||||||
@@ -557,6 +556,34 @@ def test_copilot_single_mode_retains_command_block():
|
|||||||
assert interface_custom.session_state.get('context_cmd') == 4
|
assert interface_custom.session_state.get('context_cmd') == 4
|
||||||
|
|
||||||
|
|
||||||
|
def test_aask_copilot_notes_parsing(mock_acompletion):
|
||||||
|
agent = ai(DummyConfig())
|
||||||
|
|
||||||
|
async def mock_ac(*args, **kwargs):
|
||||||
|
return MockAsyncIterator([
|
||||||
|
MockChunk("<notes>Tool used: mcp_cisco__search. MTU mismatch suspected.</notes>"),
|
||||||
|
MockChunk("<guide>Check MTU on eth0.</guide>"),
|
||||||
|
MockChunk("<risk>low</risk>")
|
||||||
|
])
|
||||||
|
|
||||||
|
mock_acompletion.side_effect = mock_ac
|
||||||
|
|
||||||
|
async def run_test():
|
||||||
|
return await agent.aask_copilot("Router#", "Why is ping failing?", node_info={"chat_history": [{"role": "user", "content": "hello"}, {"role": "assistant", "content": "hi"}]})
|
||||||
|
|
||||||
|
result = asyncio.run(run_test())
|
||||||
|
|
||||||
|
assert result["error"] is None
|
||||||
|
assert result["notes"] == "Tool used: mcp_cisco__search. MTU mismatch suspected."
|
||||||
|
assert result["guide"] == "Check MTU on eth0."
|
||||||
|
|
||||||
|
# Check that messages passed to acompletion included chat_history
|
||||||
|
call_args = mock_acompletion.call_args[1]
|
||||||
|
msgs = call_args["messages"]
|
||||||
|
assert len(msgs) == 4 # system, user(hello), asst(hi), current_user
|
||||||
|
assert msgs[1]["content"] == "hello"
|
||||||
|
assert msgs[2]["content"] == "hi"
|
||||||
|
assert msgs[3]["content"] == "Why is ping failing?"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+52
-8
@@ -1835,6 +1835,9 @@ Rules:
|
|||||||
5. Do NOT provide commands to execute unless specifically requested. Instead, explain the consequences and best practices.
|
5. Do NOT provide commands to execute unless specifically requested. Instead, explain the consequences and best practices.
|
||||||
6. Keep your guide concise and authoritative.
|
6. Keep your guide concise and authoritative.
|
||||||
7. You MUST output your response in the following strict format:
|
7. You MUST output your response in the following strict format:
|
||||||
|
<notes>
|
||||||
|
Short internal notes for future context: tools used, key findings, IP addresses, or hypotheses.
|
||||||
|
</notes>
|
||||||
<guide>
|
<guide>
|
||||||
Your brief tactical guide in markdown.
|
Your brief tactical guide in markdown.
|
||||||
</guide>
|
</guide>
|
||||||
@@ -1843,7 +1846,8 @@ Your brief tactical guide in markdown.
|
|||||||
<risk>
|
<risk>
|
||||||
low
|
low
|
||||||
</risk>
|
</risk>
|
||||||
8. Risk level is usually "low" for read-only/no commands.
|
8. CRITICAL CONVERSATION MEMORY: The chat history window retains ONLY the last 5 interactions. Messages older than 5 turns are automatically pruned. You MUST use your <notes> tag in EVERY response to record all critical facts, IP addresses, discoveries, and hypotheses. Because your <notes> are preserved in the assistant history, summarizing key facts in <notes> ensures you never lose vital context when older turns expire.
|
||||||
|
9. Risk level is usually "low" for read-only/no commands.
|
||||||
|
|
||||||
Terminal Context:
|
Terminal Context:
|
||||||
{terminal_buffer}
|
{terminal_buffer}
|
||||||
@@ -1860,6 +1864,9 @@ Rules:
|
|||||||
5. If the user wants to execute an action, provide the required CLI commands inside a <commands> block, one command per line. If no commands are needed, leave it empty or omit the block.
|
5. If the user wants to execute an action, provide the required CLI commands inside a <commands> block, one command per line. If no commands are needed, leave it empty or omit the block.
|
||||||
6. ULTRA-CONCISE. Keep your guide to the point.
|
6. ULTRA-CONCISE. Keep your guide to the point.
|
||||||
7. You MUST output your response in the following strict format:
|
7. You MUST output your response in the following strict format:
|
||||||
|
<notes>
|
||||||
|
Short internal notes for future context: tools used, key findings, IP addresses, or hypotheses.
|
||||||
|
</notes>
|
||||||
<guide>
|
<guide>
|
||||||
Your brief tactical guide in markdown. 3-4 sentences max.
|
Your brief tactical guide in markdown. 3-4 sentences max.
|
||||||
</guide>
|
</guide>
|
||||||
@@ -1870,7 +1877,8 @@ command 2
|
|||||||
<risk>
|
<risk>
|
||||||
low, high, or destructive
|
low, high, or destructive
|
||||||
</risk>
|
</risk>
|
||||||
8. Risk level: "low" for read-only/no commands, "high" for config changes, "destructive" for potentially dangerous ops.
|
8. CRITICAL CONVERSATION MEMORY: The chat history window retains ONLY the last 5 interactions. Messages older than 5 turns are automatically pruned. You MUST use your <notes> tag in EVERY response to record all critical facts, IP addresses, discoveries, and hypotheses. Because your <notes> are preserved in the assistant history, summarizing key facts in <notes> ensures you never lose vital context when older turns expire.
|
||||||
|
9. Risk level: "low" for read-only/no commands, "high" for config changes, "destructive" for potentially dangerous ops.
|
||||||
|
|
||||||
Terminal Context:
|
Terminal Context:
|
||||||
{terminal_buffer}
|
{terminal_buffer}
|
||||||
@@ -1898,10 +1906,18 @@ Node: {node_name}"""
|
|||||||
system_prompt += "\nUse these tools to validate syntax or find exact commands if needed before providing the final guide."
|
system_prompt += "\nUse these tools to validate syntax or find exact commands if needed before providing the final guide."
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{"role": "system", "content": system_prompt},
|
{"role": "system", "content": system_prompt}
|
||||||
{"role": "user", "content": user_question}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
chat_history = node_info.get("chat_history", []) if node_info else []
|
||||||
|
if chat_history:
|
||||||
|
clean_history = chat_history[-10:]
|
||||||
|
for msg in clean_history:
|
||||||
|
if isinstance(msg, dict) and "role" in msg and "content" in msg:
|
||||||
|
messages.append({"role": msg["role"], "content": msg["content"]})
|
||||||
|
|
||||||
|
messages.append({"role": "user", "content": user_question})
|
||||||
|
|
||||||
iteration = 0
|
iteration = 0
|
||||||
max_iterations = 5 # Allow up to 5 iterations for tool usage
|
max_iterations = 5 # Allow up to 5 iterations for tool usage
|
||||||
|
|
||||||
@@ -2023,6 +2039,11 @@ Node: {node_name}"""
|
|||||||
chunk_callback(new_text)
|
chunk_callback(new_text)
|
||||||
streamed_guide += new_text
|
streamed_guide += new_text
|
||||||
|
|
||||||
|
notes = ""
|
||||||
|
notes_match = re.search(r"<notes>(.*?)</notes>", full_content, re.DOTALL)
|
||||||
|
if notes_match:
|
||||||
|
notes = notes_match.group(1).strip()
|
||||||
|
|
||||||
guide = ""
|
guide = ""
|
||||||
commands = []
|
commands = []
|
||||||
risk_level = "low"
|
risk_level = "low"
|
||||||
@@ -2047,6 +2068,7 @@ Node: {node_name}"""
|
|||||||
return {
|
return {
|
||||||
"commands": commands,
|
"commands": commands,
|
||||||
"guide": guide,
|
"guide": guide,
|
||||||
|
"notes": notes,
|
||||||
"risk_level": risk_level,
|
"risk_level": risk_level,
|
||||||
"error": None
|
"error": None
|
||||||
}
|
}
|
||||||
@@ -2173,6 +2195,9 @@ Rules:
|
|||||||
5. Do NOT provide commands to execute unless specifically requested. Instead, explain the consequences and best practices.
|
5. Do NOT provide commands to execute unless specifically requested. Instead, explain the consequences and best practices.
|
||||||
6. Keep your guide concise and authoritative.
|
6. Keep your guide concise and authoritative.
|
||||||
7. You MUST output your response in the following strict format:
|
7. You MUST output your response in the following strict format:
|
||||||
|
<notes>
|
||||||
|
Short internal notes for future context: tools used, key findings, IP addresses, or hypotheses.
|
||||||
|
</notes>
|
||||||
<guide>
|
<guide>
|
||||||
Your brief tactical guide in markdown.
|
Your brief tactical guide in markdown.
|
||||||
</guide>
|
</guide>
|
||||||
@@ -2181,7 +2206,8 @@ Your brief tactical guide in markdown.
|
|||||||
<risk>
|
<risk>
|
||||||
low
|
low
|
||||||
</risk>
|
</risk>
|
||||||
8. Risk level is usually "low" for read-only/no commands.
|
8. CRITICAL CONVERSATION MEMORY: The chat history window retains ONLY the last 5 interactions. Messages older than 5 turns are automatically pruned. You MUST use your <notes> tag in EVERY response to record all critical facts, IP addresses, discoveries, and hypotheses. Because your <notes> are preserved in the assistant history, summarizing key facts in <notes> ensures you never lose vital context when older turns expire.
|
||||||
|
9. Risk level is usually "low" for read-only/no commands.
|
||||||
|
|
||||||
Terminal Context:
|
Terminal Context:
|
||||||
{terminal_buffer}
|
{terminal_buffer}
|
||||||
@@ -2198,6 +2224,9 @@ Rules:
|
|||||||
5. If the user wants to execute an action, provide the required CLI commands inside a <commands> block, one command per line. If no commands are needed, leave it empty or omit the block.
|
5. If the user wants to execute an action, provide the required CLI commands inside a <commands> block, one command per line. If no commands are needed, leave it empty or omit the block.
|
||||||
6. ULTRA-CONCISE. Keep your guide to the point.
|
6. ULTRA-CONCISE. Keep your guide to the point.
|
||||||
7. You MUST output your response in the following strict format:
|
7. You MUST output your response in the following strict format:
|
||||||
|
<notes>
|
||||||
|
Short internal notes for future context: tools used, key findings, IP addresses, or hypotheses.
|
||||||
|
</notes>
|
||||||
<guide>
|
<guide>
|
||||||
Your brief tactical guide in markdown. 3-4 sentences max.
|
Your brief tactical guide in markdown. 3-4 sentences max.
|
||||||
</guide>
|
</guide>
|
||||||
@@ -2208,7 +2237,8 @@ command 2
|
|||||||
<risk>
|
<risk>
|
||||||
low, high, or destructive
|
low, high, or destructive
|
||||||
</risk>
|
</risk>
|
||||||
8. Risk level: "low" for read-only/no commands, "high" for config changes, "destructive" for potentially dangerous ops.
|
8. CRITICAL CONVERSATION MEMORY: The chat history window retains ONLY the last 5 interactions. Messages older than 5 turns are automatically pruned. You MUST use your <notes> tag in EVERY response to record all critical facts, IP addresses, discoveries, and hypotheses. Because your <notes> are preserved in the assistant history, summarizing key facts in <notes> ensures you never lose vital context when older turns expire.
|
||||||
|
9. Risk level: "low" for read-only/no commands, "high" for config changes, "destructive" for potentially dangerous ops.
|
||||||
|
|
||||||
Terminal Context:
|
Terminal Context:
|
||||||
{terminal_buffer}
|
{terminal_buffer}
|
||||||
@@ -2236,10 +2266,18 @@ Node: {node_name}"""
|
|||||||
system_prompt += "\nUse these tools to validate syntax or find exact commands if needed before providing the final guide."
|
system_prompt += "\nUse these tools to validate syntax or find exact commands if needed before providing the final guide."
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{"role": "system", "content": system_prompt},
|
{"role": "system", "content": system_prompt}
|
||||||
{"role": "user", "content": user_question}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
chat_history = node_info.get("chat_history", []) if node_info else []
|
||||||
|
if chat_history:
|
||||||
|
clean_history = chat_history[-10:]
|
||||||
|
for msg in clean_history:
|
||||||
|
if isinstance(msg, dict) and "role" in msg and "content" in msg:
|
||||||
|
messages.append({"role": msg["role"], "content": msg["content"]})
|
||||||
|
|
||||||
|
messages.append({"role": "user", "content": user_question})
|
||||||
|
|
||||||
iteration = 0
|
iteration = 0
|
||||||
max_iterations = 5 # Allow up to 5 iterations for tool usage
|
max_iterations = 5 # Allow up to 5 iterations for tool usage
|
||||||
|
|
||||||
@@ -2361,6 +2399,11 @@ Node: {node_name}"""
|
|||||||
chunk_callback(new_text)
|
chunk_callback(new_text)
|
||||||
streamed_guide += new_text
|
streamed_guide += new_text
|
||||||
|
|
||||||
|
notes = ""
|
||||||
|
notes_match = re.search(r"<notes>(.*?)</notes>", full_content, re.DOTALL)
|
||||||
|
if notes_match:
|
||||||
|
notes = notes_match.group(1).strip()
|
||||||
|
|
||||||
guide = ""
|
guide = ""
|
||||||
commands = []
|
commands = []
|
||||||
risk_level = "low"
|
risk_level = "low"
|
||||||
@@ -2385,6 +2428,7 @@ Node: {node_name}"""
|
|||||||
return {
|
return {
|
||||||
"commands": commands,
|
"commands": commands,
|
||||||
"guide": guide,
|
"guide": guide,
|
||||||
|
"notes": notes,
|
||||||
"risk_level": risk_level,
|
"risk_level": risk_level,
|
||||||
"error": None
|
"error": None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ el.replaceWith(d);
|
|||||||
self.session_state.setdefault('persona', 'engineer')
|
self.session_state.setdefault('persona', 'engineer')
|
||||||
self.session_state.setdefault('trust_mode', False)
|
self.session_state.setdefault('trust_mode', False)
|
||||||
self.session_state.setdefault('memories', [])
|
self.session_state.setdefault('memories', [])
|
||||||
|
self.session_state.setdefault('copilot_chat_history', [])
|
||||||
self.session_state.setdefault('os', None)
|
self.session_state.setdefault('os', None)
|
||||||
self.session_state.setdefault('prompt', None)
|
self.session_state.setdefault('prompt', None)
|
||||||
self.session_state.setdefault('context_mode', self.mode_range)
|
self.session_state.setdefault('context_mode', self.mode_range)
|
||||||
@@ -416,18 +417,11 @@ el.replaceWith(d);
|
|||||||
merged_node_info['persona'] = self.session_state['persona']
|
merged_node_info['persona'] = self.session_state['persona']
|
||||||
merged_node_info['trust'] = self.session_state['trust_mode']
|
merged_node_info['trust'] = self.session_state['trust_mode']
|
||||||
merged_node_info['memories'] = list(self.session_state['memories'])
|
merged_node_info['memories'] = list(self.session_state['memories'])
|
||||||
|
merged_node_info['chat_history'] = list(self.session_state.get('copilot_chat_history', []))
|
||||||
|
|
||||||
for k, v in overrides.items():
|
for k, v in overrides.items():
|
||||||
merged_node_info[k] = v
|
merged_node_info[k] = v
|
||||||
|
|
||||||
# Enrich question
|
|
||||||
past = self.history.get_strings()
|
|
||||||
if len(past) > 1:
|
|
||||||
clean_past = [q for q in past[-6:-1] if not q.startswith('/')]
|
|
||||||
if clean_past:
|
|
||||||
history_text = "\n".join(f"- {q}" for q in clean_past)
|
|
||||||
clean_question = f"Previous questions:\n{history_text}\n\nCurrent Question:\n{clean_question}"
|
|
||||||
|
|
||||||
# 3. AI Execution
|
# 3. AI Execution
|
||||||
# Use persona from overrides (one-shot) or from session state
|
# Use persona from overrides (one-shot) or from session state
|
||||||
active_persona = merged_node_info.get('persona', self.session_state.get('persona', 'engineer'))
|
active_persona = merged_node_info.get('persona', self.session_state.get('persona', 'engineer'))
|
||||||
@@ -495,6 +489,17 @@ el.replaceWith(d);
|
|||||||
from rich.markdown import Markdown
|
from rich.markdown import Markdown
|
||||||
self.console.print(Panel(Markdown(result["guide"]), title=f"[bold {persona_color}]{persona_title}[/bold {persona_color}]", border_style=persona_color))
|
self.console.print(Panel(Markdown(result["guide"]), title=f"[bold {persona_color}]{persona_title}[/bold {persona_color}]", border_style=persona_color))
|
||||||
|
|
||||||
|
# Update copilot_chat_history with clean Q&A turn
|
||||||
|
if result and not result.get("error"):
|
||||||
|
guide = result.get("guide", "")
|
||||||
|
notes = result.get("notes", "")
|
||||||
|
if guide:
|
||||||
|
asst_msg = f"Notes: {notes}\nGuide: {guide}" if notes else guide
|
||||||
|
hist = self.session_state.setdefault("copilot_chat_history", [])
|
||||||
|
hist.append({"role": "user", "content": clean_question})
|
||||||
|
hist.append({"role": "assistant", "content": asst_msg})
|
||||||
|
self.session_state["copilot_chat_history"] = hist[-10:]
|
||||||
|
|
||||||
commands = result.get("commands", [])
|
commands = result.get("commands", [])
|
||||||
if not commands:
|
if not commands:
|
||||||
self.console.print("")
|
self.console.print("")
|
||||||
@@ -922,18 +927,11 @@ el.replaceWith(d);
|
|||||||
merged_node_info['persona'] = self.session_state['persona']
|
merged_node_info['persona'] = self.session_state['persona']
|
||||||
merged_node_info['trust'] = self.session_state['trust_mode']
|
merged_node_info['trust'] = self.session_state['trust_mode']
|
||||||
merged_node_info['memories'] = list(self.session_state['memories'])
|
merged_node_info['memories'] = list(self.session_state['memories'])
|
||||||
|
merged_node_info['chat_history'] = list(self.session_state.get('copilot_chat_history', []))
|
||||||
|
|
||||||
for k, v in overrides.items():
|
for k, v in overrides.items():
|
||||||
merged_node_info[k] = v
|
merged_node_info[k] = v
|
||||||
|
|
||||||
# Enrich question
|
|
||||||
past = self.history.get_strings()
|
|
||||||
if len(past) > 1:
|
|
||||||
clean_past = [q for q in past[-6:-1] if not q.startswith('/')]
|
|
||||||
if clean_past:
|
|
||||||
history_text = "\n".join(f"- {q}" for q in clean_past)
|
|
||||||
clean_question = f"Previous questions:\n{history_text}\n\nCurrent Question:\n{clean_question}"
|
|
||||||
|
|
||||||
# 3. AI Execution
|
# 3. AI Execution
|
||||||
# Use persona from overrides (one-shot) or from session state
|
# Use persona from overrides (one-shot) or from session state
|
||||||
active_persona = merged_node_info.get('persona', self.session_state.get('persona', 'engineer'))
|
active_persona = merged_node_info.get('persona', self.session_state.get('persona', 'engineer'))
|
||||||
@@ -1001,6 +999,17 @@ el.replaceWith(d);
|
|||||||
from rich.markdown import Markdown
|
from rich.markdown import Markdown
|
||||||
self.console.print(Panel(Markdown(result["guide"]), title=f"[bold {persona_color}]{persona_title}[/bold {persona_color}]", border_style=persona_color))
|
self.console.print(Panel(Markdown(result["guide"]), title=f"[bold {persona_color}]{persona_title}[/bold {persona_color}]", border_style=persona_color))
|
||||||
|
|
||||||
|
# Update copilot_chat_history with clean Q&A turn
|
||||||
|
if result and not result.get("error"):
|
||||||
|
guide = result.get("guide", "")
|
||||||
|
notes = result.get("notes", "")
|
||||||
|
if guide:
|
||||||
|
asst_msg = f"Notes: {notes}\nGuide: {guide}" if notes else guide
|
||||||
|
hist = self.session_state.setdefault("copilot_chat_history", [])
|
||||||
|
hist.append({"role": "user", "content": clean_question})
|
||||||
|
hist.append({"role": "assistant", "content": asst_msg})
|
||||||
|
self.session_state["copilot_chat_history"] = hist[-10:]
|
||||||
|
|
||||||
commands = result.get("commands", [])
|
commands = result.get("commands", [])
|
||||||
if not commands:
|
if not commands:
|
||||||
self.console.print("")
|
self.console.print("")
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ el.replaceWith(d);
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://nginx.gederico.dynu.net/images/CONNPY-resized.png" alt="App Logo">
|
<img src="https://nginx.gederico.dynu.net/images/CONNPY-resized.png" alt="App Logo">
|
||||||
</p>
|
</p>
|
||||||
<h1 id="connpy-v611">Connpy (v6.1.1)</h1>
|
<h1 id="connpy-v620">Connpy (v6.2.0)</h1>
|
||||||
<p><a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/v/connpy.svg?style=flat-square"></a>
|
<p><a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/v/connpy.svg?style=flat-square"></a>
|
||||||
<a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/pyversions/connpy.svg?style=flat-square"></a>
|
<a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/pyversions/connpy.svg?style=flat-square"></a>
|
||||||
<a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/dm/connpy.svg?style=flat-square&cacheSeconds=86400"></a>
|
<a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/dm/connpy.svg?style=flat-square&cacheSeconds=86400"></a>
|
||||||
@@ -4087,7 +4087,7 @@ def test(self, commands, expected, vars = None,*, folder = None, prompt = None,
|
|||||||
<nav id="sidebar">
|
<nav id="sidebar">
|
||||||
<div class="toc">
|
<div class="toc">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#connpy-v611">Connpy (v6.1.1)</a><ul>
|
<li><a href="#connpy-v620">Connpy (v6.2.0)</a><ul>
|
||||||
<li><a href="#1-ai-system">1. 🤖 AI System</a><ul>
|
<li><a href="#1-ai-system">1. 🤖 AI System</a><ul>
|
||||||
<li><a href="#1a-terminal-copilot-ctrlspace">1a. Terminal Copilot (Ctrl+Space)</a></li>
|
<li><a href="#1a-terminal-copilot-ctrlspace">1a. Terminal Copilot (Ctrl+Space)</a></li>
|
||||||
<li><a href="#1b-ai-chat-conn-ai">1b. AI Chat (conn ai)</a></li>
|
<li><a href="#1b-ai-chat-conn-ai">1b. AI Chat (conn ai)</a></li>
|
||||||
|
|||||||
Reference in New Issue
Block a user