soothe-client-python 1.0.8__tar.gz → 1.0.9__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.8 → soothe_client_python-1.0.9}/PKG-INFO +1 -1
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/daemon_session.py +68 -19
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/turn_boundary.py +58 -10
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/turn_boundary.py +57 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/.gitignore +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/LICENSE +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/README.md +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/pyproject.toml +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/__init__.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/__init__.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/attachments.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/broadcaster.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/card_projection.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/chunk_filter.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/classifier.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/events.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/loop_session_store.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/managed_client.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/observability.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/pool.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/query_gate.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/thinking_step.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/turn.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/turn_runner.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/command_client.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/errors.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/helpers.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/intent_hints.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/protocol_params.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/py.typed +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/schemas.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/session.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/stream_terminal.py +0 -0
- {soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/websocket.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.9
|
|
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
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/daemon_session.py
RENAMED
|
@@ -24,7 +24,14 @@ from soothe_client.stream_terminal import (
|
|
|
24
24
|
is_turn_end_custom_data,
|
|
25
25
|
is_turn_progress_chunk,
|
|
26
26
|
)
|
|
27
|
-
from soothe_client.turn_boundary import
|
|
27
|
+
from soothe_client.turn_boundary import (
|
|
28
|
+
frame_seq,
|
|
29
|
+
frame_turn_id,
|
|
30
|
+
is_idle_terminal_allowed,
|
|
31
|
+
is_turn_terminal_allowed,
|
|
32
|
+
parse_turn_generation,
|
|
33
|
+
turn_ids_match,
|
|
34
|
+
)
|
|
28
35
|
from soothe_client.websocket import WebSocketClient
|
|
29
36
|
|
|
30
37
|
logger = logging.getLogger(__name__)
|
|
@@ -368,7 +375,6 @@ class DaemonSession:
|
|
|
368
375
|
# Bind turn_id only after this turn's status=running (do not reuse prior).
|
|
369
376
|
self._expected_turn_id = None
|
|
370
377
|
expected_turn_id: str | None = None
|
|
371
|
-
stream_payload_seen = False
|
|
372
378
|
turn_progress_seen = False
|
|
373
379
|
last_end_seq = int(getattr(self, "_last_turn_end_seq", 0) or 0)
|
|
374
380
|
self._last_turn_end_seq = last_end_seq
|
|
@@ -439,23 +445,40 @@ class DaemonSession:
|
|
|
439
445
|
if ev_seq is not None and last_end_seq > 0 and ev_seq <= last_end_seq:
|
|
440
446
|
continue
|
|
441
447
|
|
|
442
|
-
#
|
|
443
|
-
# which may rebind to a newer
|
|
444
|
-
# running can
|
|
448
|
+
# Once bound, drop absent/mismatched turn_id on event/status
|
|
449
|
+
# (except status=running, which may rebind to a newer gen;
|
|
450
|
+
# pre-admit early running can omit turn_id). Idle/stopped
|
|
451
|
+
# fall through to their gates (cancel idle may omit turn_id).
|
|
445
452
|
ev_turn_id = frame_turn_id(event)
|
|
446
|
-
|
|
447
|
-
|
|
453
|
+
status_state = (
|
|
454
|
+
str(event.get("state") or "") if event_type == "status" else ""
|
|
448
455
|
)
|
|
456
|
+
is_running_status = status_state == "running"
|
|
457
|
+
is_terminal_status = status_state in {"idle", "stopped"}
|
|
449
458
|
if (
|
|
450
459
|
expected_turn_id
|
|
451
|
-
and ev_turn_id
|
|
452
|
-
and ev_turn_id != expected_turn_id
|
|
453
460
|
and event_type in {"event", "status"}
|
|
454
461
|
and not is_running_status
|
|
462
|
+
and not is_terminal_status
|
|
463
|
+
and not turn_ids_match(expected_turn_id, ev_turn_id)
|
|
455
464
|
):
|
|
456
465
|
logger.debug(
|
|
457
|
-
"Ignoring mismatched turn_id frame type=%s
|
|
466
|
+
"Ignoring unbound/mismatched turn_id frame type=%s "
|
|
467
|
+
"got=%s expected=%s",
|
|
458
468
|
event_type,
|
|
469
|
+
(ev_turn_id or "<absent>")[-24:],
|
|
470
|
+
expected_turn_id[-24:],
|
|
471
|
+
)
|
|
472
|
+
continue
|
|
473
|
+
if (
|
|
474
|
+
expected_turn_id
|
|
475
|
+
and is_terminal_status
|
|
476
|
+
and ev_turn_id
|
|
477
|
+
and not turn_ids_match(expected_turn_id, ev_turn_id)
|
|
478
|
+
):
|
|
479
|
+
logger.debug(
|
|
480
|
+
"Ignoring mismatched terminal status=%s got=%s expected=%s",
|
|
481
|
+
status_state,
|
|
459
482
|
ev_turn_id[-24:],
|
|
460
483
|
expected_turn_id[-24:],
|
|
461
484
|
)
|
|
@@ -478,6 +501,9 @@ class DaemonSession:
|
|
|
478
501
|
query_started = True
|
|
479
502
|
progress_seen = True
|
|
480
503
|
status_turn = frame_turn_id(event)
|
|
504
|
+
# Bind only from running frames that carry turn_id.
|
|
505
|
+
# Pre-admit early running omits turn_id and must not
|
|
506
|
+
# unlock terminals or poison the expected id.
|
|
481
507
|
if status_turn:
|
|
482
508
|
new_gen = parse_turn_generation(status_turn)
|
|
483
509
|
old_gen = parse_turn_generation(expected_turn_id)
|
|
@@ -487,11 +513,15 @@ class DaemonSession:
|
|
|
487
513
|
if expected_turn_id and status_turn != expected_turn_id:
|
|
488
514
|
# Successor turn admitted while this reader
|
|
489
515
|
# still held a stale/early binding.
|
|
490
|
-
stream_payload_seen = False
|
|
491
516
|
turn_progress_seen = False
|
|
492
517
|
expected_turn_id = status_turn
|
|
493
518
|
self._expected_turn_id = status_turn
|
|
494
519
|
elif query_started and state == "stopped":
|
|
520
|
+
stop_turn = frame_turn_id(event)
|
|
521
|
+
if expected_turn_id and not turn_ids_match(
|
|
522
|
+
expected_turn_id, stop_turn
|
|
523
|
+
):
|
|
524
|
+
continue
|
|
495
525
|
self.last_turn_end_state = state
|
|
496
526
|
if ev_seq is not None:
|
|
497
527
|
self._last_turn_end_seq = max(self._last_turn_end_seq, ev_seq)
|
|
@@ -501,7 +531,22 @@ class DaemonSession:
|
|
|
501
531
|
yield chunk
|
|
502
532
|
break
|
|
503
533
|
elif query_started and state == "idle":
|
|
504
|
-
|
|
534
|
+
idle_turn = frame_turn_id(event)
|
|
535
|
+
if not is_idle_terminal_allowed(
|
|
536
|
+
expected_turn_id=expected_turn_id,
|
|
537
|
+
frame_turn_id=idle_turn,
|
|
538
|
+
query_started=query_started,
|
|
539
|
+
turn_progress_seen=turn_progress_seen,
|
|
540
|
+
cancellation_seen=self.last_turn_cancellation_seen,
|
|
541
|
+
):
|
|
542
|
+
logger.debug(
|
|
543
|
+
"Ignoring premature idle "
|
|
544
|
+
"(loop=%s bound=%s idle_turn=%s progress=%s)",
|
|
545
|
+
(expected_loop_id or "?")[:16],
|
|
546
|
+
bool(expected_turn_id),
|
|
547
|
+
(idle_turn or "<absent>")[-24:],
|
|
548
|
+
turn_progress_seen,
|
|
549
|
+
)
|
|
505
550
|
continue
|
|
506
551
|
self.last_turn_end_state = state
|
|
507
552
|
if ev_seq is not None:
|
|
@@ -529,30 +574,34 @@ class DaemonSession:
|
|
|
529
574
|
self.turn_event_stats.filtered_early += 1
|
|
530
575
|
continue
|
|
531
576
|
|
|
532
|
-
#
|
|
577
|
+
# Strict turn_id match: absent ids never unlock turn-end.
|
|
533
578
|
if mode == "custom" and is_turn_end_custom_data(data):
|
|
534
579
|
data_turn = (
|
|
535
580
|
frame_turn_id(data if isinstance(data, dict) else None) or ev_turn_id
|
|
536
581
|
)
|
|
537
|
-
turn_ok = (
|
|
538
|
-
|
|
582
|
+
turn_ok = is_turn_terminal_allowed(
|
|
583
|
+
expected_turn_id=expected_turn_id,
|
|
584
|
+
frame_turn_id=data_turn,
|
|
585
|
+
query_started=query_started,
|
|
586
|
+
turn_progress_seen=turn_progress_seen,
|
|
539
587
|
)
|
|
540
|
-
if not turn_ok
|
|
588
|
+
if not turn_ok:
|
|
541
589
|
logger.debug(
|
|
542
590
|
"Ignoring premature turn-end frame %s "
|
|
543
|
-
"(loop=%s query_started=%s progress=%s
|
|
591
|
+
"(loop=%s query_started=%s progress=%s "
|
|
592
|
+
"bound=%s data_turn=%s)",
|
|
544
593
|
str(data.get("type", "")).strip()
|
|
545
594
|
if isinstance(data, dict)
|
|
546
595
|
else "?",
|
|
547
596
|
(expected_loop_id or "?")[:16],
|
|
548
597
|
query_started,
|
|
549
598
|
turn_progress_seen,
|
|
550
|
-
|
|
599
|
+
bool(expected_turn_id),
|
|
600
|
+
(data_turn or "<absent>")[-24:],
|
|
551
601
|
)
|
|
552
602
|
continue
|
|
553
603
|
|
|
554
604
|
progress_seen = True
|
|
555
|
-
stream_payload_seen = True
|
|
556
605
|
if is_turn_progress_chunk(mode, data):
|
|
557
606
|
turn_progress_seen = True
|
|
558
607
|
yield (namespace, mode, data)
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/turn_boundary.py
RENAMED
|
@@ -14,6 +14,13 @@ from soothe_client.stream_terminal import (
|
|
|
14
14
|
is_turn_end_custom_data,
|
|
15
15
|
is_turn_progress_chunk,
|
|
16
16
|
)
|
|
17
|
+
from soothe_client.turn_boundary import (
|
|
18
|
+
frame_turn_id,
|
|
19
|
+
is_idle_terminal_allowed,
|
|
20
|
+
is_turn_terminal_allowed,
|
|
21
|
+
parse_turn_generation,
|
|
22
|
+
turn_ids_match,
|
|
23
|
+
)
|
|
17
24
|
|
|
18
25
|
TURN_END_STREAM_END = STREAM_END
|
|
19
26
|
TURN_END_IDLE = "status.idle"
|
|
@@ -25,8 +32,9 @@ class TurnLifecycleGate:
|
|
|
25
32
|
"""Per-turn progress flags (DaemonSession parity; not shared across chats)."""
|
|
26
33
|
|
|
27
34
|
saw_running: bool = False
|
|
28
|
-
saw_stream_payload: bool = False
|
|
29
35
|
saw_turn_progress: bool = False
|
|
36
|
+
expected_turn_id: str | None = None
|
|
37
|
+
cancellation_seen: bool = False
|
|
30
38
|
|
|
31
39
|
def observe(self, msg: Any) -> None:
|
|
32
40
|
frame = _normalize_frame(msg)
|
|
@@ -36,18 +44,38 @@ class TurnLifecycleGate:
|
|
|
36
44
|
if typ == "status":
|
|
37
45
|
if str(frame.get("state") or "").strip().lower() == "running":
|
|
38
46
|
self.saw_running = True
|
|
47
|
+
status_turn = frame_turn_id(frame)
|
|
48
|
+
if status_turn:
|
|
49
|
+
new_gen = parse_turn_generation(status_turn)
|
|
50
|
+
old_gen = parse_turn_generation(self.expected_turn_id)
|
|
51
|
+
if self.expected_turn_id is None or (
|
|
52
|
+
new_gen is not None and (old_gen is None or new_gen >= old_gen)
|
|
53
|
+
):
|
|
54
|
+
if self.expected_turn_id and status_turn != self.expected_turn_id:
|
|
55
|
+
self.saw_turn_progress = False
|
|
56
|
+
self.expected_turn_id = status_turn
|
|
39
57
|
return
|
|
40
58
|
if typ == "event":
|
|
41
|
-
self.saw_stream_payload = True
|
|
42
59
|
mode = str(frame.get("mode") or "")
|
|
43
60
|
if is_turn_progress_chunk(mode, frame.get("data")):
|
|
44
61
|
self.saw_turn_progress = True
|
|
45
62
|
|
|
46
|
-
def allow_stream_end(self) -> bool:
|
|
47
|
-
return
|
|
63
|
+
def allow_stream_end(self, frame_turn: str | None) -> bool:
|
|
64
|
+
return is_turn_terminal_allowed(
|
|
65
|
+
expected_turn_id=self.expected_turn_id,
|
|
66
|
+
frame_turn_id=frame_turn,
|
|
67
|
+
query_started=self.saw_running,
|
|
68
|
+
turn_progress_seen=self.saw_turn_progress,
|
|
69
|
+
)
|
|
48
70
|
|
|
49
|
-
def allow_idle_complete(self) -> bool:
|
|
50
|
-
return
|
|
71
|
+
def allow_idle_complete(self, frame_turn: str | None) -> bool:
|
|
72
|
+
return is_idle_terminal_allowed(
|
|
73
|
+
expected_turn_id=self.expected_turn_id,
|
|
74
|
+
frame_turn_id=frame_turn,
|
|
75
|
+
query_started=self.saw_running,
|
|
76
|
+
turn_progress_seen=self.saw_turn_progress,
|
|
77
|
+
cancellation_seen=self.cancellation_seen,
|
|
78
|
+
)
|
|
51
79
|
|
|
52
80
|
|
|
53
81
|
@dataclass
|
|
@@ -69,16 +97,28 @@ class TurnBoundary:
|
|
|
69
97
|
typ = str(frame.get("type") or "")
|
|
70
98
|
if typ == "status":
|
|
71
99
|
state = str(frame.get("state") or "").strip().lower()
|
|
100
|
+
frame_turn = frame_turn_id(frame)
|
|
72
101
|
if state == "stopped" and self.gate.saw_running:
|
|
102
|
+
if self.gate.expected_turn_id and not turn_ids_match(
|
|
103
|
+
self.gate.expected_turn_id, frame_turn
|
|
104
|
+
):
|
|
105
|
+
return False, ""
|
|
73
106
|
return self._mark(TURN_END_STOPPED)
|
|
74
|
-
if state == "idle" and self.gate.allow_idle_complete():
|
|
107
|
+
if state == "idle" and self.gate.allow_idle_complete(frame_turn):
|
|
75
108
|
return self._mark(TURN_END_IDLE)
|
|
76
109
|
return False, ""
|
|
77
110
|
|
|
78
111
|
if typ == "event":
|
|
79
112
|
mode = str(frame.get("mode") or "")
|
|
80
113
|
data = frame.get("data")
|
|
81
|
-
|
|
114
|
+
data_turn = frame_turn_id(data if isinstance(data, dict) else None) or frame_turn_id(
|
|
115
|
+
frame
|
|
116
|
+
)
|
|
117
|
+
if (
|
|
118
|
+
mode == "custom"
|
|
119
|
+
and is_turn_end_custom_data(data)
|
|
120
|
+
and self.gate.allow_stream_end(data_turn)
|
|
121
|
+
):
|
|
82
122
|
return self._mark(TURN_END_STREAM_END)
|
|
83
123
|
return False, ""
|
|
84
124
|
|
|
@@ -109,19 +149,27 @@ def _normalize_frame(msg: Any) -> dict[str, Any] | None:
|
|
|
109
149
|
if isinstance(inner, dict) and inner.get("type") == "status":
|
|
110
150
|
return inner
|
|
111
151
|
if isinstance(inner, dict) and inner.get("mode"):
|
|
112
|
-
|
|
152
|
+
out = {
|
|
113
153
|
"type": "event",
|
|
114
154
|
"mode": inner.get("mode"),
|
|
115
155
|
"data": inner.get("data"),
|
|
116
156
|
"namespace": inner.get("namespace") or payload.get("namespace"),
|
|
117
157
|
}
|
|
158
|
+
tid = inner.get("turn_id") or payload.get("turn_id") or msg.get("turn_id")
|
|
159
|
+
if tid:
|
|
160
|
+
out["turn_id"] = tid
|
|
161
|
+
return out
|
|
118
162
|
mode = payload.get("mode")
|
|
119
163
|
if mode:
|
|
120
|
-
|
|
164
|
+
out = {
|
|
121
165
|
"type": "event",
|
|
122
166
|
"mode": mode,
|
|
123
167
|
"data": payload.get("data"),
|
|
124
168
|
"namespace": payload.get("namespace"),
|
|
125
169
|
}
|
|
170
|
+
tid = payload.get("turn_id") or msg.get("turn_id")
|
|
171
|
+
if tid:
|
|
172
|
+
out["turn_id"] = tid
|
|
173
|
+
return out
|
|
126
174
|
return None
|
|
127
175
|
return msg
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/turn_boundary.py
RENAMED
|
@@ -61,9 +61,66 @@ def frame_seq(frame: dict[str, Any] | None) -> int | None:
|
|
|
61
61
|
return None
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
def turn_ids_match(expected: str | None, candidate: str | None) -> bool:
|
|
65
|
+
"""True when both ids are non-empty and equal.
|
|
66
|
+
|
|
67
|
+
Absent ids never match — callers must not treat missing ``turn_id`` as
|
|
68
|
+
compatible with a bound turn (prior-goal terminals / idle can omit or
|
|
69
|
+
carry a stale generation).
|
|
70
|
+
"""
|
|
71
|
+
exp = str(expected or "").strip()
|
|
72
|
+
cand = str(candidate or "").strip()
|
|
73
|
+
return bool(exp) and bool(cand) and exp == cand
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def is_turn_terminal_allowed(
|
|
77
|
+
*,
|
|
78
|
+
expected_turn_id: str | None,
|
|
79
|
+
frame_turn_id: str | None,
|
|
80
|
+
query_started: bool,
|
|
81
|
+
turn_progress_seen: bool,
|
|
82
|
+
) -> bool:
|
|
83
|
+
"""Gate for turn-scoped ``stream.end`` / ``strange_loop.completed``.
|
|
84
|
+
|
|
85
|
+
Requires an active query, real turn progress, a bound ``expected_turn_id``,
|
|
86
|
+
and an exact ``frame_turn_id`` match. Absent ids are rejected.
|
|
87
|
+
"""
|
|
88
|
+
if not query_started or not turn_progress_seen:
|
|
89
|
+
return False
|
|
90
|
+
return turn_ids_match(expected_turn_id, frame_turn_id)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def is_idle_terminal_allowed(
|
|
94
|
+
*,
|
|
95
|
+
expected_turn_id: str | None,
|
|
96
|
+
frame_turn_id: str | None,
|
|
97
|
+
query_started: bool,
|
|
98
|
+
turn_progress_seen: bool,
|
|
99
|
+
cancellation_seen: bool = False,
|
|
100
|
+
) -> bool:
|
|
101
|
+
"""Gate for ``status=idle`` soft-complete.
|
|
102
|
+
|
|
103
|
+
Requires a bound turn. Matching ``turn_id`` needs progress or cancel.
|
|
104
|
+
Absent idle ``turn_id`` is allowed only when cancellation was already seen
|
|
105
|
+
(legacy cancel finalize); mismatched ids are always rejected.
|
|
106
|
+
"""
|
|
107
|
+
if not query_started or not str(expected_turn_id or "").strip():
|
|
108
|
+
return False
|
|
109
|
+
cand = str(frame_turn_id or "").strip()
|
|
110
|
+
if cand:
|
|
111
|
+
if not turn_ids_match(expected_turn_id, cand):
|
|
112
|
+
return False
|
|
113
|
+
return bool(turn_progress_seen or cancellation_seen)
|
|
114
|
+
# Absent idle turn_id: never end a healthy turn; cancel path only.
|
|
115
|
+
return bool(cancellation_seen)
|
|
116
|
+
|
|
117
|
+
|
|
64
118
|
__all__ = [
|
|
65
119
|
"format_turn_id",
|
|
66
120
|
"frame_seq",
|
|
67
121
|
"frame_turn_id",
|
|
122
|
+
"is_idle_terminal_allowed",
|
|
123
|
+
"is_turn_terminal_allowed",
|
|
68
124
|
"parse_turn_generation",
|
|
125
|
+
"turn_ids_match",
|
|
69
126
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/__init__.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/attachments.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/broadcaster.py
RENAMED
|
File without changes
|
|
File without changes
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/chunk_filter.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/classifier.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/events.py
RENAMED
|
File without changes
|
|
File without changes
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/managed_client.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/observability.py
RENAMED
|
File without changes
|
|
File without changes
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/query_gate.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/thinking_step.py
RENAMED
|
File without changes
|
|
File without changes
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/appkit/turn_runner.py
RENAMED
|
File without changes
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/command_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/protocol_params.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{soothe_client_python-1.0.8 → soothe_client_python-1.0.9}/src/soothe_client/stream_terminal.py
RENAMED
|
File without changes
|
|
File without changes
|