muse-code-sdk 1.3.1__tar.gz → 1.4.2__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.
Files changed (61) hide show
  1. {muse_code_sdk-1.3.1/src/muse_code_sdk.egg-info → muse_code_sdk-1.4.2}/PKG-INFO +1 -1
  2. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/pyproject.toml +1 -1
  3. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/__init__.py +2 -2
  4. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/facade/client.py +7 -1
  5. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/facade/turn_submit.py +7 -1
  6. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2/src/muse_code_sdk.egg-info}/PKG-INFO +1 -1
  7. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_publish_shape.py +14 -8
  8. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_session_fold.py +41 -7
  9. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/LICENSE +0 -0
  10. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/README.md +0 -0
  11. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/setup.cfg +0 -0
  12. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/connection/__init__.py +0 -0
  13. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/connection/connection.py +0 -0
  14. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/connection/discovery.py +0 -0
  15. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/connection/spawn.py +0 -0
  16. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/connection/validation.py +0 -0
  17. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/errors.py +0 -0
  18. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/facade/__init__.py +0 -0
  19. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/facade/approval.py +0 -0
  20. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/facade/discarded.py +0 -0
  21. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/facade/gap_fill.py +0 -0
  22. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/facade/host_death.py +0 -0
  23. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/facade/session.py +0 -0
  24. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/facade/turn_handle.py +0 -0
  25. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/fold/__init__.py +0 -0
  26. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/fold/item_store.py +0 -0
  27. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/fold/session_fold.py +0 -0
  28. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/fold/state_store.py +0 -0
  29. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/pending/__init__.py +0 -0
  30. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/pending/pending_command_set.py +0 -0
  31. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/py.typed +0 -0
  32. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/replay.py +0 -0
  33. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code/sync_facade.py +0 -0
  34. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code_sdk.egg-info/SOURCES.txt +0 -0
  35. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code_sdk.egg-info/dependency_links.txt +0 -0
  36. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code_sdk.egg-info/requires.txt +0 -0
  37. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/src/muse_code_sdk.egg-info/top_level.txt +0 -0
  38. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_asyncio_cancellation.py +0 -0
  39. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_compatibility_row.py +0 -0
  40. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_connection.py +0 -0
  41. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_connection_error_arms.py +0 -0
  42. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_docs_generation.py +0 -0
  43. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_external_audience.py +0 -0
  44. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_facade_approval.py +0 -0
  45. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_facade_gap_fill.py +0 -0
  46. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_facade_persistent_approval.py +0 -0
  47. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_facade_turn.py +0 -0
  48. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_host_discovery.py +0 -0
  49. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_item_store.py +0 -0
  50. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_manifests.py +0 -0
  51. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_muse_serve_child.py +0 -0
  52. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_pending_command_corpus.py +0 -0
  53. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_pending_command_set.py +0 -0
  54. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_pending_command_wire_binding.py +0 -0
  55. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_source_closure.py +0 -0
  56. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_spawn_handshake.py +0 -0
  57. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_state_store.py +0 -0
  58. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_sync_facade.py +0 -0
  59. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_tolerance_corpus.py +0 -0
  60. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/tests/test_transcript_replay.py +0 -0
  61. {muse_code_sdk-1.3.1 → muse_code_sdk-1.4.2}/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.3.1
3
+ Version: 1.4.2
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.3.1"
8
+ version = "1.4.2"
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"
@@ -77,7 +77,7 @@ __all__ = [
77
77
  ]
78
78
 
79
79
  EXPECTED_SCHEMA_FINGERPRINT: Final[str] = (
80
- "sha256:b1e6676d624e116e2c1b150fec3192200d2cbca8ed79898e44f8921759c7872f"
80
+ "sha256:61afea3112e0906e9dc3a536144278a74cb4b36fc6e20901a91d4432ba3568e2"
81
81
  )
82
82
  """The stable schema-bundle fingerprint this SDK was built against.
83
83
 
@@ -89,7 +89,7 @@ Python SDK reds this lane instead of drifting silently.
89
89
  # The lockstep rule: the release train bumps pyproject.toml, the
90
90
  # distribution-metadata authority; this constant is aligned at publication
91
91
  # time.
92
- __version__: Final[str] = "1.3.1"
92
+ __version__: Final[str] = "1.4.2"
93
93
 
94
94
  # The facade is imported last so the constants above are already bound: the
95
95
  # spawn path reads ``EXPECTED_SCHEMA_FINGERPRINT`` (lazily, at initialize
@@ -23,7 +23,7 @@ from __future__ import annotations
23
23
 
24
24
  import asyncio
25
25
  from dataclasses import dataclass, fields
26
- from typing import Any, Awaitable, Generic, Mapping, Sequence, Set, TypeVar
26
+ from typing import Any, Awaitable, Generic, List, Mapping, Sequence, Set, TypeVar
27
27
 
28
28
  from muse_code_msp import (
29
29
  ClientCapabilities,
@@ -77,6 +77,9 @@ class StartSessionOptions:
77
77
  session_id: A caller-chosen session id; omitted = the host mints one.
78
78
  workspace_root: The workspace the session works in; omitted = the
79
79
  host decides.
80
+ workspace_roots: The session's initial runtime workspace-root set
81
+ (wire ``workspaceRoots``; first root = the CWD). When
82
+ ``workspace_root`` is also given the two must agree.
80
83
  model_id: The model to serve the session with; omitted = the
81
84
  provider's default.
82
85
  """
