aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-16 14:15:27 +0200
committerDanilo M. <danix@danix.xyz>2026-10-16 14:15:27 +0200
commit4a1f1166ac27b9016a4e7b4d1468c2410ac1213d (patch)
treeea45d6bdf3366a1acb748495ede41374210d1ec5
parent460cd4456790092ea42a8848bb0ea517a1174fca (diff)
downloaddesktop-assistant-4a1f1166ac27b9016a4e7b4d1468c2410ac1213d.tar.gz
desktop-assistant-4a1f1166ac27b9016a4e7b4d1468c2410ac1213d.zip
Mark the errors that end a reply
Every client sees every event, so a command error from one client, such as opening a conversation another just deleted, reached a canvas watching a reply and was taken as that reply's end: the canvas stopped rendering tokens the daemon kept streaming. An error from the reply thread now carries turn: true; command errors carry none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--assistant.py12
-rw-r--r--docs/superpowers/specs/2026-10-16-text-daemon-design.md2
2 files changed, 9 insertions, 5 deletions
diff --git a/assistant.py b/assistant.py
index db527a5..777ceba 100644
--- a/assistant.py
+++ b/assistant.py
@@ -548,7 +548,9 @@ def reply(mine, text):
with lock:
if turn == mine:
cur["pending"] = None
- emit({"type": "error", "message": str(e) or type(e).__name__})
+ # turn: this error ended the reply. Command errors carry no
+ # turn and must not end a reply another client is watching.
+ emit({"type": "error", "message": str(e) or type(e).__name__, "turn": True})
emit({"type": "state", "state": "idle"})
@@ -824,7 +826,8 @@ def test():
script["replies"] = [['data: {"error": {"message": "context full"}}\n\n']]
CFG["thinking"] = False
send(type="ask", text="too much")
- assert until("error")[-1]["message"] == "context full"
+ e = until("error")[-1]
+ assert e["message"] == "context full" and e["turn"] is True
assert script["body"]["chat_template_kwargs"]["enable_thinking"] is False
CFG["thinking"] = True
# Stop mid-stream: no token after done, nothing saved.
@@ -865,7 +868,8 @@ def test():
({"type": "ask", "text": None}, "bad text"),
({"type": "search", "q": 3}, "bad query")):
send(**cmd)
- assert until("error")[-1]["message"] == why, cmd
+ e = until("error")[-1]
+ assert e["message"] == why and "turn" not in e, cmd # a command error
assert cur["id"] == conv # none of them touched the conversation
real, DB = DB, None # every query now raises
send(type="list")
@@ -887,7 +891,7 @@ def test():
# llama-server down: an error, not a dead thread.
CFG["llm"] = "http://127.0.0.1:9/v1"
send(type="ask", text="anyone?")
- assert until("error")[-1]["type"] == "error"
+ assert until("error")[-1]["turn"] is True
assert until("state")[-1]["state"] == "idle" and cur["pending"] is None
# A second client gets hello with the conversation so far.
c2 = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
diff --git a/docs/superpowers/specs/2026-10-16-text-daemon-design.md b/docs/superpowers/specs/2026-10-16-text-daemon-design.md
index 58d0240..223af38 100644
--- a/docs/superpowers/specs/2026-10-16-text-daemon-design.md
+++ b/docs/superpowers/specs/2026-10-16-text-daemon-design.md
@@ -102,7 +102,7 @@ Outbound:
| `token` | `delta` |
| `tool` | `name`, `done` (false when it starts, true when it returns) |
| `done` | `id`: the conversation the turn was saved in; `stopped`: true for a reply that was stopped |
-| `error` | `message` |
+| `error` | `message`; `turn`: true when the error ended the reply in flight, absent for a command error |
| `ctx` | `used`, `max` |
| `conversations` | `items`: `[{id, title, updated}]`, newest first |
| `conversation` | `id`, `turns`: `[{user, reply, tools}]` |