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>
|
||||
|
||||
|
||||
# Connpy (v6.1.1)
|
||||
# Connpy (v6.2.0)
|
||||
[](https://pypi.org/pypi/connpy/)
|
||||
[](https://pypi.org/pypi/connpy/)
|
||||
[](https://pypi.org/pypi/connpy/)
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
</p>
|
||||
|
||||
|
||||
# Connpy (v6.1.1)
|
||||
# Connpy (v6.2.0)
|
||||
[](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.
|
||||
6. Keep your guide concise and authoritative.
|
||||
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>
|
||||
Your brief tactical guide in markdown.
|
||||
</guide>
|
||||
@@ -1402,7 +1405,8 @@ Your brief tactical guide in markdown.
|
||||
<risk>
|
||||
low
|
||||
</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_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.
|
||||
6. ULTRA-CONCISE. Keep your guide to the point.
|
||||
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>
|
||||
Your brief tactical guide in markdown. 3-4 sentences max.
|
||||
</guide>
|
||||
@@ -1429,7 +1436,8 @@ command 2
|
||||
<risk>
|
||||
low, high, or destructive
|
||||
</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_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."
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_question}
|
||||
{"role": "system", "content": system_prompt}
|
||||
]
|
||||
|
||||
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
|
||||
max_iterations = 5 # Allow up to 5 iterations for tool usage
|
||||
|
||||
@@ -1582,6 +1598,11 @@ Node: {node_name}"""
|
||||
chunk_callback(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 = ""
|
||||
commands = []
|
||||
risk_level = "low"
|
||||
@@ -1606,6 +1627,7 @@ Node: {node_name}"""
|
||||
return {
|
||||
"commands": commands,
|
||||
"guide": guide,
|
||||
"notes": notes,
|
||||
"risk_level": risk_level,
|
||||
"error": None
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ class CopilotInterface:
|
||||
self.session_state.setdefault('persona', 'engineer')
|
||||
self.session_state.setdefault('trust_mode', False)
|
||||
self.session_state.setdefault('memories', [])
|
||||
self.session_state.setdefault('copilot_chat_history', [])
|
||||
self.session_state.setdefault('os', None)
|
||||
self.session_state.setdefault('prompt', None)
|
||||
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['trust'] = self.session_state['trust_mode']
|
||||
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():
|
||||
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
|
||||
# Use persona from overrides (one-shot) or from session state
|
||||
active_persona = merged_node_info.get('persona', self.session_state.get('persona', 'engineer'))
|
||||
@@ -461,6 +455,17 @@ class CopilotInterface:
|
||||
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))
|
||||
|
||||
# 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", [])
|
||||
if not commands:
|
||||
self.console.print("")
|
||||
|
||||
@@ -27,22 +27,21 @@ def mock_acompletion():
|
||||
with patch('litellm.acompletion') as mock:
|
||||
yield mock
|
||||
|
||||
class MockDelta:
|
||||
def __init__(self, content):
|
||||
self.content = content
|
||||
|
||||
class MockChoice:
|
||||
def __init__(self, content):
|
||||
self.delta = MockDelta(content)
|
||||
|
||||
class MockChunk:
|
||||
def __init__(self, content):
|
||||
self.choices = [MockChoice(content)]
|
||||
|
||||
def test_aask_copilot_tool_call(mock_acompletion):
|
||||
agent = ai(DummyConfig())
|
||||
|
||||
# Setup mock response for streaming
|
||||
class MockDelta:
|
||||
def __init__(self, content):
|
||||
self.content = content
|
||||
|
||||
class MockChoice:
|
||||
def __init__(self, content):
|
||||
self.delta = MockDelta(content)
|
||||
|
||||
class MockChunk:
|
||||
def __init__(self, content):
|
||||
self.choices = [MockChoice(content)]
|
||||
|
||||
# acompletion is awaited and returns an async iterator
|
||||
async def mock_ac(*args, **kwargs):
|
||||
return MockAsyncIterator([
|
||||
@@ -557,6 +556,34 @@ def test_copilot_single_mode_retains_command_block():
|
||||
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.
|
||||
6. Keep your guide concise and authoritative.
|
||||
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>
|
||||
Your brief tactical guide in markdown.
|
||||
</guide>
|
||||
@@ -1843,7 +1846,8 @@ Your brief tactical guide in markdown.
|
||||
<risk>
|
||||
low
|
||||
</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_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.
|
||||
6. ULTRA-CONCISE. Keep your guide to the point.
|
||||
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>
|
||||
Your brief tactical guide in markdown. 3-4 sentences max.
|
||||
</guide>
|
||||
@@ -1870,7 +1877,8 @@ command 2
|
||||
<risk>
|
||||
low, high, or destructive
|
||||
</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_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."
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_question}
|
||||
{"role": "system", "content": system_prompt}
|
||||
]
|
||||
|
||||
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
|
||||
max_iterations = 5 # Allow up to 5 iterations for tool usage
|
||||
|
||||
@@ -2023,6 +2039,11 @@ Node: {node_name}"""
|
||||
chunk_callback(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 = ""
|
||||
commands = []
|
||||
risk_level = "low"
|
||||
@@ -2047,6 +2068,7 @@ Node: {node_name}"""
|
||||
return {
|
||||
"commands": commands,
|
||||
"guide": guide,
|
||||
"notes": notes,
|
||||
"risk_level": risk_level,
|
||||
"error": None
|
||||
}
|
||||
@@ -2173,6 +2195,9 @@ Rules:
|
||||
5. Do NOT provide commands to execute unless specifically requested. Instead, explain the consequences and best practices.
|
||||
6. Keep your guide concise and authoritative.
|
||||
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>
|
||||
Your brief tactical guide in markdown.
|
||||
</guide>
|
||||
@@ -2181,7 +2206,8 @@ Your brief tactical guide in markdown.
|
||||
<risk>
|
||||
low
|
||||
</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_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.
|
||||
6. ULTRA-CONCISE. Keep your guide to the point.
|
||||
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>
|
||||
Your brief tactical guide in markdown. 3-4 sentences max.
|
||||
</guide>
|
||||
@@ -2208,7 +2237,8 @@ command 2
|
||||
<risk>
|
||||
low, high, or destructive
|
||||
</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_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."
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_question}
|
||||
{"role": "system", "content": system_prompt}
|
||||
]
|
||||
|
||||
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
|
||||
max_iterations = 5 # Allow up to 5 iterations for tool usage
|
||||
|
||||
@@ -2361,6 +2399,11 @@ Node: {node_name}"""
|
||||
chunk_callback(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 = ""
|
||||
commands = []
|
||||
risk_level = "low"
|
||||
@@ -2385,6 +2428,7 @@ Node: {node_name}"""
|
||||
return {
|
||||
"commands": commands,
|
||||
"guide": guide,
|
||||
"notes": notes,
|
||||
"risk_level": risk_level,
|
||||
"error": None
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ el.replaceWith(d);
|
||||
self.session_state.setdefault('persona', 'engineer')
|
||||
self.session_state.setdefault('trust_mode', False)
|
||||
self.session_state.setdefault('memories', [])
|
||||
self.session_state.setdefault('copilot_chat_history', [])
|
||||
self.session_state.setdefault('os', None)
|
||||
self.session_state.setdefault('prompt', None)
|
||||
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['trust'] = self.session_state['trust_mode']
|
||||
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():
|
||||
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
|
||||
# Use persona from overrides (one-shot) or from session state
|
||||
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
|
||||
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", [])
|
||||
if not commands:
|
||||
self.console.print("")
|
||||
@@ -922,18 +927,11 @@ el.replaceWith(d);
|
||||
merged_node_info['persona'] = self.session_state['persona']
|
||||
merged_node_info['trust'] = self.session_state['trust_mode']
|
||||
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():
|
||||
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
|
||||
# Use persona from overrides (one-shot) or from session state
|
||||
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
|
||||
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", [])
|
||||
if not commands:
|
||||
self.console.print("")
|
||||
|
||||
@@ -41,7 +41,7 @@ el.replaceWith(d);
|
||||
<p align="center">
|
||||
<img src="https://nginx.gederico.dynu.net/images/CONNPY-resized.png" alt="App Logo">
|
||||
</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>
|
||||
<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>
|
||||
@@ -4087,7 +4087,7 @@ def test(self, commands, expected, vars = None,*, folder = None, prompt = None,
|
||||
<nav id="sidebar">
|
||||
<div class="toc">
|
||||
<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="#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>
|
||||
|
||||
Reference in New Issue
Block a user