muse-code-sdk 1.4.7__tar.gz → 1.4.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.
- {muse_code_sdk-1.4.7/src/muse_code_sdk.egg-info → muse_code_sdk-1.4.9}/PKG-INFO +1 -1
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/pyproject.toml +1 -1
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/__init__.py +2 -2
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/facade/client.py +7 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/facade/turn_handle.py +4 -3
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/fold/__init__.py +2 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/fold/session_fold.py +45 -1
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9/src/muse_code_sdk.egg-info}/PKG-INFO +1 -1
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_facade_turn.py +45 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_session_fold.py +95 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/LICENSE +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/README.md +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/setup.cfg +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/connection/__init__.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/connection/connection.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/connection/discovery.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/connection/spawn.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/connection/validation.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/errors.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/facade/__init__.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/facade/approval.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/facade/discarded.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/facade/gap_fill.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/facade/host_death.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/facade/host_start.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/facade/session.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/facade/turn_submit.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/fold/item_store.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/fold/state_store.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/pending/__init__.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/pending/pending_command_set.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/py.typed +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/replay.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code/sync_facade.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code_sdk.egg-info/SOURCES.txt +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code_sdk.egg-info/dependency_links.txt +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code_sdk.egg-info/requires.txt +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/src/muse_code_sdk.egg-info/top_level.txt +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_asyncio_cancellation.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_compatibility_row.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_connection.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_connection_error_arms.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_docs_generation.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_external_audience.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_facade_approval.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_facade_gap_fill.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_facade_open_same_chunk.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_facade_persistent_approval.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_facade_required_selection.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_host_discovery.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_item_store.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_manifests.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_muse_serve_child.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_pending_command_corpus.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_pending_command_set.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_pending_command_wire_binding.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_permission_state_wire_types.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_public_mirror_text.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_publish_shape.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_source_closure.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_spawn_handshake.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_spawn_host_lost.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_state_store.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_sync_facade.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_tolerance_corpus.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/tests/test_transcript_replay.py +0 -0
- {muse_code_sdk-1.4.7 → muse_code_sdk-1.4.9}/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.9
|
|
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.9"
|
|
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:5d3b43e5ccdb7dc4f5a32c380f5f2cde0f10e3059b93610afb2e73f73b1d914c"
|
|
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.9"
|
|
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:
|
|
@@ -21,6 +21,7 @@ from .session_fold import (
|
|
|
21
21
|
LIVE_HOST_STATE_PROJECTIONS,
|
|
22
22
|
VIEW_EVENT_METHODS,
|
|
23
23
|
ApprovalPending,
|
|
24
|
+
ApprovalReviewingNotice,
|
|
24
25
|
ApprovalResolvedFold,
|
|
25
26
|
DeliveryGap,
|
|
26
27
|
FoldItems,
|
|
@@ -48,6 +49,7 @@ __all__ = [
|
|
|
48
49
|
"VIEW_EVENT_METHODS",
|
|
49
50
|
"Appended",
|
|
50
51
|
"ApprovalPending",
|
|
52
|
+
"ApprovalReviewingNotice",
|
|
51
53
|
"ApprovalResolvedFold",
|
|
52
54
|
"BufferedForAbsentItem",
|
|
53
55
|
"DeliveryGap",
|
|
@@ -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,8 +72,12 @@ 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",
|
|
78
|
+
# The reviewer-held twin of `approval/requested`: routed to its own
|
|
79
|
+
# non-storing notice.
|
|
80
|
+
"approval/reviewing",
|
|
76
81
|
"approval/updated",
|
|
77
82
|
"approval/resolved",
|
|
78
83
|
"userInput/requested",
|
|
@@ -139,6 +144,9 @@ class TurnEntry:
|
|
|
139
144
|
``turn/completed`` folded.
|
|
140
145
|
error: Present iff the terminal was ``"failed"``.
|
|
141
146
|
retry_scheduled: The latest scheduled model retry; non-terminal.
|
|
147
|
+
model_retry_scheduled: The latest scheduled provider-backoff retry;
|
|
148
|
+
non-terminal. The model-attempt twin of ``retry_scheduled``;
|
|
149
|
+
clears on the turn's terminal.
|
|
142
150
|
foreground_completed: The latest foreground-completed notice;
|
|
143
151
|
non-terminal. Tells a renderer the answer is done while
|
|
144
152
|
``blockingAgents`` hold the turn; clears on the turn's terminal.
|
|
@@ -150,6 +158,7 @@ class TurnEntry:
|
|
|
150
158
|
terminal: str | None = None
|
|
151
159
|
error: Params | None = None
|
|
152
160
|
retry_scheduled: Params | None = None
|
|
161
|
+
model_retry_scheduled: Params | None = None
|
|
153
162
|
foreground_completed: Params | None = None
|
|
154
163
|
|
|
155
164
|
|
|
@@ -275,6 +284,19 @@ class ModelRouteUnservedNotice:
|
|
|
275
284
|
provider_id: str | None
|
|
276
285
|
|
|
277
286
|
|
|
287
|
+
@dataclass(frozen=True)
|
|
288
|
+
class ApprovalReviewingNotice:
|
|
289
|
+
"""The ``approval/reviewing`` typed-activity signal.
|
|
290
|
+
|
|
291
|
+
The host's built-in reviewer holds this approval. A notice, never a
|
|
292
|
+
pending entry: it seeds no store and opens no decision — the approval is
|
|
293
|
+
absent from ``pending_approvals`` until ``approval/requested`` releases it
|
|
294
|
+
to the human (or ``approval/resolved`` settles it) on the same id.
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
approval_id: str
|
|
298
|
+
|
|
299
|
+
|
|
278
300
|
@dataclass(frozen=True)
|
|
279
301
|
class IgnoredUnrecognizedMethod:
|
|
280
302
|
"""A newer host's method this SDK has never heard of."""
|
|
@@ -324,6 +346,7 @@ FoldOutcome = (
|
|
|
324
346
|
| UserInputSettledFold
|
|
325
347
|
| DeliveryGap
|
|
326
348
|
| ModelRouteUnservedNotice
|
|
349
|
+
| ApprovalReviewingNotice
|
|
327
350
|
| IgnoredUnrecognizedMethod
|
|
328
351
|
| IgnoredMissingParams
|
|
329
352
|
| IgnoredStaleFrame
|
|
@@ -422,6 +445,7 @@ class _InternalTurn:
|
|
|
422
445
|
terminal: str | None = None
|
|
423
446
|
error: Params | None = None
|
|
424
447
|
retry_scheduled: Params | None = None
|
|
448
|
+
model_retry_scheduled: Params | None = None
|
|
425
449
|
foreground_completed: Params | None = None
|
|
426
450
|
|
|
427
451
|
|
|
@@ -572,10 +596,14 @@ class SessionFold:
|
|
|
572
596
|
return self._turn_unqueued(params)
|
|
573
597
|
if method == "turn/retryScheduled":
|
|
574
598
|
return self._turn_retry_scheduled(params)
|
|
599
|
+
if method == "turn/modelRetryScheduled":
|
|
600
|
+
return self._turn_model_retry_scheduled(params)
|
|
575
601
|
if method == "turn/foregroundCompleted":
|
|
576
602
|
return self._turn_foreground_completed(params)
|
|
577
603
|
if method == "approval/requested":
|
|
578
604
|
return self._approval_requested(params)
|
|
605
|
+
if method == "approval/reviewing":
|
|
606
|
+
return ApprovalReviewingNotice(approval_id=str(params["approvalId"]))
|
|
579
607
|
if method == "approval/updated":
|
|
580
608
|
return self._approval_updated(params)
|
|
581
609
|
if method == "approval/resolved":
|
|
@@ -675,6 +703,9 @@ class SessionFold:
|
|
|
675
703
|
# The retry hint is a "retrying in Ns" countdown, and it clears on the
|
|
676
704
|
# turn's next event — which a completion is.
|
|
677
705
|
turn.retry_scheduled = None
|
|
706
|
+
# Same for the provider-backoff hint: a finished turn is not
|
|
707
|
+
# waiting out a backoff.
|
|
708
|
+
turn.model_retry_scheduled = None
|
|
678
709
|
# Same for the foreground hint: a finished turn is not
|
|
679
710
|
# background-waiting.
|
|
680
711
|
turn.foreground_completed = None
|
|
@@ -720,6 +751,18 @@ class SessionFold:
|
|
|
720
751
|
turn.retry_scheduled = params
|
|
721
752
|
return TurnFold(turn_id, turn.state)
|
|
722
753
|
|
|
754
|
+
def _turn_model_retry_scheduled(self, params: Params) -> FoldOutcome:
|
|
755
|
+
# Non-terminal by contract: it never resolves a turn-wait.
|
|
756
|
+
turn_id = str(params["turnId"])
|
|
757
|
+
turn = self._turn_for(turn_id)
|
|
758
|
+
# ...but it is still a turn frame, so it takes the same redelivery
|
|
759
|
+
# guard as the retry hint: a replayed page must not re-plant the
|
|
760
|
+
# provider-backoff hint on a turn that already left `running`.
|
|
761
|
+
if turn.state != "running":
|
|
762
|
+
return IgnoredStaleFrame("turn/modelRetryScheduled", turn_id)
|
|
763
|
+
turn.model_retry_scheduled = params
|
|
764
|
+
return TurnFold(turn_id, turn.state)
|
|
765
|
+
|
|
723
766
|
def _turn_foreground_completed(self, params: Params) -> FoldOutcome:
|
|
724
767
|
# Non-terminal by contract: it never resolves a turn-wait.
|
|
725
768
|
turn_id = str(params["turnId"])
|
|
@@ -753,6 +796,7 @@ class SessionFold:
|
|
|
753
796
|
terminal=turn.terminal,
|
|
754
797
|
error=turn.error,
|
|
755
798
|
retry_scheduled=turn.retry_scheduled,
|
|
799
|
+
model_retry_scheduled=turn.model_retry_scheduled,
|
|
756
800
|
foreground_completed=turn.foreground_completed,
|
|
757
801
|
)
|
|
758
802
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: muse-code-sdk
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.9
|
|
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()
|
|
@@ -15,6 +15,8 @@ from typing import Any
|
|
|
15
15
|
from muse_code.fold import (
|
|
16
16
|
LIVE_HOST_STATE_PROJECTIONS,
|
|
17
17
|
VIEW_EVENT_METHODS,
|
|
18
|
+
ApprovalPending,
|
|
19
|
+
ApprovalReviewingNotice,
|
|
18
20
|
ApprovalResolvedFold,
|
|
19
21
|
DeliveryGap,
|
|
20
22
|
FoldItems,
|
|
@@ -110,6 +112,25 @@ def retry_scheduled(turn_id: str, view_cursor: str) -> dict[str, Any]:
|
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
|
|
115
|
+
def model_retry_scheduled(turn_id: str, view_cursor: str) -> dict[str, Any]:
|
|
116
|
+
return {
|
|
117
|
+
"method": "turn/modelRetryScheduled",
|
|
118
|
+
"params": {
|
|
119
|
+
"attempt": 2,
|
|
120
|
+
"errorKind": "overloaded",
|
|
121
|
+
"httpStatus": 529,
|
|
122
|
+
"maxAttempts": 5,
|
|
123
|
+
"nextAttempt": 3,
|
|
124
|
+
"retryDelayMs": 4_000,
|
|
125
|
+
"sessionId": SESSION,
|
|
126
|
+
"sourceRange": SOURCE,
|
|
127
|
+
"system": "meta",
|
|
128
|
+
"turnId": turn_id,
|
|
129
|
+
"viewCursor": view_cursor,
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
113
134
|
def foreground_completed(turn_id: str, view_cursor: str) -> dict[str, Any]:
|
|
114
135
|
return {
|
|
115
136
|
"method": "turn/foregroundCompleted",
|
|
@@ -159,6 +180,13 @@ def approval_requested(
|
|
|
159
180
|
}
|
|
160
181
|
|
|
161
182
|
|
|
183
|
+
def approval_reviewing(approval_id: str, view_cursor: str) -> dict[str, Any]:
|
|
184
|
+
# The reviewer-held twin of `approval/requested`: same params shape,
|
|
185
|
+
# different method.
|
|
186
|
+
event = approval_requested(approval_id, view_cursor)
|
|
187
|
+
return {"method": "approval/reviewing", "params": event["params"]}
|
|
188
|
+
|
|
189
|
+
|
|
162
190
|
def approval_updated(
|
|
163
191
|
approval_id: str, view_cursor: str, source_index: int = 1, command: str | None = None
|
|
164
192
|
) -> dict[str, Any]:
|
|
@@ -493,6 +521,58 @@ def test_a_replayed_page_never_re_plants_the_retry_hint_on_a_turn_that_left_runn
|
|
|
493
521
|
assert fold.apply(retry) == IgnoredStaleFrame("turn/retryScheduled", "turn-1")
|
|
494
522
|
|
|
495
523
|
|
|
524
|
+
def test_turn_model_retry_scheduled_is_non_terminal_the_turn_keeps_running() -> None:
|
|
525
|
+
fold = SessionFold()
|
|
526
|
+
fold.apply(turn_started("turn-1", "cmd-1", "v:s:1"))
|
|
527
|
+
fold.apply(model_retry_scheduled("turn-1", "v:s:2"))
|
|
528
|
+
|
|
529
|
+
assert fold.active_turn_id == "turn-1", "a scheduled backoff never settles a turn"
|
|
530
|
+
held = fold.turn("turn-1")
|
|
531
|
+
assert held is not None
|
|
532
|
+
assert held.state == "running"
|
|
533
|
+
assert held.model_retry_scheduled is not None
|
|
534
|
+
assert held.model_retry_scheduled["nextAttempt"] == 3
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def test_turn_completed_clears_the_provider_backoff_hint() -> None:
|
|
538
|
+
fold = SessionFold()
|
|
539
|
+
fold.apply(turn_started("turn-1", "cmd-1", "v:s:1"))
|
|
540
|
+
fold.apply(model_retry_scheduled("turn-1", "v:s:2"))
|
|
541
|
+
held = fold.turn("turn-1")
|
|
542
|
+
assert held is not None and held.model_retry_scheduled is not None, (
|
|
543
|
+
"the hint is set while the turn runs"
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
fold.apply(turn_completed("turn-1", "completed", "v:s:3"))
|
|
547
|
+
|
|
548
|
+
held = fold.turn("turn-1")
|
|
549
|
+
assert held is not None and held.model_retry_scheduled is None
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def test_a_replayed_page_never_re_plants_the_backoff_hint_on_a_turn_that_left_running() -> None:
|
|
553
|
+
fold = SessionFold()
|
|
554
|
+
backoff = model_retry_scheduled("turn-1", "v:s:2")
|
|
555
|
+
page = [
|
|
556
|
+
turn_started("turn-1", "cmd-1", "v:s:1"),
|
|
557
|
+
backoff,
|
|
558
|
+
turn_completed("turn-1", "cancelled", "v:s:3"),
|
|
559
|
+
turn_retracted("turn-1", "cmd-1", "v:s:4"),
|
|
560
|
+
]
|
|
561
|
+
for event in page:
|
|
562
|
+
fold.apply(event)
|
|
563
|
+
held = fold.turn("turn-1")
|
|
564
|
+
assert held is not None and held.model_retry_scheduled is None, "in-order delivery is clean"
|
|
565
|
+
|
|
566
|
+
for event in page:
|
|
567
|
+
fold.apply(event)
|
|
568
|
+
|
|
569
|
+
held = fold.turn("turn-1")
|
|
570
|
+
assert held is not None
|
|
571
|
+
assert held.state == "retracted"
|
|
572
|
+
assert held.model_retry_scheduled is None, "the replayed hint is dropped"
|
|
573
|
+
assert fold.apply(backoff) == IgnoredStaleFrame("turn/modelRetryScheduled", "turn-1")
|
|
574
|
+
|
|
575
|
+
|
|
496
576
|
def test_turn_completed_clears_the_foreground_hint() -> None:
|
|
497
577
|
fold = SessionFold()
|
|
498
578
|
fold.apply(turn_started("turn-1", "cmd-1", "v:s:1"))
|
|
@@ -1001,6 +1081,19 @@ def test_the_items_surface_is_an_allowlist_pinned_by_member_set() -> None:
|
|
|
1001
1081
|
assert state_public == {"families", "get", "has"}
|
|
1002
1082
|
|
|
1003
1083
|
|
|
1084
|
+
def test_approval_reviewing_is_a_notice_that_opens_no_pending_approval() -> None:
|
|
1085
|
+
# The host's built-in reviewer holds the approval; opening a pending
|
|
1086
|
+
# entry here would defeat the presentation hold. The release arrives as
|
|
1087
|
+
# `approval/requested` on the same id.
|
|
1088
|
+
fold = SessionFold()
|
|
1089
|
+
outcome = fold.apply(approval_reviewing("a-1", "v:s:1"))
|
|
1090
|
+
assert outcome == ApprovalReviewingNotice(approval_id="a-1")
|
|
1091
|
+
assert fold.pending_approvals() == []
|
|
1092
|
+
released = fold.apply(approval_requested("a-1", "v:s:2"))
|
|
1093
|
+
assert isinstance(released, ApprovalPending)
|
|
1094
|
+
assert len(fold.pending_approvals()) == 1
|
|
1095
|
+
|
|
1096
|
+
|
|
1004
1097
|
def test_every_generated_view_notification_is_routed_never_the_default() -> None:
|
|
1005
1098
|
# The runtime twin of the TS AssertNever coverage pins, PLUS the routing
|
|
1006
1099
|
# table: every generated notification except the handshake's
|
|
@@ -1081,9 +1174,11 @@ def test_every_generated_view_notification_is_routed_never_the_default() -> None
|
|
|
1081
1174
|
"turn/completed": turn_completed("turn-t", "completed", "v:t:6"),
|
|
1082
1175
|
"turn/retracted": turn_retracted("turn-t2", "cmd-t2", "v:t:7"),
|
|
1083
1176
|
"turn/retryScheduled": retry_scheduled("turn-t3", "v:t:8"),
|
|
1177
|
+
"turn/modelRetryScheduled": model_retry_scheduled("turn-t3", "v:t:8b"),
|
|
1084
1178
|
"turn/foregroundCompleted": foreground_completed("turn-t5", "v:t:23"),
|
|
1085
1179
|
"turn/unqueued": turn_unqueued("turn-t4", "cmd-t4", "v:t:9"),
|
|
1086
1180
|
"approval/requested": approval_requested("a-t", "v:t:10"),
|
|
1181
|
+
"approval/reviewing": approval_reviewing("a-r", "v:t:23"),
|
|
1087
1182
|
"approval/updated": approval_updated("a-t", "v:t:10b"),
|
|
1088
1183
|
"approval/resolved": approval_resolved("a-t", "approved", "v:t:11"),
|
|
1089
1184
|
"userInput/requested": user_input_requested("u-t", "v:t:12"),
|
|
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
|