@@ -85,6 +88,7 @@ class StartSessionOptions:
85
88
  provider_id: str | None = None
86
89
  session_id: str | None = None
87
90
  workspace_root: str | None = None
91
+ workspace_roots: List[str] | None = None
88
92
  model_id: str | None = None
89
93
 
90
94
 
@@ -322,6 +326,8 @@ class MuseClient(Generic[_I]):
322
326
  params["sessionId"] = options.session_id
323
327
  if options.workspace_root is not None:
324
328
  params["workspaceRoot"] = options.workspace_root
329
+ if options.workspace_roots is not None:
330
+ params["workspaceRoots"] = options.workspace_roots
325
331
  if options.model_id is not None:
326
332
  params["modelId"] = options.model_id
327
333
  raw = await self._connection.command("session/start", params)
@@ -51,6 +51,9 @@ class SendUserTurnOptions(Generic[_I]):
51
51
  if_busy: How to treat a submit while a turn runs (wire ``ifBusy``).
52
52
  reasoning_effort: The turn's reasoning effort (wire
53
53
  ``reasoningEffort``).
54
+ workspace_roots: A sticky replacement of the session's runtime
55
+ workspace-root set for this turn and subsequent turns (wire
56
+ ``workspaceRoots``).
54
57
  composer_input: What to hand back to the composer if this submit is
55
58
  ever retired — the protocol restores the INPUT, not the wire parts.
56
59
  Optional: a consumer rendering nothing optimistically has nothing
@@ -61,6 +64,7 @@ class SendUserTurnOptions(Generic[_I]):
61
64
  display_text: str | None = None
62
65
  if_busy: IfBusy | None = None
63
66
  reasoning_effort: ReasoningEffort | None = None
67
+ workspace_roots: List[str] | None = None
64
68
  composer_input: _I | None = None
65
69
 
66
70
 
@@ -82,7 +86,7 @@ def _camel(field_name: str) -> str:
82
86
  # own field, never a wire member, so it is dropped
83
87
  # before the compare; `commandId`/`sessionId` are excluded because the
84
88
  # connection mints the first and the session owns the second.
85
- _TURN_WIRE_FIELDS = {"input", "display_text", "if_busy", "reasoning_effort"}
89
+ _TURN_WIRE_FIELDS = {"input", "display_text", "if_busy", "reasoning_effort", "workspace_roots"}
86
90
  _TURN_EXCLUDED = {"commandId", "sessionId"}
