@softspark/ai-toolkit 4.32.4 → 4.33.0

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.
@@ -14,8 +14,8 @@ import json
14
14
  import os
15
15
  import re
16
16
  import secrets
17
- import signal
18
17
  import shutil
18
+ import signal
19
19
  import stat
20
20
  import subprocess
21
21
  import sys
@@ -44,16 +44,26 @@ from install_steps.install_state import (
44
44
  secure_dsh_state_mutation_supported,
45
45
  )
46
46
 
47
- SUPPORTED_DSH_VERSION = "0.1.1-rc.2"
47
+ SUPPORTED_DSH_VERSION = "0.1.2-rc.1"
48
48
  MINIMUM_PNPM_VERSION = (11, 7, 0)
49
49
  MAXIMUM_PNPM_VERSION_EXCLUSIVE = (12, 0, 0)
50
50
  SUPPORTED_PNPM_RANGE = ">=11.7.0,<12.0.0"
51
51
  DEFAULT_PROFILE = "web"
52
52
  PRESET_NAME = "softspark-orchestrator"
53
53
  PACKAGES = {
54
- "@softspark/dsh-codex": "1.0.0",
55
- "@softspark/dsh-orchestrator": "1.0.1",
54
+ "@softspark/dsh-codex": "1.5.0",
55
+ "@softspark/dsh-orchestrator": "2.0.0",
56
56
  }
57
+ CLAUDE_SDK_PACKAGE = "@anthropic-ai/claude-agent-sdk"
58
+ CLAUDE_SDK_VERSION = "0.3.263"
59
+ CLAUDE_PROVIDER = "@deepseek-ai/dsh-subagent-claude-code"
60
+ CLAUDE_SDK_OVERRIDE = f"{CLAUDE_PROVIDER}@0.1.2-rc.1>{CLAUDE_SDK_PACKAGE}"
61
+ PNPM_WORKSPACE_FILENAME = "pnpm-workspace.yaml"
62
+ DEFAULT_PROFILE_PATCH = (
63
+ b"# Your patch layer for this dsh profile, applied after every bundle layer:\n"
64
+ b"# a top-level YAML array of loader patch entries (id-targeted config\n"
65
+ b"# overrides, disables, and insert lists; `!!js` expressions allowed).\n[]\n"
66
+ )
57
67
  MANAGED_PACKAGE_NAMES = tuple(PACKAGES)
58
68
  PROBE_TIMEOUT_SECONDS = 5
59
69
  PACKAGE_MUTATION_TIMEOUT_SECONDS = 300
@@ -378,9 +388,7 @@ def _acquire_dsh_home_directory_lock(home: _PinnedDshHome) -> None:
378
388
  try:
379
389
  fcntl.flock(home.root_descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB)
380
390
  except BlockingIOError as error:
381
- raise DshLifecycleError(
382
- "DSH home directory lifecycle lock is busy"
383
- ) from error
391
+ raise DshLifecycleError("DSH home directory lifecycle lock is busy") from error
384
392
  except OSError as error:
