Bolt Console: desktop app with chat list / new chat, dictation, file tools
Add a chat list and server-minted new chats on top of the desk API's
GET/POST /desk/chats:
- server_client.list_chats() / create_chat(), same shape and error
handling as list_outbox_files().
- Switching is config.set_session(id): a plain reassignment of the
SESSION_ID every call reads, persisted to session.json. The window
refuses to switch mid-turn rather than locking.
- gui/chat_list_window.py: a Chats dialog (Ctrl+K) with a QListWidget,
populated on open, with Refresh and New Chat. Network calls run as
QRunnables on the global QThreadPool and come back through Signals,
never from a slot. The header New chat (Ctrl+N) uses the same path.
- Switching clears the local transcript; there is no endpoint for a
past chat's messages. Bolt's memory is per user, so it carries over.
- Terminal: /chats, /chat <n|id>, and /new (server-minted id, local
fallback).
This commit also carries the uncommitted work since 54030a7 that turned
this Bolt-Pet fork into Bolt Console: the pet, wake word, TTS and screen
features removed; a Claude Code-style terminal client; the PySide6
window with tool cards, approvals and diffs; mic dictation through the
server's /desk/stt relay; filectl/JSON repairs; and detection of
invented tool results.
Build output (build/, dist/, Release/, *.spec) is now ignored.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+18
-4
@@ -4,7 +4,7 @@ read/write/edit local files instead of a raw shell heredoc.
|
||||
filectl {"op": ...} is a single-line JSON envelope (not a multi-line
|
||||
marker block) because it rides the "command" tool marker, which the main
|
||||
repo's tool-call extractor only captures up to the next newline — see the
|
||||
module docstring in bolt_pet/file_ops.py."""
|
||||
module docstring in bolt_console/file_ops.py."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
@@ -14,7 +14,7 @@ import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
from bolt_pet import file_ops
|
||||
from bolt_console import file_ops
|
||||
|
||||
|
||||
def _cmd(payload: dict) -> str:
|
||||
@@ -305,7 +305,7 @@ def test_a_repair_is_reported_in_the_output_never_hidden(tmp_path):
|
||||
tomorrow — the model has to be told while it still has the turn."""
|
||||
(tmp_path / "a.txt").write_text("hello", encoding="utf-8")
|
||||
action = file_ops.parse(
|
||||
'filectl {"op":"list","path":"%s","recursive":false"}' % tmp_path
|
||||
'filectl {"op":"list","path":%s,"recursive":false"}' % json.dumps(str(tmp_path))
|
||||
)
|
||||
output = file_ops.execute(action)
|
||||
assert "a.txt" in output
|
||||
@@ -315,7 +315,7 @@ def test_a_repair_is_reported_in_the_output_never_hidden(tmp_path):
|
||||
|
||||
def test_valid_json_gets_no_repair_note(tmp_path):
|
||||
(tmp_path / "a.txt").write_text("hello", encoding="utf-8")
|
||||
action = file_ops.parse('filectl {"op": "list", "path": "%s"}' % tmp_path)
|
||||
action = file_ops.parse('filectl {"op": "list", "path": %s}' % json.dumps(str(tmp_path)))
|
||||
assert "_repairs" not in action
|
||||
assert "malformed" not in file_ops.execute(action)
|
||||
|
||||
@@ -327,3 +327,17 @@ def test_genuinely_unparseable_json_points_at_the_character():
|
||||
message = str(excinfo.value)
|
||||
assert "^" in message # caret under the offending character
|
||||
assert '"extra"' in message # ...and the fragment around it
|
||||
|
||||
|
||||
def test_a_raw_windows_path_is_repaired_and_reported():
|
||||
r"""Models type Windows paths with single backslashes. `\U` isn't a JSON
|
||||
escape, so strict parsing would reject the whole call."""
|
||||
action = file_ops.parse(r'filectl {"op": "read", "path": "C:\Users\sam\data.txt"}')
|
||||
assert action["path"] == r"C:\Users\sam\data.txt"
|
||||
assert any("backslash" in repair for repair in action["_repairs"])
|
||||
|
||||
|
||||
def test_the_backslash_repair_leaves_valid_escapes_alone():
|
||||
# The escaped pair and the valid \n survive; only the lone \U is doubled.
|
||||
action = file_ops.parse(r'filectl {"op": "read", "path": "C:\\ok\Users\n.txt"}')
|
||||
assert action["path"] == "C:\\ok\\Users\n.txt"
|
||||
|
||||
Reference in New Issue
Block a user