soothe-client-python 1.0.3__tar.gz → 1.0.7__tar.gz
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.
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/PKG-INFO +2 -2
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/pyproject.toml +1 -1
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/__init__.py +2 -2
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/__init__.py +11 -4
- soothe_client_python-1.0.7/src/soothe_client/appkit/card_projection.py +10 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/daemon_session.py +67 -59
- soothe_client_python-1.0.3/src/soothe_client/appkit/session_store.py → soothe_client_python-1.0.7/src/soothe_client/appkit/loop_session_store.py +4 -4
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/pool.py +3 -3
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/turn_runner.py +2 -2
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/helpers.py +4 -4
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/intent_hints.py +0 -2
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/protocol_params.py +6 -5
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/stream_terminal.py +11 -3
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/websocket.py +14 -8
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/.gitignore +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/LICENSE +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/README.md +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/attachments.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/broadcaster.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/chunk_filter.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/classifier.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/events.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/managed_client.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/observability.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/query_gate.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/thinking_step.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/turn.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/turn_boundary.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/command_client.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/errors.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/py.typed +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/schemas.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/session.py +0 -0
- {soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/turn_boundary.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: soothe-client-python
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.7
|
|
4
4
|
Summary: WebSocket client + appkit for soothe-daemon (Python)
|
|
5
5
|
Project-URL: Homepage, https://github.com/mirasoth/soothe-client-python
|
|
6
6
|
Project-URL: Documentation, https://soothe.readthedocs.io
|
|
@@ -21,7 +21,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
21
21
|
Requires-Python: <3.15,>=3.11
|
|
22
22
|
Requires-Dist: pillow<12.0.0,>=10.0.0
|
|
23
23
|
Requires-Dist: pydantic<3.0.0,>=2.0.0
|
|
24
|
-
Requires-Dist: soothe-sdk<2.0.0,>=1.0.
|
|
24
|
+
Requires-Dist: soothe-sdk<2.0.0,>=1.0.2
|
|
25
25
|
Requires-Dist: websockets>=12.0
|
|
26
26
|
Provides-Extra: dev
|
|
27
27
|
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
@@ -33,7 +33,7 @@ from soothe_client.helpers import (
|
|
|
33
33
|
check_daemon_status,
|
|
34
34
|
connected_websocket,
|
|
35
35
|
fetch_config_section,
|
|
36
|
-
|
|
36
|
+
fetch_execution_state,
|
|
37
37
|
fetch_loop_history,
|
|
38
38
|
fetch_loop_messages,
|
|
39
39
|
fetch_skills_catalog,
|
|
@@ -91,8 +91,8 @@ __all__ = [
|
|
|
91
91
|
"request_daemon_shutdown",
|
|
92
92
|
"fetch_skills_catalog",
|
|
93
93
|
"fetch_config_section",
|
|
94
|
+
"fetch_execution_state",
|
|
94
95
|
"fetch_loop_history",
|
|
95
|
-
"fetch_loop_cards",
|
|
96
96
|
"fetch_loop_messages",
|
|
97
97
|
# Intent hints
|
|
98
98
|
"TEXT_COMPLETION",
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/__init__.py
RENAMED
|
@@ -7,7 +7,7 @@ Primary building blocks for agent UIs and backends:
|
|
|
7
7
|
- ``QueryGate`` — one-in-flight query per session
|
|
8
8
|
- ``EventClassifier`` — stream → deliverable mapping
|
|
9
9
|
- ``SSEBroadcaster`` — drop-on-full fan-out to subscribers
|
|
10
|
-
- ``
|
|
10
|
+
- ``LoopSessionStore`` — persistence seam (Protocol)
|
|
11
11
|
|
|
12
12
|
Advanced stream/pipeline plumbing lives in submodules
|
|
13
13
|
(``events``, ``chunk_filter``, ``turn``, ``managed_client``).
|
|
@@ -21,6 +21,7 @@ from soothe_client.appkit.attachments import (
|
|
|
21
21
|
compact_image_attachment,
|
|
22
22
|
)
|
|
23
23
|
from soothe_client.appkit.broadcaster import SSEBroadcaster, SSEEvent
|
|
24
|
+
from soothe_client.appkit.card_projection import CardProjection, parse_card_custom_payload
|
|
24
25
|
from soothe_client.appkit.classifier import (
|
|
25
26
|
EVENT_FINAL_REPORT,
|
|
26
27
|
ChatEventResult,
|
|
@@ -29,6 +30,11 @@ from soothe_client.appkit.classifier import (
|
|
|
29
30
|
EventClassifier,
|
|
30
31
|
)
|
|
31
32
|
from soothe_client.appkit.daemon_session import DaemonSession
|
|
33
|
+
from soothe_client.appkit.loop_session_store import (
|
|
34
|
+
LoopSessionEntry,
|
|
35
|
+
LoopSessionStore,
|
|
36
|
+
SessionMessage,
|
|
37
|
+
)
|
|
32
38
|
from soothe_client.appkit.pool import (
|
|
33
39
|
ConnectionPool,
|
|
34
40
|
ErrPoolExhausted,
|
|
@@ -37,7 +43,6 @@ from soothe_client.appkit.pool import (
|
|
|
37
43
|
default_pool_config,
|
|
38
44
|
)
|
|
39
45
|
from soothe_client.appkit.query_gate import ErrQueryBusy, QueryGate
|
|
40
|
-
from soothe_client.appkit.session_store import SessionEntry, SessionMessage, SessionStore
|
|
41
46
|
from soothe_client.appkit.thinking_step import (
|
|
42
47
|
DEFAULT_THINKING_STEP_EVENTS,
|
|
43
48
|
extract_thinking_step,
|
|
@@ -72,6 +77,7 @@ __all__ = [
|
|
|
72
77
|
"STREAM_CLOSE_FAIL",
|
|
73
78
|
"STREAM_CLOSE_SOFT_COMPLETE",
|
|
74
79
|
"Attachment",
|
|
80
|
+
"CardProjection",
|
|
75
81
|
"ChatEventResult",
|
|
76
82
|
"ChatEventTerminal",
|
|
77
83
|
"ClassifierConfig",
|
|
@@ -85,6 +91,8 @@ __all__ = [
|
|
|
85
91
|
"ErrQueryTimeout",
|
|
86
92
|
"EventClassifier",
|
|
87
93
|
"InputOpts",
|
|
94
|
+
"LoopSessionEntry",
|
|
95
|
+
"LoopSessionStore",
|
|
88
96
|
"OnComplete",
|
|
89
97
|
"OnError",
|
|
90
98
|
"PoolConfig",
|
|
@@ -92,9 +100,7 @@ __all__ = [
|
|
|
92
100
|
"QueryGate",
|
|
93
101
|
"SSEBroadcaster",
|
|
94
102
|
"SSEEvent",
|
|
95
|
-
"SessionEntry",
|
|
96
103
|
"SessionMessage",
|
|
97
|
-
"SessionStore",
|
|
98
104
|
"StreamClosePolicy",
|
|
99
105
|
"TURN_END_IDLE",
|
|
100
106
|
"TURN_END_STOPPED",
|
|
@@ -111,4 +117,5 @@ __all__ = [
|
|
|
111
117
|
"extract_thinking_step",
|
|
112
118
|
"idle_timeout_for_turn",
|
|
113
119
|
"input_message_for_loop",
|
|
120
|
+
"parse_card_custom_payload",
|
|
114
121
|
]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""In-memory apply helpers for daemon ``soothe.card.*`` frames."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from soothe_sdk.display.card_wire import CardProjection, parse_card_custom_payload
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"CardProjection",
|
|
9
|
+
"parse_card_custom_payload",
|
|
10
|
+
]
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/daemon_session.py
RENAMED
|
@@ -82,9 +82,21 @@ class DaemonSession:
|
|
|
82
82
|
self.last_turn_cancellation_seen: bool = False
|
|
83
83
|
self.last_turn_error_message: str | None = None
|
|
84
84
|
# Last completed turn's seq floor (drop stale prior-turn frames).
|
|
85
|
+
# Scoped to the current peer seq namespace: reset on reconnect / loop
|
|
86
|
+
# change — daemon seq is per-process and restarts after daemon restart.
|
|
85
87
|
self._last_turn_end_seq: int = 0
|
|
86
88
|
self._expected_turn_id: str | None = None
|
|
87
89
|
|
|
90
|
+
def _reset_turn_seq_floor(self) -> None:
|
|
91
|
+
"""Clear seq/turn filters when the peer seq namespace may have restarted.
|
|
92
|
+
|
|
93
|
+
Keep-TUI reconnect after a daemon restart (or subscribe to a different
|
|
94
|
+
loop) would otherwise drop all low-seq frames — including
|
|
95
|
+
``status=running`` — leaving ``query_started=False`` and a silent TUI.
|
|
96
|
+
"""
|
|
97
|
+
self._last_turn_end_seq = 0
|
|
98
|
+
self._expected_turn_id = None
|
|
99
|
+
|
|
88
100
|
@property
|
|
89
101
|
def client(self) -> WebSocketClient:
|
|
90
102
|
"""Subscribed stream WebSocket (loop events)."""
|
|
@@ -131,6 +143,7 @@ class DaemonSession:
|
|
|
131
143
|
return await self._bootstrap_loop(resume_loop_id=resume_loop_id)
|
|
132
144
|
|
|
133
145
|
async def _bootstrap_loop(self, *, resume_loop_id: str | None = None) -> dict[str, Any]:
|
|
146
|
+
prev_loop_id = self._loop_id
|
|
134
147
|
status_event = await bootstrap_loop_session(
|
|
135
148
|
self._client,
|
|
136
149
|
resume_loop_id=resume_loop_id,
|
|
@@ -140,6 +153,9 @@ class DaemonSession:
|
|
|
140
153
|
if status_event.get("type") == "error":
|
|
141
154
|
raise RuntimeError(str(status_event.get("message", "daemon bootstrap failed")))
|
|
142
155
|
self._loop_id = status_event.get("loop_id")
|
|
156
|
+
# New/changed loop has an independent seq counter on the daemon.
|
|
157
|
+
if prev_loop_id != self._loop_id:
|
|
158
|
+
self._reset_turn_seq_floor()
|
|
143
159
|
return status_event
|
|
144
160
|
|
|
145
161
|
async def new_loop(self) -> dict[str, Any]:
|
|
@@ -179,6 +195,10 @@ class DaemonSession:
|
|
|
179
195
|
await self._client.close()
|
|
180
196
|
await connect_websocket_with_retries(self._client)
|
|
181
197
|
|
|
198
|
+
# Peer seq is process-local; daemon restart restarts counters at 1 while
|
|
199
|
+
# this session may still hold a high floor from before the disconnect.
|
|
200
|
+
self._reset_turn_seq_floor()
|
|
201
|
+
|
|
182
202
|
if resume_loop_id:
|
|
183
203
|
reattach = getattr(self._client, "reattach_and_probe", None)
|
|
184
204
|
if callable(reattach):
|
|
@@ -411,7 +431,7 @@ class DaemonSession:
|
|
|
411
431
|
):
|
|
412
432
|
continue
|
|
413
433
|
|
|
414
|
-
# IG-
|
|
434
|
+
# IG-616 phase 5: drop frames at or before prior turn end seq.
|
|
415
435
|
ev_seq = frame_seq(event)
|
|
416
436
|
if ev_seq is not None and last_end_seq > 0 and ev_seq <= last_end_seq:
|
|
417
437
|
continue
|
|
@@ -506,7 +526,7 @@ class DaemonSession:
|
|
|
506
526
|
self.turn_event_stats.filtered_early += 1
|
|
507
527
|
continue
|
|
508
528
|
|
|
509
|
-
# Prefer turn_id match when present; else IG-
|
|
529
|
+
# Prefer turn_id match when present; else IG-615 progress gate.
|
|
510
530
|
if mode == "custom" and is_turn_end_custom_data(data):
|
|
511
531
|
data_turn = (
|
|
512
532
|
frame_turn_id(data if isinstance(data, dict) else None) or ev_turn_id
|
|
@@ -603,40 +623,6 @@ class DaemonSession:
|
|
|
603
623
|
await self._rpc_client.wait_for_connection_ack(ack_timeout_s=_RPC_HANDSHAKE_TIMEOUT_S)
|
|
604
624
|
self._rpc_connected = True
|
|
605
625
|
|
|
606
|
-
async def fetch_loop_cards(self, loop_id: str) -> SimpleNamespace:
|
|
607
|
-
"""Fetch bound display-card snapshot for a loop."""
|
|
608
|
-
lid = str(loop_id or "").strip()
|
|
609
|
-
if not lid:
|
|
610
|
-
return SimpleNamespace(cards=[], seq=0, success=False)
|
|
611
|
-
|
|
612
|
-
async with self._rpc_lock:
|
|
613
|
-
await self._ensure_rpc_connected()
|
|
614
|
-
try:
|
|
615
|
-
resp = await self._rpc_client.request(
|
|
616
|
-
"loop_cards_fetch",
|
|
617
|
-
{"loop_id": lid},
|
|
618
|
-
timeout=30.0,
|
|
619
|
-
)
|
|
620
|
-
except Exception:
|
|
621
|
-
logger.warning("loop_cards_fetch failed for loop %s", lid[:16], exc_info=True)
|
|
622
|
-
return SimpleNamespace(cards=[], seq=0, success=False)
|
|
623
|
-
|
|
624
|
-
raw_cards = resp.get("cards")
|
|
625
|
-
cards = list(raw_cards) if isinstance(raw_cards, list) else []
|
|
626
|
-
seq = int(resp.get("seq") or 0)
|
|
627
|
-
context_tokens_raw = resp.get("context_tokens")
|
|
628
|
-
context_tokens = (
|
|
629
|
-
context_tokens_raw
|
|
630
|
-
if isinstance(context_tokens_raw, int) and context_tokens_raw >= 0
|
|
631
|
-
else 0
|
|
632
|
-
)
|
|
633
|
-
return SimpleNamespace(
|
|
634
|
-
cards=cards,
|
|
635
|
-
seq=seq,
|
|
636
|
-
context_tokens=context_tokens,
|
|
637
|
-
success=True,
|
|
638
|
-
)
|
|
639
|
-
|
|
640
626
|
async def fetch_loop_history(self, loop_id: str) -> SimpleNamespace:
|
|
641
627
|
"""Fetch goal display snapshots plus live card tail."""
|
|
642
628
|
lid = str(loop_id or "").strip()
|
|
@@ -711,34 +697,56 @@ class DaemonSession:
|
|
|
711
697
|
values: dict[str, Any] = dict(raw) if isinstance(raw, dict) else {}
|
|
712
698
|
return SimpleNamespace(values=values)
|
|
713
699
|
|
|
714
|
-
async def
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
"""Merge partial state into the loop on the daemon host."""
|
|
700
|
+
async def fetch_execution_state(self, loop_id: str) -> SimpleNamespace:
|
|
701
|
+
"""Fetch execution-progress snapshot (plan, step_index, iteration, status).
|
|
702
|
+
|
|
703
|
+
Returns a namespace with ``plan``, ``step_index``, ``iteration``,
|
|
704
|
+
``status`` for the loop's bound checkpoint thread. On RPC failure the
|
|
705
|
+
fields degrade gracefully (``step_index=0``, ``iteration=0``,
|
|
706
|
+
``status=None``, ``plan=None``) so a resume gate can still proceed.
|
|
707
|
+
"""
|
|
723
708
|
lid = str(loop_id or "").strip()
|
|
709
|
+
empty = SimpleNamespace(
|
|
710
|
+
plan=None,
|
|
711
|
+
step_index=0,
|
|
712
|
+
iteration=0,
|
|
713
|
+
status=None,
|
|
714
|
+
loop_id=lid or None,
|
|
715
|
+
success=False,
|
|
716
|
+
)
|
|
724
717
|
if not lid:
|
|
725
|
-
return
|
|
718
|
+
return empty
|
|
726
719
|
|
|
727
720
|
async with self._rpc_lock:
|
|
728
721
|
await self._ensure_rpc_connected()
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
722
|
+
try:
|
|
723
|
+
resp = await self._rpc_client.request(
|
|
724
|
+
"loop_execution_state_fetch",
|
|
725
|
+
{"loop_id": lid},
|
|
726
|
+
timeout=30.0,
|
|
727
|
+
)
|
|
728
|
+
except Exception:
|
|
729
|
+
logger.warning(
|
|
730
|
+
"loop_execution_state_fetch failed for loop %s", lid[:16], exc_info=True
|
|
731
|
+
)
|
|
732
|
+
return empty
|
|
733
|
+
|
|
734
|
+
plan = resp.get("plan")
|
|
735
|
+
step_index_raw = resp.get("step_index")
|
|
736
|
+
step_index = step_index_raw if isinstance(step_index_raw, int) else 0
|
|
737
|
+
iteration_raw = resp.get("iteration")
|
|
738
|
+
iteration = iteration_raw if isinstance(iteration_raw, int) else 0
|
|
739
|
+
status_raw = resp.get("status")
|
|
740
|
+
status = status_raw if isinstance(status_raw, str) else None
|
|
741
|
+
success = bool(resp.get("success", True))
|
|
742
|
+
return SimpleNamespace(
|
|
743
|
+
plan=plan,
|
|
744
|
+
step_index=step_index,
|
|
745
|
+
iteration=iteration,
|
|
746
|
+
status=status,
|
|
747
|
+
loop_id=resp.get("loop_id", lid),
|
|
748
|
+
success=success,
|
|
749
|
+
)
|
|
742
750
|
|
|
743
751
|
async def fetch_conversation_log(
|
|
744
752
|
self,
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from typing import Any, Protocol, runtime_checkable
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class
|
|
8
|
+
class LoopSessionEntry:
|
|
9
9
|
"""Persisted mapping between an application session id and a daemon loop id."""
|
|
10
10
|
|
|
11
11
|
__slots__ = (
|
|
@@ -63,10 +63,10 @@ class SessionMessage:
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
@runtime_checkable
|
|
66
|
-
class
|
|
66
|
+
class LoopSessionStore(Protocol):
|
|
67
67
|
"""Persistence seam between appkit and the application's storage backend."""
|
|
68
68
|
|
|
69
|
-
async def get_session(self, session_id: str) ->
|
|
69
|
+
async def get_session(self, session_id: str) -> LoopSessionEntry | None:
|
|
70
70
|
"""Return the persisted entry for ``session_id``, or None."""
|
|
71
71
|
...
|
|
72
72
|
|
|
@@ -97,4 +97,4 @@ class SessionStore(Protocol):
|
|
|
97
97
|
...
|
|
98
98
|
|
|
99
99
|
|
|
100
|
-
__all__ = ["
|
|
100
|
+
__all__ = ["LoopSessionEntry", "SessionMessage", "LoopSessionStore"]
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Manages a pool of daemon connections, one active per session. Reuses an
|
|
4
4
|
active connection when still live, otherwise bootstraps a fresh loop or
|
|
5
5
|
reattaches an existing one. Persistence of session↔loop mappings is
|
|
6
|
-
abstracted behind ``
|
|
6
|
+
abstracted behind ``LoopSessionStore``.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
@@ -15,6 +15,7 @@ from collections.abc import AsyncIterator
|
|
|
15
15
|
from dataclasses import dataclass
|
|
16
16
|
from typing import Any
|
|
17
17
|
|
|
18
|
+
from soothe_client.appkit.loop_session_store import LoopSessionStore
|
|
18
19
|
from soothe_client.appkit.managed_client import (
|
|
19
20
|
BootstrapFunc,
|
|
20
21
|
ClientFactory,
|
|
@@ -22,7 +23,6 @@ from soothe_client.appkit.managed_client import (
|
|
|
22
23
|
default_bootstrap_func,
|
|
23
24
|
default_client_factory,
|
|
24
25
|
)
|
|
25
|
-
from soothe_client.appkit.session_store import SessionStore
|
|
26
26
|
from soothe_client.errors import StaleLoopError
|
|
27
27
|
|
|
28
28
|
|
|
@@ -96,7 +96,7 @@ class ConnectionPool:
|
|
|
96
96
|
def __init__(
|
|
97
97
|
self,
|
|
98
98
|
url: str,
|
|
99
|
-
store:
|
|
99
|
+
store: LoopSessionStore,
|
|
100
100
|
cfg: PoolConfig | None = None,
|
|
101
101
|
factory: ClientFactory | None = None,
|
|
102
102
|
) -> None:
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/turn_runner.py
RENAMED
|
@@ -21,9 +21,9 @@ from typing import Any
|
|
|
21
21
|
from soothe_client.appkit.attachments import CompactImageOptions, compact_attachments
|
|
22
22
|
from soothe_client.appkit.broadcaster import SSEBroadcaster, SSEEvent
|
|
23
23
|
from soothe_client.appkit.classifier import ChatEventTerminal, EventClassifier
|
|
24
|
+
from soothe_client.appkit.loop_session_store import LoopSessionStore, SessionMessage
|
|
24
25
|
from soothe_client.appkit.pool import ConnectionPool, PooledConn
|
|
25
26
|
from soothe_client.appkit.query_gate import QueryGate
|
|
26
|
-
from soothe_client.appkit.session_store import SessionMessage, SessionStore
|
|
27
27
|
from soothe_client.appkit.turn_boundary import TurnBoundary, is_daemon_turn_end_event
|
|
28
28
|
from soothe_client.intent_hints import validate_loop_input_intent_hint
|
|
29
29
|
|
|
@@ -135,7 +135,7 @@ class TurnRunner:
|
|
|
135
135
|
pool: ConnectionPool,
|
|
136
136
|
gate: QueryGate,
|
|
137
137
|
classifier: EventClassifier,
|
|
138
|
-
store:
|
|
138
|
+
store: LoopSessionStore,
|
|
139
139
|
broadcaster: SSEBroadcaster | None,
|
|
140
140
|
cfg: TurnConfig | None = None,
|
|
141
141
|
) -> None:
|
|
@@ -428,18 +428,18 @@ async def fetch_loop_history(
|
|
|
428
428
|
return await client.loop_history_fetch(lid, timeout=timeout)
|
|
429
429
|
|
|
430
430
|
|
|
431
|
-
async def
|
|
431
|
+
async def fetch_execution_state(
|
|
432
432
|
client: WebSocketClient,
|
|
433
433
|
loop_id: str,
|
|
434
434
|
*,
|
|
435
435
|
timeout: float = 30.0,
|
|
436
436
|
) -> dict[str, Any]:
|
|
437
|
-
"""Fetch
|
|
437
|
+
"""Fetch execution-progress snapshot (plan, step_index, iteration, status)."""
|
|
438
438
|
lid = str(loop_id or "").strip()
|
|
439
439
|
if not lid:
|
|
440
440
|
raise ValueError("loop_id is required")
|
|
441
441
|
await _ensure_handshake(client, timeout=timeout)
|
|
442
|
-
return await client.
|
|
442
|
+
return await client.loop_execution_state_fetch(lid, timeout=timeout)
|
|
443
443
|
|
|
444
444
|
|
|
445
445
|
async def fetch_loop_messages(
|
|
@@ -480,6 +480,6 @@ __all__ = [
|
|
|
480
480
|
"fetch_skills_catalog",
|
|
481
481
|
"fetch_config_section",
|
|
482
482
|
"fetch_loop_history",
|
|
483
|
-
"fetch_loop_cards",
|
|
484
483
|
"fetch_loop_messages",
|
|
484
|
+
"fetch_execution_state",
|
|
485
485
|
]
|
|
@@ -9,8 +9,6 @@ IMAGE_TO_TEXT: Final = "image_to_text"
|
|
|
9
9
|
OCR: Final = "ocr"
|
|
10
10
|
EMBED: Final = "embed"
|
|
11
11
|
|
|
12
|
-
REMOVED_INTENT_HINTS: frozenset[str] = frozenset({"direct_llm", "quiz", "direct_model"})
|
|
13
|
-
|
|
14
12
|
# Default deliverable phases for turn-ending replies (excludes plan_direct narration).
|
|
15
13
|
DEFAULT_DELIVERABLE_PHASES: frozenset[str] = frozenset(
|
|
16
14
|
{
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/protocol_params.py
RENAMED
|
@@ -22,7 +22,7 @@ __all__ = [
|
|
|
22
22
|
"LoopMessagesParams",
|
|
23
23
|
"LoopStateGetParams",
|
|
24
24
|
"LoopStateUpdateParams",
|
|
25
|
-
"
|
|
25
|
+
"LoopExecutionStateFetchParams",
|
|
26
26
|
"LoopDetachParams",
|
|
27
27
|
# Subscription params
|
|
28
28
|
"SubscribeParams",
|
|
@@ -269,16 +269,17 @@ class LoopStateUpdateParams(ParamsBase):
|
|
|
269
269
|
as_node: str | None = None
|
|
270
270
|
|
|
271
271
|
|
|
272
|
-
class
|
|
273
|
-
"""Params for ``method=
|
|
272
|
+
class LoopExecutionStateFetchParams(ParamsBase):
|
|
273
|
+
"""Params for ``method=loop_execution_state_fetch``.
|
|
274
|
+
|
|
275
|
+
Returns a focused execution-progress snapshot (plan, step_index,
|
|
276
|
+
iteration, status) for the loop's bound checkpoint thread.
|
|
274
277
|
|
|
275
278
|
Attributes:
|
|
276
279
|
loop_id: Loop identifier (required).
|
|
277
|
-
since: Fetch cards after this sequence.
|
|
278
280
|
"""
|
|
279
281
|
|
|
280
282
|
loop_id: str = Field(..., min_length=1)
|
|
281
|
-
since: str | None = None
|
|
282
283
|
|
|
283
284
|
|
|
284
285
|
class LoopHistoryFetchParams(ParamsBase):
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/stream_terminal.py
RENAMED
|
@@ -9,6 +9,11 @@ from __future__ import annotations
|
|
|
9
9
|
from typing import Any, TypeGuard
|
|
10
10
|
|
|
11
11
|
from soothe_sdk.core.events import (
|
|
12
|
+
CARD_CREATED,
|
|
13
|
+
CARD_FINALIZED,
|
|
14
|
+
CARD_REPLAY_BEGIN,
|
|
15
|
+
CARD_REPLAY_END,
|
|
16
|
+
CARD_UPDATED,
|
|
12
17
|
PLAN_CREATED,
|
|
13
18
|
STRANGE_LOOP_COMPLETED,
|
|
14
19
|
STRANGE_LOOP_STEP_COMPLETED,
|
|
@@ -28,6 +33,9 @@ _TURN_PROGRESS_CUSTOM_TYPES = frozenset(
|
|
|
28
33
|
STRANGE_LOOP_STEP_STARTED,
|
|
29
34
|
STRANGE_LOOP_STEP_QUEUED,
|
|
30
35
|
STRANGE_LOOP_STEP_COMPLETED,
|
|
36
|
+
CARD_CREATED,
|
|
37
|
+
CARD_UPDATED,
|
|
38
|
+
CARD_FINALIZED,
|
|
31
39
|
}
|
|
32
40
|
)
|
|
33
41
|
|
|
@@ -35,9 +43,9 @@ _TURN_PROGRESS_CUSTOM_TYPES = frozenset(
|
|
|
35
43
|
STALE_TURN_PENDING_TYPES = frozenset(
|
|
36
44
|
{
|
|
37
45
|
"connection_ack",
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
CARD_REPLAY_BEGIN,
|
|
47
|
+
CARD_REPLAY_END,
|
|
48
|
+
CARD_CREATED,
|
|
41
49
|
"complete",
|
|
42
50
|
}
|
|
43
51
|
)
|
|
@@ -1066,14 +1066,6 @@ class WebSocketClient:
|
|
|
1066
1066
|
timeout=timeout,
|
|
1067
1067
|
)
|
|
1068
1068
|
|
|
1069
|
-
async def loop_cards_fetch(self, loop_id: str, *, timeout: float = 30.0) -> dict[str, Any]:
|
|
1070
|
-
"""Fetch bound display-card snapshot (``loop_cards_fetch``)."""
|
|
1071
|
-
return await self.request(
|
|
1072
|
-
"loop_cards_fetch",
|
|
1073
|
-
{"loop_id": loop_id},
|
|
1074
|
-
timeout=timeout,
|
|
1075
|
-
)
|
|
1076
|
-
|
|
1077
1069
|
async def loop_messages(
|
|
1078
1070
|
self,
|
|
1079
1071
|
loop_id: str,
|
|
@@ -1103,6 +1095,20 @@ class WebSocketClient:
|
|
|
1103
1095
|
timeout=timeout,
|
|
1104
1096
|
)
|
|
1105
1097
|
|
|
1098
|
+
async def loop_execution_state_fetch(
|
|
1099
|
+
self, loop_id: str, *, timeout: float = 30.0
|
|
1100
|
+
) -> dict[str, Any]:
|
|
1101
|
+
"""Fetch execution-progress snapshot (``loop_execution_state_fetch``).
|
|
1102
|
+
|
|
1103
|
+
Returns ``{plan, step_index, iteration, status}`` for the loop's bound
|
|
1104
|
+
checkpoint thread.
|
|
1105
|
+
"""
|
|
1106
|
+
return await self.request(
|
|
1107
|
+
"loop_execution_state_fetch",
|
|
1108
|
+
{"loop_id": loop_id},
|
|
1109
|
+
timeout=timeout,
|
|
1110
|
+
)
|
|
1111
|
+
|
|
1106
1112
|
async def loop_state_update(
|
|
1107
1113
|
self,
|
|
1108
1114
|
loop_id: str,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/attachments.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/broadcaster.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/chunk_filter.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/classifier.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/events.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/managed_client.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/observability.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/query_gate.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/thinking_step.py
RENAMED
|
File without changes
|
|
File without changes
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/appkit/turn_boundary.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/command_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{soothe_client_python-1.0.3 → soothe_client_python-1.0.7}/src/soothe_client/turn_boundary.py
RENAMED
|
File without changes
|