muse-code-sdk 1.4.6__tar.gz → 1.4.8__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.
- {muse_code_sdk-1.4.6/src/muse_code_sdk.egg-info → muse_code_sdk-1.4.8}/PKG-INFO +1 -1
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/pyproject.toml +1 -1
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/__init__.py +2 -2
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/facade/client.py +7 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/facade/turn_handle.py +4 -3
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/fold/session_fold.py +26 -1
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8/src/muse_code_sdk.egg-info}/PKG-INFO +1 -1
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_facade_turn.py +45 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_session_fold.py +72 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/LICENSE +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/README.md +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/setup.cfg +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/connection/__init__.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/connection/connection.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/connection/discovery.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/connection/spawn.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/connection/validation.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/errors.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/facade/__init__.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/facade/approval.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/facade/discarded.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/facade/gap_fill.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/facade/host_death.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/facade/host_start.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/facade/session.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/facade/turn_submit.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/fold/__init__.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/fold/item_store.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/fold/state_store.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/pending/__init__.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/pending/pending_command_set.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/py.typed +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/replay.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code/sync_facade.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code_sdk.egg-info/SOURCES.txt +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code_sdk.egg-info/dependency_links.txt +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code_sdk.egg-info/requires.txt +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/src/muse_code_sdk.egg-info/top_level.txt +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_asyncio_cancellation.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_compatibility_row.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_connection.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_connection_error_arms.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_docs_generation.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_external_audience.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_facade_approval.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_facade_gap_fill.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_facade_open_same_chunk.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_facade_persistent_approval.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_facade_required_selection.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_host_discovery.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_item_store.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_manifests.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_muse_serve_child.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_pending_command_corpus.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_pending_command_set.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_pending_command_wire_binding.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_permission_state_wire_types.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_public_mirror_text.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_publish_shape.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_source_closure.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_spawn_handshake.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_spawn_host_lost.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_state_store.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_sync_facade.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_tolerance_corpus.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_transcript_replay.py +0 -0
- {muse_code_sdk-1.4.6 → muse_code_sdk-1.4.8}/tests/test_typing_gates.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: muse-code-sdk
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.8
|
|
4
4
|
Summary: Muse Code SDK for MSP: asyncio-first facade over the generated wire types (muse-code-msp), with a thin sync wrapper.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/meta-models/muse-code-sdk
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "muse-code-sdk"
|
|
8
|
-
version = "1.4.
|
|
8
|
+
version = "1.4.8"
|
|
9
9
|
description = "Muse Code SDK for MSP: asyncio-first facade over the generated wire types (muse-code-msp), with a thin sync wrapper."
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.10"
|
|
@@ -80,7 +80,7 @@ __all__ = [
|
|
|
80
80
|
]
|
|
81
81
|
|
|
82
82
|
EXPECTED_SCHEMA_FINGERPRINT: Final[str] = (
|
|
83
|
-
"sha256:
|
|
83
|
+
"sha256:4e3200c3c9dc0cd7919d11022630d43f9bc1c5cdda375381e90b0183b5665069"
|
|
84
84
|
)
|
|
85
85
|
"""The stable schema-bundle fingerprint this SDK was built against.
|
|
86
86
|
|
|
@@ -92,7 +92,7 @@ Python SDK reds this lane instead of drifting silently.
|
|
|
92
92
|
# The lockstep rule: the release train bumps pyproject.toml, the
|
|
93
93
|
# distribution-metadata authority; this constant is aligned at publication
|
|
94
94
|
# time.
|
|
95
|
-
__version__: Final[str] = "1.4.
|
|
95
|
+
__version__: Final[str] = "1.4.8"
|
|
96
96
|
|
|
97
97
|
# The facade is imported last so the constants above are already bound: the
|
|
98
98
|
# spawn path reads ``EXPECTED_SCHEMA_FINGERPRINT`` (lazily, at initialize
|
|
@@ -130,12 +130,17 @@ class ResumeSessionOptions:
|
|
|
130
130
|
exclude_items: Ask the host to omit item bodies from the history.
|
|
131
131
|
history: The history preference forwarded as the wire's ``history``
|
|
132
132
|
member.
|
|
133
|
+
permission_profile: The permission profile pick forwarded as the
|
|
134
|
+
wire's ``permissionProfile`` member: the host restores the
|
|
135
|
+
retained authority first, then applies the pick. Omitted means
|
|
136
|
+
no override.
|
|
133
137
|
"""
|
|
134
138
|
|
|
135
139
|
session_id: str
|
|
136
140
|
cursor: str | None = None
|
|
137
141
|
exclude_items: bool | None = None
|
|
138
142
|
history: Mapping[str, Any] | None = None
|
|
143
|
+
permission_profile: str | None = None
|
|
139
144
|
|
|
140
145
|
|
|
141
146
|
# Drift pins — the Python twin of the TS `AssertNever` on START_FORWARDED /
|
|
@@ -483,6 +488,8 @@ class MuseClient(Generic[_I]):
|
|
|
483
488
|
params["excludeItems"] = options.exclude_items
|
|
484
489
|
if options.history is not None:
|
|
485
490
|
params["history"] = options.history
|
|
491
|
+
if options.permission_profile is not None:
|
|
492
|
+
params["permissionProfile"] = options.permission_profile
|
|
486
493
|
return await self._open("session/resume", params, _ResumeOpening)
|
|
487
494
|
|
|
488
495
|
async def close(self) -> None:
|
|
@@ -97,9 +97,10 @@ class _TerminalUnknown:
|
|
|
97
97
|
|
|
98
98
|
TurnOutcome = Union[_Completed, _Unqueued, _TerminalUnknown]
|
|
99
99
|
"""How a turn ended, from a waiter's point of view. ``turn/retracted``,
|
|
100
|
-
``turn/retryScheduled``,
|
|
101
|
-
absent: a retracted turn still
|
|
102
|
-
|
|
100
|
+
``turn/retryScheduled``, ``turn/modelRetryScheduled``, and
|
|
101
|
+
``turn/foregroundCompleted`` are deliberately absent: a retracted turn still
|
|
102
|
+
reaches its own terminal, and a scheduled retry, a scheduled backoff, or a
|
|
103
|
+
foreground notice is explicitly non-terminal."""
|
|
103
104
|
|
|
104
105
|
|
|
105
106
|
def is_launch_failure(outcome: TurnOutcome) -> bool:
|
|
@@ -10,7 +10,8 @@ are neither an item nor a state family:
|
|
|
10
10
|
|
|
11
11
|
- the TURN LIFECYCLE — ``turn/started``, ``turn/completed``,
|
|
12
12
|
``turn/retracted``, ``turn/unqueued``, and the non-terminal
|
|
13
|
-
``turn/retryScheduled
|
|
13
|
+
``turn/retryScheduled``, ``turn/modelRetryScheduled``, and
|
|
14
|
+
``turn/foregroundCompleted``;
|
|
14
15
|
- the APPROVAL and USER-INPUT view events as fold INPUTS — the pending sets
|
|
15
16
|
and their first durable terminal. The decision flow (choosing and sending a
|
|
16
17
|
resolution) is the facade's, not the fold's;
|
|
@@ -71,6 +72,7 @@ VIEW_EVENT_METHODS: frozenset[str] = frozenset(
|
|
|
71
72
|
"turn/foregroundCompleted",
|
|
72
73
|
"turn/retracted",
|
|
73
74
|
"turn/retryScheduled",
|
|
75
|
+
"turn/modelRetryScheduled",
|
|
74
76
|
"turn/unqueued",
|
|
75
77
|
"approval/requested",
|
|
76
78
|
"approval/updated",
|
|
@@ -139,6 +141,9 @@ class TurnEntry:
|
|
|
139
141
|
``turn/completed`` folded.
|
|
140
142
|
error: Present iff the terminal was ``"failed"``.
|
|
141
143
|
retry_scheduled: The latest scheduled model retry; non-terminal.
|
|
144
|
+
model_retry_scheduled: The latest scheduled provider-backoff retry;
|
|
145
|
+
non-terminal. The model-attempt twin of ``retry_scheduled``;
|
|
146
|
+
clears on the turn's terminal.
|
|
142
147
|
foreground_completed: The latest foreground-completed notice;
|
|
143
148
|
non-terminal. Tells a renderer the answer is done while
|
|
144
149
|
``blockingAgents`` hold the turn; clears on the turn's terminal.
|
|
@@ -150,6 +155,7 @@ class TurnEntry:
|
|
|
150
155
|
terminal: str | None = None
|
|
151
156
|
error: Params | None = None
|
|
152
157
|
retry_scheduled: Params | None = None
|
|
158
|
+
model_retry_scheduled: Params | None = None
|
|
153
159
|
foreground_completed: Params | None = None
|
|
154
160
|
|
|
155
161
|
|
|
@@ -422,6 +428,7 @@ class _InternalTurn:
|
|
|
422
428
|
terminal: str | None = None
|
|
423
429
|
error: Params | None = None
|
|
424
430
|
retry_scheduled: Params | None = None
|
|
431
|
+
model_retry_scheduled: Params | None = None
|
|
425
432
|
foreground_completed: Params | None = None
|
|
426
433
|
|
|
427
434
|
|
|
@@ -572,6 +579,8 @@ class SessionFold:
|
|
|
572
579
|
return self._turn_unqueued(params)
|
|
573
580
|
if method == "turn/retryScheduled":
|
|
574
581
|
return self._turn_retry_scheduled(params)
|
|
582
|
+
if method == "turn/modelRetryScheduled":
|
|
583
|
+
return self._turn_model_retry_scheduled(params)
|
|
575
584
|
if method == "turn/foregroundCompleted":
|
|
576
585
|
return self._turn_foreground_completed(params)
|
|
577
586
|
if method == "approval/requested":
|
|
@@ -675,6 +684,9 @@ class SessionFold:
|
|
|
675
684
|
# The retry hint is a "retrying in Ns" countdown, and it clears on the
|
|
676
685
|
# turn's next event — which a completion is.
|
|
677
686
|
turn.retry_scheduled = None
|
|
687
|
+
# Same for the provider-backoff hint: a finished turn is not
|
|
688
|
+
# waiting out a backoff.
|
|
689
|
+
turn.model_retry_scheduled = None
|
|
678
690
|
# Same for the foreground hint: a finished turn is not
|
|
679
691
|
# background-waiting.
|
|
680
692
|
turn.foreground_completed = None
|
|
@@ -720,6 +732,18 @@ class SessionFold:
|
|
|
720
732
|
turn.retry_scheduled = params
|
|
721
733
|
return TurnFold(turn_id, turn.state)
|
|
722
734
|
|
|
735
|
+
def _turn_model_retry_scheduled(self, params: Params) -> FoldOutcome:
|
|
736
|
+
# Non-terminal by contract: it never resolves a turn-wait.
|
|
737
|
+
turn_id = str(params["turnId"])
|
|
738
|
+
turn = self._turn_for(turn_id)
|
|
739
|
+
# ...but it is still a turn frame, so it takes the same redelivery
|
|
740
|
+
# guard as the retry hint: a replayed page must not re-plant the
|
|
741
|
+
# provider-backoff hint on a turn that already left `running`.
|
|
742
|
+
if turn.state != "running":
|
|
743
|
+
return IgnoredStaleFrame("turn/modelRetryScheduled", turn_id)
|
|
744
|
+
turn.model_retry_scheduled = params
|
|
745
|
+
return TurnFold(turn_id, turn.state)
|
|
746
|
+
|
|
723
747
|
def _turn_foreground_completed(self, params: Params) -> FoldOutcome:
|
|
724
748
|
# Non-terminal by contract: it never resolves a turn-wait.
|
|
725
749
|
turn_id = str(params["turnId"])
|
|
@@ -753,6 +777,7 @@ class SessionFold:
|
|
|
753
777
|
terminal=turn.terminal,
|
|
754
778
|
error=turn.error,
|
|
755
779
|
retry_scheduled=turn.retry_scheduled,
|
|
780
|
+
model_retry_scheduled=turn.model_retry_scheduled,
|
|
756
781
|
foreground_completed=turn.foreground_completed,
|
|
757
782
|
)
|
|
758
783
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: muse-code-sdk
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.8
|
|
4
4
|
Summary: Muse Code SDK for MSP: asyncio-first facade over the generated wire types (muse-code-msp), with a thin sync wrapper.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/meta-models/muse-code-sdk
|
|
@@ -139,6 +139,25 @@ def turn_retry_scheduled(turn_id: str, view_cursor: str) -> dict[str, Any]:
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
|
|
142
|
+
def turn_model_retry_scheduled(turn_id: str, view_cursor: str) -> dict[str, Any]:
|
|
143
|
+
return {
|
|
144
|
+
"method": "turn/modelRetryScheduled",
|
|
145
|
+
"params": {
|
|
146
|
+
"attempt": 2,
|
|
147
|
+
"errorKind": "overloaded",
|
|
148
|
+
"httpStatus": 529,
|
|
149
|
+
"maxAttempts": 5,
|
|
150
|
+
"nextAttempt": 3,
|
|
151
|
+
"retryDelayMs": 4_000,
|
|
152
|
+
"sessionId": SESSION,
|
|
153
|
+
"sourceRange": SOURCE,
|
|
154
|
+
"system": "meta",
|
|
155
|
+
"turnId": turn_id,
|
|
156
|
+
"viewCursor": view_cursor,
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
142
161
|
def turn_foreground_completed(turn_id: str, view_cursor: str) -> dict[str, Any]:
|
|
143
162
|
return {
|
|
144
163
|
"method": "turn/foregroundCompleted",
|
|
@@ -331,6 +350,32 @@ async def test_turn_retry_scheduled_never_settles_the_wait() -> None:
|
|
|
331
350
|
await watcher
|
|
332
351
|
|
|
333
352
|
|
|
353
|
+
@pytest.mark.asyncio
|
|
354
|
+
async def test_turn_model_retry_scheduled_never_settles_the_wait() -> None:
|
|
355
|
+
s = session()
|
|
356
|
+
turn = s.turn("t-1")
|
|
357
|
+
s.apply(turn_started("t-1", "v:1"))
|
|
358
|
+
s.apply(turn_model_retry_scheduled("t-1", "v:2"))
|
|
359
|
+
|
|
360
|
+
settled = False
|
|
361
|
+
|
|
362
|
+
async def watch() -> None:
|
|
363
|
+
nonlocal settled
|
|
364
|
+
await turn.completed
|
|
365
|
+
settled = True
|
|
366
|
+
|
|
367
|
+
import asyncio
|
|
368
|
+
|
|
369
|
+
watcher = asyncio.ensure_future(watch())
|
|
370
|
+
for _ in range(5):
|
|
371
|
+
await asyncio.sleep(0)
|
|
372
|
+
assert settled is False, "a scheduled backoff is a fact about a RUNNING turn"
|
|
373
|
+
|
|
374
|
+
s.apply(turn_completed("t-1", "completed", "v:3"))
|
|
375
|
+
assert (await turn.completed).kind == "completed"
|
|
376
|
+
await watcher
|
|
377
|
+
|
|
378
|
+
|
|
334
379
|
@pytest.mark.asyncio
|
|
335
380
|
async def test_turn_foreground_completed_never_settles_the_wait() -> None:
|
|
336
381
|
s = session()
|
|
@@ -110,6 +110,25 @@ def retry_scheduled(turn_id: str, view_cursor: str) -> dict[str, Any]:
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
|
|
113
|
+
def model_retry_scheduled(turn_id: str, view_cursor: str) -> dict[str, Any]:
|
|
114
|
+
return {
|
|
115
|
+
"method": "turn/modelRetryScheduled",
|
|
116
|
+
"params": {
|
|
117
|
+
"attempt": 2,
|
|
118
|
+
"errorKind": "overloaded",
|
|
119
|
+
"httpStatus": 529,
|
|
120
|
+
"maxAttempts": 5,
|
|
121
|
+
"nextAttempt": 3,
|
|
122
|
+
"retryDelayMs": 4_000,
|
|
123
|
+
"sessionId": SESSION,
|
|
124
|
+
"sourceRange": SOURCE,
|
|
125
|
+
"system": "meta",
|
|
126
|
+
"turnId": turn_id,
|
|
127
|
+
"viewCursor": view_cursor,
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
113
132
|
def foreground_completed(turn_id: str, view_cursor: str) -> dict[str, Any]:
|
|
114
133
|
return {
|
|
115
134
|
"method": "turn/foregroundCompleted",
|
|
@@ -493,6 +512,58 @@ def test_a_replayed_page_never_re_plants_the_retry_hint_on_a_turn_that_left_runn
|
|
|
493
512
|
assert fold.apply(retry) == IgnoredStaleFrame("turn/retryScheduled", "turn-1")
|
|
494
513
|
|
|
495
514
|
|
|
515
|
+
def test_turn_model_retry_scheduled_is_non_terminal_the_turn_keeps_running() -> None:
|
|
516
|
+
fold = SessionFold()
|
|
517
|
+
fold.apply(turn_started("turn-1", "cmd-1", "v:s:1"))
|
|
518
|
+
fold.apply(model_retry_scheduled("turn-1", "v:s:2"))
|
|
519
|
+
|
|
520
|
+
assert fold.active_turn_id == "turn-1", "a scheduled backoff never settles a turn"
|
|
521
|
+
held = fold.turn("turn-1")
|
|
522
|
+
assert held is not None
|
|
523
|
+
assert held.state == "running"
|
|
524
|
+
assert held.model_retry_scheduled is not None
|
|
525
|
+
assert held.model_retry_scheduled["nextAttempt"] == 3
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def test_turn_completed_clears_the_provider_backoff_hint() -> None:
|
|
529
|
+
fold = SessionFold()
|
|
530
|
+
fold.apply(turn_started("turn-1", "cmd-1", "v:s:1"))
|
|
531
|
+
fold.apply(model_retry_scheduled("turn-1", "v:s:2"))
|
|
532
|
+
held = fold.turn("turn-1")
|
|
533
|
+
assert held is not None and held.model_retry_scheduled is not None, (
|
|
534
|
+
"the hint is set while the turn runs"
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
fold.apply(turn_completed("turn-1", "completed", "v:s:3"))
|
|
538
|
+
|
|
539
|
+
held = fold.turn("turn-1")
|
|
540
|
+
assert held is not None and held.model_retry_scheduled is None
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def test_a_replayed_page_never_re_plants_the_backoff_hint_on_a_turn_that_left_running() -> None:
|
|
544
|
+
fold = SessionFold()
|
|
545
|
+
backoff = model_retry_scheduled("turn-1", "v:s:2")
|
|
546
|
+
page = [
|
|
547
|
+
turn_started("turn-1", "cmd-1", "v:s:1"),
|
|
548
|
+
backoff,
|
|
549
|
+
turn_completed("turn-1", "cancelled", "v:s:3"),
|
|
550
|
+
turn_retracted("turn-1", "cmd-1", "v:s:4"),
|
|
551
|
+
]
|
|
552
|
+
for event in page:
|
|
553
|
+
fold.apply(event)
|
|
554
|
+
held = fold.turn("turn-1")
|
|
555
|
+
assert held is not None and held.model_retry_scheduled is None, "in-order delivery is clean"
|
|
556
|
+
|
|
557
|
+
for event in page:
|
|
558
|
+
fold.apply(event)
|
|
559
|
+
|
|
560
|
+
held = fold.turn("turn-1")
|
|
561
|
+
assert held is not None
|
|
562
|
+
assert held.state == "retracted"
|
|
563
|
+
assert held.model_retry_scheduled is None, "the replayed hint is dropped"
|
|
564
|
+
assert fold.apply(backoff) == IgnoredStaleFrame("turn/modelRetryScheduled", "turn-1")
|
|
565
|
+
|
|
566
|
+
|
|
496
567
|
def test_turn_completed_clears_the_foreground_hint() -> None:
|
|
497
568
|
fold = SessionFold()
|
|
498
569
|
fold.apply(turn_started("turn-1", "cmd-1", "v:s:1"))
|
|
@@ -1081,6 +1152,7 @@ def test_every_generated_view_notification_is_routed_never_the_default() -> None
|
|
|
1081
1152
|
"turn/completed": turn_completed("turn-t", "completed", "v:t:6"),
|
|
1082
1153
|
"turn/retracted": turn_retracted("turn-t2", "cmd-t2", "v:t:7"),
|
|
1083
1154
|
"turn/retryScheduled": retry_scheduled("turn-t3", "v:t:8"),
|
|
1155
|
+
"turn/modelRetryScheduled": model_retry_scheduled("turn-t3", "v:t:8b"),
|
|
1084
1156
|
"turn/foregroundCompleted": foreground_completed("turn-t5", "v:t:23"),
|
|
1085
1157
|
"turn/unqueued": turn_unqueued("turn-t4", "cmd-t4", "v:t:9"),
|
|
1086
1158
|
"approval/requested": approval_requested("a-t", "v:t:10"),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|