385
393
  raise DshLifecycleError(
386
394
  "unable to acquire DSH home directory lifecycle lock"
@@ -414,10 +422,14 @@ def _cleanup_failed_lifecycle_lock(
414
422
  dir_fd=home.root_descriptor,
415
423
  follow_symlinks=False,
416
424
  )
417
- if not stat.S_ISREG(named.st_mode) or (
418
- named.st_dev,
419
- named.st_ino,
420
- ) != identity:
425
+ if (
426
+ not stat.S_ISREG(named.st_mode)
427
+ or (
428
+ named.st_dev,
429
+ named.st_ino,
430
+ )
431
+ != identity
432
+ ):
421
433
  return path
422
434
  _secure_rename_noreplace_at(
423
435
  home.root_descriptor,
@@ -429,10 +441,14 @@ def _cleanup_failed_lifecycle_lock(
429
441
  dir_fd=home.root_descriptor,
430
442
  follow_symlinks=False,
431
443
  )
432
- if not stat.S_ISREG(moved.st_mode) or (
433
- moved.st_dev,
434
- moved.st_ino,
435
- ) != identity:
444
+ if (
445
+ not stat.S_ISREG(moved.st_mode)
446
+ or (
447
+ moved.st_dev,
448
+ moved.st_ino,
449
+ )
450
+ != identity
451
+ ):
436
452
  return recovery
437
453
  os.unlink(recovery.name, dir_fd=home.root_descriptor)
438
454
  return None
@@ -459,11 +475,7 @@ def _lifecycle_lock_recovery_artifacts(dsh_home: Path) -> tuple[Path, ...]:
459
475
  pass
460
476
  return tuple(
461
477
  sorted(
462
- (
463
- path
464
- for path in candidates
465
- if path.exists() or path.is_symlink()
466
- ),
478
+ (path for path in candidates if path.exists() or path.is_symlink()),
467
479
  key=str,
468
480
  )
469
481
  )
@@ -522,10 +534,14 @@ def _read_lifecycle_lock_fields_at(
522
534
  descriptor = os.open(path.name, flags, dir_fd=home.root_descriptor)
523
535
  try:
524
536
  metadata = os.fstat(descriptor)
525
- if not stat.S_ISREG(metadata.st_mode) or (
526
- metadata.st_dev,
527
- metadata.st_ino,
528
- ) != identity:
537
+ if (
538
+ not stat.S_ISREG(metadata.st_mode)
539
+ or (
540
+ metadata.st_dev,
541
+ metadata.st_ino,
542
+ )
543
+ != identity
544
+ ):
529
545
  raise DshLifecycleError(
530
546
  f"DSH lifecycle lock identity changed while reading: {path}"
531
547
  )
@@ -1025,12 +1041,21 @@ class _ProfileTransaction:
1025
1041
  latest_manifest: _OwnedEntry | None = None
1026
1042
  latest_package_identity: _PackageMutationIdentity | None = None
1027
1043
  rollback_blocked: bool = False
1044
+ workspace: _PathPrestate | None = None
1045
+ latest_workspace: _OwnedEntry | None = None
1046
+ profile_patch: _PathPrestate | None = None
1047
+ latest_profile_patch: _OwnedEntry | None = None
1048
+ pnpm_settings: dict[str, object] | None = None
1049
+ initialized_manifest: _PathPrestate | None = None
1028
1050
 
1029
1051
 
1030
1052
  @dataclass(frozen=True)
1031
1053
  class _PackageMutationIdentity:
1032
1054
  manifest: _PathPrestate | None
1033
1055
  package_trees: dict[Path, dict[Path, _PathPrestate] | None]
1056
+ workspace: _PathPrestate | None = None
1057
+ workspace_entry: _OwnedEntry | None = None
1058
+ manifest_entry: _OwnedEntry | None = None
1034
1059
 
1035
1060
 
1036
1061
  @dataclass(frozen=True)
@@ -1101,14 +1126,32 @@ def _capture_package_mutation_identity(
1101
1126
  )
1102
1127
  for package in PACKAGES
1103
1128
  }
1104
- return _PackageMutationIdentity(manifest, package_trees)
1129
+ workspace_path = profile_root / PNPM_WORKSPACE_FILENAME
1130
+ workspace = _optional_profile_file(workspace_path)
1131
+ workspace_entry = _capture_entry(workspace_path) if workspace is not None else None
1132
+ return _PackageMutationIdentity(
1133
+ manifest,
1134
+ package_trees,
1135
+ workspace,
1136
+ workspace_entry,
1137
+ _capture_entry(manifest_path) if manifest is not None else None,
1138
+ )
1139
+
1140
+
1141
+ def _optional_profile_file(path: Path) -> _PathPrestate | None:
1142
+ if not path.exists() and not path.is_symlink():
1143
+ return None
1144
+ snapshot = _path_prestate(path)
1145
+ if snapshot.kind != "file":
1146
+ raise DshLifecycleError(f"unsafe DSH profile configuration: {path}")
1147
+ return snapshot
1105
1148
 
1106
1149
 
1107
1150
  def _capture_profile_transaction(dsh_home: Path, profile: str) -> _ProfileTransaction:
1108
1151
  profile_root = dsh_home / "profiles" / profile
1109
1152
  node_modules = profile_root / "node_modules"
1110
1153
  namespace = node_modules / "@softspark"
1111
- base_paths = (profile_root, node_modules, namespace)
1154
+ base_paths = (profile_root.parent, profile_root, node_modules, namespace)
1112
1155
  base_existed = {
1113
1156
  path: path.exists() and path.is_dir() and not path.is_symlink()
1114
1157
  for path in base_paths
@@ -1133,6 +1176,13 @@ def _capture_profile_transaction(dsh_home: Path, profile: str) -> _ProfileTransa
1133
1176
  manifest,
1134
1177
  package_trees,
1135
1178
  {},
1179
+ workspace=_optional_profile_file(profile_root / PNPM_WORKSPACE_FILENAME),
1180
+ profile_patch=(
1181
+ _path_prestate(profile_root / "cordis.patch.yml")
1182
+ if (profile_root / "cordis.patch.yml").exists()
1183
+ or (profile_root / "cordis.patch.yml").is_symlink()
1184
+ else None
1185
+ ),
1136
1186
  )
1137
1187
  transaction.latest_package_identity = _capture_package_mutation_identity(
1138
1188
  profile_root
@@ -1158,6 +1208,24 @@ def _observe_profile_transaction(transaction: _ProfileTransaction) -> None:
1158
1208
  transaction.latest_manifest = None
1159
1209
  else:
1160
1210
  transaction.latest_manifest = None
1211
+ workspace_path = transaction.profile_root / PNPM_WORKSPACE_FILENAME
1212
+ transaction.latest_workspace = (
1213
+ _capture_entry(workspace_path)
1214
+ if workspace_path.exists() or workspace_path.is_symlink()
1215
+ else None
1216
+ )
1217
+ patch_path = transaction.profile_root / "cordis.patch.yml"
1218
+ if (
1219
+ transaction.profile_patch is None
1220
+ and patch_path.exists()
1221
+ and transaction.latest_profile_patch is None
1222
+ ):
1223
+ if _read_regular_bytes(patch_path) not in {DEFAULT_PROFILE_PATCH, b"[]\n"}:
1224
+ transaction.rollback_blocked = True
1225
+ raise DshLifecycleError(
1226
+ "unexpected profile patch appeared during initialization; preserved"
1227
+ )
1228
+ transaction.latest_profile_patch = _capture_entry(patch_path)
1161
1229
 
1162
1230
 
1163
1231
  def _run_profile_command(
@@ -1232,6 +1300,8 @@ def _run_profile_command(
1232
1300
  "package recovery target drifted during successful external mutation"
1233
1301
  )
1234
1302
  transaction.latest_package_identity = after
1303
+ if transaction.pnpm_settings is not None and argv[4] in {"add", "remove"}:
1304
+ _check_pnpm_settings_after_mutation(argv[0], dsh_home, argv[3], transaction)
1235
1305
  return result
1236
1306
 
1237
1307
 
@@ -1409,7 +1479,11 @@ def _created_file_matches(
1409
1479
  )
1410
1480
 
1411
1481
 
1412
- def _create_snapshot_file(snapshot: _PathPrestate, parent: int) -> bool:
1482
+ def _create_snapshot_file(
1483
+ snapshot: _PathPrestate,
1484
+ parent: int,
1485
+ created_entries: list[_OwnedEntry] | None = None,
1486
+ ) -> bool:
1413
1487
  _assert_active_dsh_home_binding()
1414
1488
  flags = os.O_RDWR | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW
1415
1489
  descriptor = os.open(snapshot.path.name, flags, snapshot.mode, dir_fd=parent)
@@ -1428,6 +1502,19 @@ def _create_snapshot_file(snapshot: _PathPrestate, parent: int) -> bool:
1428
1502
  created=created,
1429
1503
  )
1430
1504
  _assert_active_dsh_home_binding()
1505
+ if created_matches and created_entries is not None:
1506
+ parent_metadata = os.fstat(parent)
1507
+ created_entries.append(
1508
+ _OwnedEntry(
1509
+ snapshot.path,
1510
+ created.st_dev,
1511
+ created.st_ino,
1512
+ "file",
1513
+ hashlib.sha256(snapshot.content or b"").hexdigest(),
1514
+ parent_device=parent_metadata.st_dev,
1515
+ parent_inode=parent_metadata.st_ino,
1516
+ )
1517
+ )
1431
1518
  return created_matches
1432
1519
  finally:
1433
1520
  os.close(descriptor)
@@ -1697,16 +1784,30 @@ def _manifest_has_only_empty_managed_dependencies(path: Path) -> bool:
1697
1784
 
1698
1785
  def _restore_manifest_prestate(transaction: _ProfileTransaction) -> list[Path]:
1699
1786
  path = transaction.profile_root / "package.json"
1700
- expected = transaction.manifest
1701
- current_identity = transaction.latest_manifest
1787
+ removable = _manifest_has_only_empty_managed_dependencies(path)
1788
+ if not removable and transaction.initialized_manifest is not None:
1789
+ try:
1790
+ removable = json.loads(_read_regular_bytes(path)) == json.loads(
1791
+ transaction.initialized_manifest.content or b"{}"
1792
+ )
1793
+ except (DshLifecycleError, ValueError):
1794
+ removable = False
1795
+ return _restore_profile_file_prestate(
1796
+ path, transaction.manifest, transaction.latest_manifest, removable=removable
1797
+ )
1798
+
1799
+
1800
+ def _restore_profile_file_prestate(
1801
+ path: Path,
1802
+ expected: _PathPrestate | None,
1803
+ current_identity: _OwnedEntry | None,
1804
+ *,
1805
+ removable: bool = False,
1806
+ ) -> list[Path]:
1702
1807
  if expected is None:
1703
1808
  if not path.exists() and not path.is_symlink():
1704
1809
  return []
1705
- if (
1706
- current_identity is None
1707
- or current_identity.kind != "file"
1708
- or not _manifest_has_only_empty_managed_dependencies(path)
1709
- ):
1810
+ if current_identity is None or current_identity.kind != "file" or not removable:
1710
1811
  return [path]
1711
1812
  try:
1712
1813
  _assert_entry_unchanged(current_identity, "profile package manifest")
@@ -1827,6 +1928,26 @@ def _restore_profile_prestate(transaction: _ProfileTransaction) -> list[Path]:
1827
1928
  for root, snapshots in transaction.package_trees.items():
1828
1929
  residuals.extend(_restore_tree_prestate(root, snapshots))
1829
1930
  residuals.extend(_restore_manifest_prestate(transaction))
1931
+ residuals.extend(
1932
+ _restore_profile_file_prestate(
1933
+ transaction.profile_root / PNPM_WORKSPACE_FILENAME,
1934
+ transaction.workspace,
1935
+ transaction.latest_workspace,
1936
+ removable=True,
1937
+ )
1938
+ )
1939
+ if (
1940
+ transaction.profile_patch is None
1941
+ and transaction.latest_profile_patch is not None
1942
+ ):
1943
+ residuals.extend(
1944
+ _restore_profile_file_prestate(
1945
+ transaction.profile_root / "cordis.patch.yml",
1946
+ None,
1947
+ transaction.latest_profile_patch,
1948
+ removable=True,
1949
+ )
1950
+ )
1830
1951
  for path in reversed(tuple(transaction.base_existed)):
1831
1952
  if transaction.base_existed[path]:
1832
1953
  continue
@@ -2442,7 +2563,9 @@ def _copy_tree_exclusive(
2442
2563
  raise DshLifecycleError(
2443
2564
  f"managed preset contains an unsupported entry: {relative}"
2444
2565
  )
2445
- source_content, source_metadata = _stable_regular_file_bytes(source_path)
2566
+ source_content, source_metadata = _stable_regular_file_bytes(
2567
+ source_path
2568
+ )
2446
2569
  expected_digest = hashlib.sha256(source_content).hexdigest()
2447
2570
  flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW
2448
2571
  try:
@@ -2710,7 +2833,7 @@ def _remove_exact_managed_tree(
2710
2833
 
2711
2834
 
2712
2835
  def _validate_profile(profile: str) -> str:
2713
- if profile in {".", ".."} or not PROFILE_PATTERN.fullmatch(profile):
2836
+ if profile in {".", "..", "node_modules"} or not PROFILE_PATTERN.fullmatch(profile):
2714
2837
  raise DshLifecycleError(
2715
2838
  "invalid DSH profile id; use 1-64 lowercase letters, digits, '.', '_' or '-'"
2716
2839
  )
@@ -2786,11 +2909,12 @@ def _mutation_environment(
2786
2909
  ) -> dict[str, str]:
2787
2910
  environment = _minimal_environment(dsh_home)
2788
2911
  verified_directory = str(prerequisites.pnpm.command_path.parent)
2789
- environment["PATH"] = (
2790
- verified_directory + os.pathsep + prerequisites.execution_path
2791
- )
2912
+ environment["PATH"] = verified_directory + os.pathsep + prerequisites.execution_path
2792
2913
  selected = shutil.which("pnpm", path=environment["PATH"])
2793
- if selected is None or _absolute_command_path(selected) != prerequisites.pnpm.command_path:
2914
+ if (
2915
+ selected is None
2916
+ or _absolute_command_path(selected) != prerequisites.pnpm.command_path
2917
+ ):
2794
2918
  raise DshLifecycleError("pnpm prerequisite PATH binding changed")
2795
2919
  return environment
2796
2920
 
@@ -2991,9 +3115,7 @@ def _run(argv: list[str], *, dsh_home: Path) -> subprocess.CompletedProcess[str]
2991
3115
  timeout=PACKAGE_MUTATION_TIMEOUT_SECONDS
2992
3116
  )
2993
3117
  if _process_group_exists(process.pid):
2994
- raise DshLifecycleError(
2995
- "DSH command left a descendant process running"
2996
- )
3118
+ raise DshLifecycleError("DSH command left a descendant process running")
2997
3119
  _assert_active_dsh_home_binding()
2998
3120
  _active_prerequisites()
2999
3121
  if process.returncode in {-2, 130}:
@@ -3041,7 +3163,9 @@ def _capture_executable_prerequisite(
3041
3163
  try:
3042
3164
  command_metadata = command_path.lstat()
3043
3165
  command_link_target = (
3044
- os.readlink(command_path) if stat.S_ISLNK(command_metadata.st_mode) else None
3166
+ os.readlink(command_path)
3167
+ if stat.S_ISLNK(command_metadata.st_mode)
3168
+ else None
3045
3169
  )
3046
3170
  resolved_path = command_path.resolve(strict=True)
3047
3171
  resolved_metadata = resolved_path.stat(follow_symlinks=False)
@@ -3308,9 +3432,9 @@ def _tree_inventory(root: Path) -> dict[str, object]:
3308
3432
  try:
3309
3433
  children = sorted(
3310
3434
  directory.iterdir(),
3311
- key=lambda item: item.relative_to(root)
3312
- .as_posix()
3313
- .encode("utf-8", errors="strict"),
3435
+ key=lambda item: (
3436
+ item.relative_to(root).as_posix().encode("utf-8", errors="strict")
3437
+ ),
3314
3438
  reverse=True,
3315
3439
  )
3316
3440
  except (OSError, UnicodeError) as error:
@@ -3507,6 +3631,309 @@ def _profile_package_versions(dsh_home: Path, profile: str) -> dict[str, str]:
3507
3631
  return versions
3508
3632
 
3509
3633
 
3634
+ def _parse_pnpm_settings(result: subprocess.CompletedProcess[str]) -> dict[str, object]:
3635
+ try:
3636
+ if len(result.stdout.encode("utf-8")) > 256 * 1024:
3637
+ raise ValueError("oversized configuration")
3638
+ value = json.loads(result.stdout)
3639
+ except (ValueError, UnicodeError) as error:
3640
+ raise DshLifecycleError(
3641
+ "pnpm returned invalid profile configuration"
3642
+ ) from error
3643
+ if not isinstance(value, dict) or not all(isinstance(key, str) for key in value):
3644
+ raise DshLifecycleError("pnpm returned invalid profile configuration")
3645
+ return value
3646
+
3647
+
3648
+ def _read_pnpm_settings(
3649
+ executable: str, dsh_home: Path, profile: str, transaction: _ProfileTransaction
3650
+ ) -> dict[str, object]:
3651
+ before = transaction.latest_package_identity
3652
+ result = _run_profile_command(
3653
+ [
3654
+ executable,
3655
+ "plugin",
3656
+ "--profile",
3657
+ profile,
3658
+ "config",
3659
+ "list",
3660
+ "--json",
3661
+ "--location",
3662
+ "project",
3663
+ ],
3664
+ dsh_home=dsh_home,
3665
+ transaction=transaction,
3666
+ )
3667
+ if before != transaction.latest_package_identity:
3668
+ transaction.rollback_blocked = True
3669
+ raise DshLifecycleError(
3670
+ "profile identity changed during configuration inspection; preserved"
3671
+ )
3672
+ return _parse_pnpm_settings(result)
3673
+
3674
+
3675
+ def _initial_profile_manifest(profile: str) -> bytes:
3676
+ app = {
3677
+ "web": "dsh-web-app",
3678
+ "acp": "dsh-acp-app",
3679
+ "headless": "dsh-headless",
3680
+ "sdk": "dsh-sdk-app",
3681
+ }.get(profile)
3682
+ bundles = ["@deepseek-ai/dsh-base"]
3683
+ if app is not None:
3684
+ bundles.append(f"@deepseek-ai/{app}")
3685
+ if profile == "sdk-minimal":
3686
+ bundles = ["@deepseek-ai/dsh-sdk-minimal"]
3687
+ reload = (
3688
+ "startup" if profile in {"acp", "headless", "sdk", "sdk-minimal"} else "live"
3689
+ )
3690
+ return (
3691
+ json.dumps(
3692
+ {
3693
+ "name": f"dsh-profile-{profile}",
3694
+ "private": True,
3695
+ "dependencies": {},
3696
+ "dsh": {"profile": {"bundles": bundles, "patchReload": reload}},
3697
+ },
3698
+ indent=2,
3699
+ )
3700
+ + "\n"
3701
+ ).encode()
3702
+
3703
+
3704
+ def _preclaim_profile_configuration(
3705
+ transaction: _ProfileTransaction, profile: str
3706
+ ) -> None:
3707
+ """Own initialization inodes before invoking a command that may initialize a profile."""
3708
+ existing = [
3709
+ entry
3710
+ for entry in (transaction.latest_manifest, transaction.latest_workspace)
3711
+ if entry is not None
3712
+ ]
3713
+ try:
3714
+ for path in (transaction.profile_root.parent, transaction.profile_root):
3715
+ if not transaction.base_existed[path]:
3716
+ owned: list[_OwnedEntry] = []
3717
+ _claim_directory(path, owned)
3718
+ transaction.created_base[path] = owned[0]
3719
+ files = (
3720
+ ("package.json", transaction.manifest, _initial_profile_manifest(profile)),
3721
+ (
3722
+ PNPM_WORKSPACE_FILENAME,
3723
+ transaction.workspace,
3724
+ b'{"packages":["."],"nodeLinker":"hoisted","autoInstallPeers":false}\n',
3725
+ ),
3726
+ ("cordis.patch.yml", transaction.profile_patch, DEFAULT_PROFILE_PATCH),
3727
+ )
3728
+ parent = _open_active_dsh_directory(transaction.profile_root)
3729
+ try:
3730
+ for name, prestate, content in files:
3731
+ if prestate is not None:
3732
+ continue
3733
+ snapshot = _PathPrestate(
3734
+ transaction.profile_root / name, "file", 0o600, content
3735
+ )
3736
+ claimed: list[_OwnedEntry] = []
3737
+ if not _create_snapshot_file(snapshot, parent, claimed):
3738
+ raise DshLifecycleError(
3739
+ "profile initialization file identity changed"
3740
+ )
3741
+ existing.append(claimed[0])
3742
+ if name == "package.json":
3743
+ transaction.initialized_manifest = snapshot
3744
+ elif name == "cordis.patch.yml":
3745
+ transaction.latest_profile_patch = claimed[0]
3746
+ finally:
3747
+ os.close(parent)
3748
+ for entry in existing:
3749
+ _assert_entry_unchanged(entry, "profile initialization")
3750
+ transaction.latest_package_identity = _capture_package_mutation_identity(
3751
+ transaction.profile_root
3752
+ )
3753
+ _observe_profile_transaction(transaction)
3754
+ except (DshLifecycleError, OSError, KeyboardInterrupt):
3755
+ transaction.rollback_blocked = True
3756
+ raise DshLifecycleError(
3757
+ "profile initialization ownership changed; preserved for recovery"
3758
+ ) from None
3759
+
3760
+
3761
+ def _read_only_pnpm_settings(dsh_home: Path, profile: str) -> dict[str, object]:
3762
+ root = dsh_home / "profiles" / profile
3763
+ if not root.exists():
3764
+ return {}
3765
+ _optional_profile_file(root / PNPM_WORKSPACE_FILENAME)
3766
+ executable, _version = _find_supported_pnpm(dsh_home)
3767
+ result = _run_version_probe(
3768
+ [
3769
+ executable,
3770
+ "--dir",
3771
+ str(root),
3772
+ "config",
3773
+ "list",
3774
+ "--json",
3775
+ "--location",
3776
+ "project",
3777
+ ],
3778
+ dsh_home=dsh_home,
3779
+ label="pnpm configuration",
3780
+ remediation=f"inspect profile configuration at {str(root)!r}",
3781
+ )
3782
+ return _parse_pnpm_settings(result)
3783
+
3784
+
3785
+ def _compatible_sdk_overrides(settings: dict[str, object]) -> dict[str, str]:
3786
+ linker = settings.get("nodeLinker")
3787
+ if linker is not None and linker != "hoisted":
3788
+ raise DshLifecycleError(
3789
+ "DSH profile requires nodeLinker: hoisted; existing setting was preserved"
3790
+ )
3791
+ overrides = settings.get("overrides", {})
3792
+ if not isinstance(overrides, dict) or not all(
3793
+ isinstance(key, str) and isinstance(value, str)
3794
+ for key, value in overrides.items()
3795
+ ):
3796
+ raise DshLifecycleError(
3797
+ "DSH profile overrides must be an object of package selectors and versions"
3798
+ )
3799
+ for key, value in overrides.items():
3800
+ if CLAUDE_SDK_PACKAGE in key and value != CLAUDE_SDK_VERSION:
3801
+ raise DshLifecycleError(
3802
+ "conflicting Claude SDK override; existing profile settings were preserved"
3803
+ )
3804
+ return {**overrides, CLAUDE_SDK_OVERRIDE: CLAUDE_SDK_VERSION}
3805
+
3806
+
3807
+ def _configure_claude_sdk(
3808
+ executable: str, dsh_home: Path, profile: str, transaction: _ProfileTransaction
3809
+ ) -> None:
3810
+ if transaction.base_existed[transaction.profile_root]:
3811
+ _compatible_sdk_overrides(_read_only_pnpm_settings(dsh_home, profile))
3812
+ _preclaim_profile_configuration(transaction, profile)
3813
+ settings = _read_pnpm_settings(executable, dsh_home, profile, transaction)
3814
+ overrides = _compatible_sdk_overrides(settings)
3815
+ changes: dict[str, object] = {"overrides": overrides}
3816
+ if settings.get("nodeLinker") is None:
3817
+ changes["nodeLinker"] = "hoisted"
3818
+ for key, value in changes.items():
3819
+ if settings.get(key) == value:
3820
+ continue
3821
+ _run_profile_command(
3822
+ [
3823
+ executable,
3824
+ "plugin",
3825
+ "--profile",
3826
+ profile,
3827
+ "config",
3828
+ "set",
3829
+ "--json",
3830
+ "--location",
3831
+ "project",
3832
+ key,
3833
+ json.dumps(value),
3834
+ ],
3835
+ dsh_home=dsh_home,
3836
+ transaction=transaction,
3837
+ )
3838
+ expected = {**settings, key: value}
3839
+ settings = _read_pnpm_settings(executable, dsh_home, profile, transaction)
3840
+ if settings != expected:
3841
+ transaction.rollback_blocked = True
3842
+ raise DshLifecycleError(
3843
+ "profile settings changed during configuration; refusing to overwrite concurrent edits"
3844
+ )
3845
+ transaction.pnpm_settings = settings
3846
+
3847
+
3848
+ def _check_pnpm_settings_after_mutation(
3849
+ executable: str, dsh_home: Path, profile: str, transaction: _ProfileTransaction
3850
+ ) -> None:
3851
+ expected = dict(transaction.pnpm_settings or {})
3852
+ found = _read_pnpm_settings(executable, dsh_home, profile, transaction)
3853
+ # pnpm may record release-age approval for the exact reviewed SDK artifacts.
3854
+ previous = expected.pop("minimumReleaseAgeExclude", [])
3855
+ current = found.get("minimumReleaseAgeExclude", [])
3856
+ remaining = {
3857
+ key: value for key, value in found.items() if key != "minimumReleaseAgeExclude"
3858
+ }
3859
+ approved = {f"{CLAUDE_SDK_PACKAGE}@{CLAUDE_SDK_VERSION}"}
3860
+ approved.update(f"{package}@{version}" for package, version in PACKAGES.items())
3861
+ approved.update(
3862
+ f"{CLAUDE_SDK_PACKAGE}-{platform}@{CLAUDE_SDK_VERSION}"
3863
+ for platform in (
3864
+ "darwin-arm64",
3865
+ "darwin-x64",
3866
+ "linux-arm64",
3867
+ "linux-arm64-musl",
3868
+ "linux-x64",
3869
+ "linux-x64-musl",
3870
+ "win32-arm64",
3871
+ "win32-x64",
3872
+ )
3873
+ )
3874
+ if (
3875
+ remaining != expected
3876
+ or not isinstance(previous, list)
3877
+ or not isinstance(current, list)
3878
+ or any(not isinstance(item, str) for item in [*previous, *current])
3879
+ or not set(previous).issubset(current)
3880
+ or not set(current).difference(previous).issubset(approved)
3881
+ ):
3882
+ transaction.rollback_blocked = True
3883
+ raise DshLifecycleError(
3884
+ "profile settings changed during package mutation; refusing to overwrite concurrent edits"
3885
+ )
3886
+ transaction.pnpm_settings = found
3887
+
3888
+
3889
+ def _assert_installed_claude_sdk(dsh_home: Path, profile: str) -> None:
3890
+ modules = dsh_home / "profiles" / profile / "node_modules"
3891
+ provider = modules / Path(CLAUDE_PROVIDER)
3892
+ _assert_no_symlink_ancestry(provider, "Claude provider")
3893
+ try:
3894
+ provider_manifest = json.loads(_read_regular_bytes(provider / "package.json"))
3895
+ except (DshLifecycleError, ValueError) as error:
3896
+ raise DshLifecycleError(
3897
+ "installed Claude provider metadata is missing or unsafe"
3898
+ ) from error
3899
+ if (
3900
+ not isinstance(provider_manifest, dict)
3901
+ or provider_manifest.get("name") != CLAUDE_PROVIDER
3902
+ or provider_manifest.get("version") != SUPPORTED_DSH_VERSION
3903
+ ):
3904
+ raise DshLifecycleError(
3905
+ "installed Claude provider version does not match the qualified DSH host"
3906
+ )
3907
+ # Match the actual bare import's nearest node_modules candidates, including shadows.
3908
+ candidates = (
3909
+ provider / "lib" / "node_modules",
3910
+ provider / "node_modules",
3911
+ provider.parent / "node_modules",
3912
+ modules,
3913
+ )
3914
+ for base in candidates:
3915
+ root = base / Path(CLAUDE_SDK_PACKAGE)
3916
+ if not root.exists() and not root.is_symlink():
3917
+ continue
3918
+ _assert_no_symlink_ancestry(root, "Claude SDK")
3919
+ try:
3920
+ manifest = json.loads(_read_regular_bytes(root / "package.json"))
3921
+ except (DshLifecycleError, ValueError) as error:
3922
+ raise DshLifecycleError(
3923
+ "installed Claude SDK metadata is unreadable"
3924
+ ) from error
3925
+ if (
3926
+ isinstance(manifest, dict)
3927
+ and manifest.get("name") == CLAUDE_SDK_PACKAGE
3928
+ and manifest.get("version") == CLAUDE_SDK_VERSION
3929
+ ):
3930
+ return
3931
+ break
3932
+ raise DshLifecycleError(
3933
+ f"installed Claude SDK does not match {CLAUDE_SDK_VERSION}; run 'ai-toolkit dsh update'"
3934
+ )
3935
+
3936
+
3510
3937
  def _profile_unmanaged_dependencies(
3511
3938
  dsh_home: Path, profile: str
3512
3939
  ) -> dict[str, dict[str, object]]:
@@ -4034,6 +4461,12 @@ def _install(
4034
4461
  if source_hash != current_hash:
4035
4462
  raise DshLifecycleError("installed DSH package preset source has drifted")
4036
4463
  _current_dsh_executable(dsh_home)
4464
+ settings = _read_only_pnpm_settings(dsh_home, profile)
4465
+ if settings.get("overrides", {}) != _compatible_sdk_overrides(settings):
4466
+ raise DshLifecycleError(
4467
+ "Claude SDK compatibility configuration is missing; run 'ai-toolkit dsh update'"
4468
+ )
4469
+ _assert_installed_claude_sdk(dsh_home, profile)
4037
4470
  return f"DSH profile integration already installed and unchanged: {profile}"
4038
4471
  if preset_destination.exists() or preset_destination.is_symlink():
4039
4472
  raise DshLifecycleError(
@@ -4059,6 +4492,10 @@ def _install(
4059
4492
  for package, version in PACKAGES.items()
4060
4493
  ]
4061
4494
  if dry_run:
4495
+ _compatible_sdk_overrides(_read_only_pnpm_settings(dsh_home, profile))
4496
+ print(
4497
+ f"PLAN Claude SDK profile override: {CLAUDE_SDK_OVERRIDE} -> {CLAUDE_SDK_VERSION}"
4498
+ )
4062
4499
  for command in commands:
4063
4500
  print("PLAN argv:", repr(command))
4064
4501
  print(
@@ -4082,6 +4519,7 @@ def _install(
4082
4519
  expected_transaction=profile_transaction,
4083
4520
  )
4084
4521
  try:
4522
+ _configure_claude_sdk(executable, dsh_home, profile, profile_transaction)
4085
4523
  expected_managed = dict(present_packages)
4086
4524
  observed_trees: dict[str, dict[str, object]] = {}
4087
4525
  for (package, version), command in zip(PACKAGES.items(), commands, strict=True):
@@ -4115,6 +4553,7 @@ def _install(
4115
4553
  expected_managed=PACKAGES,
4116
4554
  expected_unmanaged=unmanaged_dependencies,
4117
4555
  )
4556
+ _assert_installed_claude_sdk(dsh_home, profile)
4118
4557
  package_trees = _package_tree_inventories(dsh_home, profile)
4119
4558
  source = _package_source(dsh_home, profile)
4120
4559
  preset_hash = _tree_hash(source)
@@ -4347,6 +4786,10 @@ def _update(
4347
4786
  for package, version in PACKAGES.items()
4348
4787
  ]
4349
4788
  if dry_run:
4789
+ _compatible_sdk_overrides(_read_only_pnpm_settings(dsh_home, profile))
4790
+ print(
4791
+ f"PLAN Claude SDK profile override: {CLAUDE_SDK_OVERRIDE} -> {CLAUDE_SDK_VERSION}"
4792
+ )
4350
4793
  for command in commands:
4351
4794
  print("PLAN argv:", repr(command))
4352
4795
  print(f"PLAN preset: {_package_source(dsh_home, profile)} -> {destination}")
@@ -4366,11 +4809,10 @@ def _update(
4366
4809
  expected_transaction=profile_transaction,
4367
4810
  )
4368
4811
  try:
4812
+ _configure_claude_sdk(executable, dsh_home, profile, profile_transaction)
4369
4813
  expected_managed = dict(recorded_packages)
4370
4814
  expected_trees = dict(record["package_trees"])
4371
- for (package, version), command in zip(
4372
- PACKAGES.items(), commands, strict=True
4373
- ):
4815
+ for (package, version), command in zip(PACKAGES.items(), commands, strict=True):
4374
4816
  _run_profile_command(
4375
4817
  command, dsh_home=dsh_home, transaction=profile_transaction
4376
4818
  )
@@ -4398,6 +4840,7 @@ def _update(
4398
4840
  expected_managed=PACKAGES,
4399
4841
  expected_unmanaged=unmanaged_dependencies,
4400
4842
  )
4843
+ _assert_installed_claude_sdk(dsh_home, profile)
4401
4844
  package_trees = _package_tree_inventories(dsh_home, profile)
4402
4845
  preset_hash, backup = _replace_owned_preset(
4403
4846
  _package_source(dsh_home, profile),
@@ -4645,6 +5088,15 @@ def _doctor(*, profile: str) -> int:
4645
5088
  print(f"{package}: {found} (expected {expected})")
4646
5089
  if found != expected:
4647
5090
  healthy = False
5091
+ try:
5092
+ settings = _read_only_pnpm_settings(dsh_home, profile)
5093
+ if settings.get("overrides", {}) != _compatible_sdk_overrides(settings):
5094
+ raise DshLifecycleError("profile SDK override is missing")
5095
+ _assert_installed_claude_sdk(dsh_home, profile)
5096
+ print(f"Claude SDK: compatible ({CLAUDE_SDK_VERSION})")
5097
+ except DshLifecycleError as error:
5098
+ print(f"Claude SDK: incompatible ({error})")
5099
+ healthy = False
4648
5100
  try:
4649
5101
  record = get_dsh_profile(profile)
4650
5102
  except ValueError as error:
@@ -4778,6 +5230,9 @@ def _uninstall(
4778
5230
  )
4779
5231
  package_mutation_started = False
4780
5232
  try:
5233
+ profile_transaction.pnpm_settings = _read_pnpm_settings(
5234
+ executable, dsh_home, profile, profile_transaction
5235
+ )
4781
5236
  _assert_entry_unchanged(destination_identity, "managed preset")
4782
5237
  backup = _relocate_owned_preset(
4783
5238
  destination_identity,
@@ -4793,9 +5248,7 @@ def _uninstall(
4793
5248
  )
4794
5249
  expected_managed = dict(packages)
4795
5250
  expected_trees = dict(record["package_trees"])
4796
- for package, command in zip(
4797
- reversed(recorded_packages), commands, strict=True
4798
- ):
5251
+ for package, command in zip(reversed(recorded_packages), commands, strict=True):
4799
5252
  package_mutation_started = True
4800
5253
  _run_profile_command(
4801
5254
  command, dsh_home=dsh_home, transaction=profile_transaction