87
91
  assert {
88
92
  _camel(f.name) for f in fields(SendUserTurnOptions) if f.name in _TURN_WIRE_FIELDS
@@ -283,4 +287,6 @@ class TurnSubmitter(Generic[_I]):
283
287
  params["ifBusy"] = options.if_busy
284
288
  if options.reasoning_effort is not None:
285
289
  params["reasoningEffort"] = options.reasoning_effort
290
+ if options.workspace_roots is not None:
291
+ params["workspaceRoots"] = options.workspace_roots
286
292
  return params
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: muse-code-sdk
3
- Version: 1.3.1
3
+ Version: 1.4.2
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
@@ -103,20 +103,26 @@ def test_the_version_source_is_the_static_manifest_field() -> None:
103
103
  # eligibility. Publication is permitted at exactly this lockstep
104
104
  # version — never a train-external one — so
105
105
  # the equality here is what the published wheel's version claims.
106
- # One-time lockstep exception (owner-directed 2026-09-21): the
107
- # 1.3.0 PyPI files were deleted after publication and PyPI
108
- # reserves deleted filenames forever, so the republish rides 1.3.1
109
- # while the train stays at 1.3.0. Exactly this pair is allowed;
110
- # anything else still requires lockstep equality.
111
- assert (version == _host_version()) or (
112
- (version, _host_version()) == ("1.3.1", "1.3.0")
113
- ), (
106
+ assert version == _host_version(), (
114
107
  f"{distribution}: version {version!r} != the host manifest's "
115
108
  f"{_host_version()!r} (the version lockstep rule: only the "
116
109
  "release train moves these versions, in one commit)"
117
110
  )
118
111
 
119
112
 
113
+ def test_the_runtime_version_matches_the_sdk_manifest() -> None:
114
+ import muse_code
115
+
116
+ # `muse_code.__version__` is a hand-kept copy of the manifest field; a
117
+ # release bump that moves one without the other ships a wheel that
118
+ # misreports its own version.
119
+ manifest_version = _pyproject(SDK_DIR)["project"]["version"]
120
+ assert muse_code.__version__ == manifest_version, (
121
+ f"muse_code.__version__ {muse_code.__version__!r} != "
122
+ f"muse-code-sdk manifest version {manifest_version!r}"
123
+ )
124
+
125
+
120
126
  def test_the_license_is_mit_with_shipped_text() -> None:
121
127
  for package_dir, (distribution, _) in PACKAGES.items():
122
128
  project = _pyproject(package_dir)["project"]
@@ -806,6 +806,25 @@ def test_an_unrecognized_notification_method_folds_without_raising() -> None:
806
806
  assert fold.active_turn_id == "turn-1", "the rest of the fold is untouched"
807
807
 
808
808
 
809
+ def test_a_session_lifecycle_broadcast_keeps_its_pre_enrollment_drop() -> None:
810
+ # The runtime twin of the TypeScript SDK's lifecycle pin: the generated
811
+ # NOTIFICATIONS union carries both lifecycle names, but they are
812
+ # control-plane broadcasts, not view events, and the fold deliberately
813
+ # keeps the pre-enrollment runtime answer (the same sanctioned initial
814
+ # drop the delivery-gap marker had before it became a fold input). This
815
+ # pin is the tripwire the SDK's conscious lifecycle surfacing must
816
+ # replace; it reds if a refactor makes a lifecycle frame seed an item or
817
+ # a state family instead.
818
+ fold = SessionFold()
819
+ fold.apply(turn_started("turn-1", "cmd-1", "v:s:1"))
820
+ for method in ("session/started", "session/closed"):
821
+ outcome = fold.apply({"method": method, "params": {"sessionId": SESSION}})
822
+ assert outcome == IgnoredUnrecognizedMethod(method)
823
+ assert fold.items.size == 0, "a lifecycle broadcast seeds no item"
824
+ assert fold.session_state.families() == [], "and no state family"
825
+ assert fold.active_turn_id == "turn-1", "the rest of the fold is untouched"
826
+
827
+
809
828
  def test_a_real_view_gap_frame_moves_the_folds_currency_and_no_store() -> None:
810
829
  fold = SessionFold()
811
830
  fold.apply(turn_started("turn-1", "cmd-1", "v:s:0"))
@@ -925,19 +944,34 @@ def test_every_generated_view_notification_is_routed_never_the_default() -> None
925
944
  # push, not a view-fold input — it reports that the live view stream died,
926
945
  # so it carries no cursor and folds into no transcript state. Excluded here
927
946
  # like the handshake's `initialized`; the client handles it out of band.
928
- non_fold_notifications = {"initialized", "session/viewHealthChanged"}
929
- assert VIEW_EVENT_METHODS == set(NOTIFICATIONS) - non_fold_notifications - LIVE_HOST_STATE_PROJECTIONS, (
947
+ # Deletion completion has no view envelope or fold state.
948
+ non_fold_notifications = {"initialized", "session/viewHealthChanged", "session/deleteCompleted"}
949
+ # Session lifecycle broadcasts (a session was loaded or unloaded on the
950
+ # host) are control-plane notifications, not view events — they carry no
951
+ # view envelope, so they are facade inputs, not fold inputs. Today a
952
+ # lifecycle frame keeps the pre-enrollment runtime answer
953
+ # (``IgnoredUnrecognizedMethod``, pinned by the lifecycle runtime test)
954
+ # until the SDK consciously surfaces the pair.
955
+ session_lifecycle_broadcasts = {"session/started", "session/closed"}
956
+ assert VIEW_EVENT_METHODS == (
957
+ set(NOTIFICATIONS)
958
+ - non_fold_notifications
959
+ - session_lifecycle_broadcasts
960
+ - LIVE_HOST_STATE_PROJECTIONS
961
+ ), (
930
962
  "a protocol enrollment added or retired a view notification; route it in "
931
963
  "SessionFold.apply (exclusions: `initialized`, the command-plane "
932
- "session/viewHealthChanged push, and the live host-state projections, "
933
- "the governing decision)"
964
+ "session/viewHealthChanged push, the session lifecycle broadcasts, and "
965
+ "the live host-state projections, the governing decision)"
934
966
  )
935
967
  # The reverse pin (the TS StaleExclusion twin): every excluded projection
936
968
  # names a REAL generated notification, so a retired or renamed method
937
969
  # cannot rot in the exclusion set as dead vocabulary.
938
- assert (LIVE_HOST_STATE_PROJECTIONS | non_fold_notifications) <= set(NOTIFICATIONS), (
939
- "an excluded method (live host-state projection or non-fold "
940
- "notification) names no generated notification"
970
+ assert (
971
+ LIVE_HOST_STATE_PROJECTIONS | non_fold_notifications | session_lifecycle_broadcasts
972
+ ) <= set(NOTIFICATIONS), (
973
+ "an excluded method (live host-state projection, lifecycle broadcast, "
974
+ "or non-fold notification) names no generated notification"
941
975
  )
942
976
 
943
977
  by_method: dict[str, dict[str, Any]] = {
File without changes
File without changes
File without changes