claude-mpm 5.6.4__py3-none-any.whl → 5.6.8__py3-none-any.whl

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 (73) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/base_agent.json +1 -1
  3. claude_mpm/cli/commands/skill_source.py +51 -2
  4. claude_mpm/cli/commands/skills.py +5 -3
  5. claude_mpm/cli/parsers/skill_source_parser.py +4 -0
  6. claude_mpm/cli/parsers/skills_parser.py +5 -0
  7. claude_mpm/config/skill_sources.py +16 -0
  8. claude_mpm/core/config.py +27 -19
  9. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -1
  10. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +16 -16
  11. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  12. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-312.pyc +0 -0
  13. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  14. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  15. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-312.pyc +0 -0
  16. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-314.pyc +0 -0
  17. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  18. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  19. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-312.pyc +0 -0
  20. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-314.pyc +0 -0
  21. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  22. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-312.pyc +0 -0
  23. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-314.pyc +0 -0
  24. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  25. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-314.pyc +0 -0
  26. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  27. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-312.pyc +0 -0
  28. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-314.pyc +0 -0
  29. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  30. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-312.pyc +0 -0
  31. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-314.pyc +0 -0
  32. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  33. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-312.pyc +0 -0
  34. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-314.pyc +0 -0
  35. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +29 -30
  36. claude_mpm/hooks/claude_hooks/installer.py +41 -0
  37. claude_mpm/hooks/claude_hooks/memory_integration.py +30 -21
  38. claude_mpm/hooks/claude_hooks/response_tracking.py +39 -58
  39. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  40. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-312.pyc +0 -0
  41. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-314.pyc +0 -0
  42. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  43. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-312.pyc +0 -0
  44. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-314.pyc +0 -0
  45. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  46. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-312.pyc +0 -0
  47. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-314.pyc +0 -0
  48. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  49. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-312.pyc +0 -0
  50. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-314.pyc +0 -0
  51. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  52. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-312.pyc +0 -0
  53. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-314.pyc +0 -0
  54. claude_mpm/hooks/claude_hooks/services/connection_manager.py +23 -28
  55. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +22 -26
  56. claude_mpm/hooks/claude_hooks/services/state_manager.py +23 -36
  57. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +47 -73
  58. claude_mpm/hooks/session_resume_hook.py +22 -18
  59. claude_mpm/hooks/templates/pre_tool_use_template.py +10 -2
  60. claude_mpm/scripts/claude-hook-handler.sh +5 -5
  61. claude_mpm/services/agents/agent_selection_service.py +2 -2
  62. claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
  63. claude_mpm/services/skills/git_skill_source_manager.py +79 -8
  64. claude_mpm/services/skills/selective_skill_deployer.py +28 -0
  65. claude_mpm/services/skills/skill_discovery_service.py +17 -1
  66. claude_mpm/services/skills_deployer.py +31 -5
  67. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.8.dist-info}/METADATA +1 -1
  68. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.8.dist-info}/RECORD +73 -34
  69. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.8.dist-info}/WHEEL +0 -0
  70. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.8.dist-info}/entry_points.txt +0 -0
  71. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.8.dist-info}/licenses/LICENSE +0 -0
  72. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.8.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  73. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.8.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION CHANGED
@@ -1 +1 @@
1
- 5.6.4
1
+ 5.6.8
@@ -28,4 +28,4 @@
28
28
  "claude-4-opus"
29
29
  ]
30
30
  }
31
- }
31
+ }
@@ -11,6 +11,7 @@ for better UX. Handles errors gracefully with actionable messages.
11
11
 
12
12
  import json
13
13
  import logging
14
+ import os
14
15
  import re
15
16
 
16
17
  from ...config.skill_sources import SkillSource, SkillSourceConfiguration
@@ -20,6 +21,33 @@ from ...services.skills.skill_discovery_service import SkillDiscoveryService
20
21
  logger = logging.getLogger(__name__)
21
22
 
22
23
 
24
+ def _get_github_token(source: SkillSource | None = None) -> str | None:
25
+ """Get GitHub token with source-specific override support.
26
+
27
+ Priority: source.token > GITHUB_TOKEN > GH_TOKEN
28
+
29
+ Args:
30
+ source: Optional SkillSource to check for per-source token
31
+
32
+ Returns:
33
+ GitHub token if found, None otherwise
34
+
35
+ Security Note:
36
+ Token is never logged or printed to avoid exposure.
37
+ """
38
+ # Priority 1: Per-source token (env var reference or direct)
39
+ if source and source.token:
40
+ if source.token.startswith("$"):
41
+ # Env var reference: $VAR_NAME -> os.environ.get("VAR_NAME")
42
+ env_var_name = source.token[1:]
43
+ return os.environ.get(env_var_name)
44
+ # Direct token (not recommended but supported)
45
+ return source.token
46
+
47
+ # Priority 2-3: Global environment variables
48
+ return os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
49
+
50
+
23
51
  def _test_skill_repository_access(source: SkillSource) -> dict:
24
52
  """Test if skill repository is accessible via GitHub API.
25
53
 
@@ -58,7 +86,13 @@ def _test_skill_repository_access(source: SkillSource) -> dict:
58
86
  # Test GitHub API access
59
87
  api_url = f"https://api.github.com/repos/{owner_repo}"
60
88
 
61
- response = requests.get(api_url, timeout=10)
89
+ # Build headers with authentication if token available
90
+ headers = {"Accept": "application/vnd.github+json"}
91
+ token = _get_github_token(source)
92
+ if token:
93
+ headers["Authorization"] = f"token {token}"
94
+
95
+ response = requests.get(api_url, headers=headers, timeout=10)
62
96
 
63
97
  if response.status_code == 200:
64
98
  return {"accessible": True, "error": None}
@@ -68,9 +102,14 @@ def _test_skill_repository_access(source: SkillSource) -> dict:
68
102
  "error": f"Repository not found: {owner_repo}",
69
103
  }
70
104
  if response.status_code == 403:
105
+ error_msg = "Access denied (private repository or rate limit)"
106
+ if not token:
107
+ error_msg += (
108
+ ". Try setting GITHUB_TOKEN environment variable for private repos"
109
+ )
71
110
  return {
72
111
  "accessible": False,
73
- "error": "Access denied (private repository or rate limit)",
112
+ "error": error_msg,
74
113
  }
75
114
  return {
76
115
  "accessible": False,
@@ -263,6 +302,15 @@ def handle_add_skill_source(args) -> int:
263
302
 
264
303
  # Create new source
265
304
  enabled = not args.disabled
305
+ token = getattr(args, "token", None)
306
+
307
+ # Security warning for direct tokens
308
+ if token and not token.startswith("$"):
309
+ print("⚠️ Warning: Direct token values in config are not recommended")
310
+ print(" Consider using environment variable reference instead:")
311
+ print(" --token $MY_PRIVATE_TOKEN")
312
+ print()
313
+
266
314
  source = SkillSource(
267
315
  id=source_id,
268
316
  type="git",
@@ -270,6 +318,7 @@ def handle_add_skill_source(args) -> int:
270
318
  branch=args.branch,
271
319
  priority=args.priority,
272
320
  enabled=enabled,
321
+ token=token,
273
322
  )
274
323
 
275
324
  # Determine if we should test
@@ -538,6 +538,7 @@ class SkillsManagementCommand(BaseCommand):
538
538
  toolchain = getattr(args, "toolchain", None)
539
539
  categories = getattr(args, "categories", None)
540
540
  force = getattr(args, "force", False)
541
+ deploy_all = getattr(args, "all", False)
541
542
 
542
543
  if collection:
543
544
  console.print(
@@ -548,14 +549,15 @@ class SkillsManagementCommand(BaseCommand):
548
549
  "\n[bold cyan]Deploying skills from default collection...[/bold cyan]\n"
549
550
  )
550
551
 
551
- # Selective deployment is ALWAYS enabled (deploy only agent-referenced skills)
552
- # This ensures only skills linked to deployed agents are deployed
552
+ # Use selective deployment unless --all flag is provided
553
+ # Selective mode deploys only agent-referenced skills
554
+ # --all mode deploys all available skills from the collection
553
555
  result = self.skills_deployer.deploy_skills(
554
556
  collection=collection,
555
557
  toolchain=toolchain,
556
558
  categories=categories,
557
559
  force=force,
558
- selective=True, # Always use selective deployment
560
+ selective=not deploy_all,
559
561
  )
560
562
 
561
563
  # Display results
@@ -76,6 +76,10 @@ def add_skill_source_subparser(subparsers) -> argparse.ArgumentParser:
76
76
  dest="skip_test",
77
77
  help="Skip immediate testing (not recommended)",
78
78
  )
79
+ add_parser.add_argument(
80
+ "--token",
81
+ help="GitHub token or env var reference (e.g., ghp_xxx or $PRIVATE_TOKEN)",
82
+ )
79
83
 
80
84
  # Remove repository
81
85
  remove_parser = skill_source_subparsers.add_parser(
@@ -167,6 +167,11 @@ def add_skills_subparser(subparsers) -> argparse.ArgumentParser:
167
167
  action="store_true",
168
168
  help="Force redeployment of already deployed skills",
169
169
  )
170
+ deploy_github_parser.add_argument(
171
+ "--all",
172
+ action="store_true",
173
+ help="Deploy all available skills, not just agent-referenced ones",
174
+ )
170
175
 
171
176
  # List available GitHub skills
172
177
  list_available_parser = skills_subparsers.add_parser(
@@ -54,6 +54,7 @@ class SkillSource:
54
54
  branch: Git branch to use (default: "main")
55
55
  priority: Priority for skill resolution (lower = higher precedence)
56
56
  enabled: Whether this source should be synced
57
+ token: Optional GitHub token or env var reference (e.g., "$MY_TOKEN")
57
58
 
58
59
  Priority System:
59
60
  - 0: Reserved for system repository (highest precedence)
@@ -61,6 +62,12 @@ class SkillSource:
61
62
  - 100-999: Normal priority custom sources
62
63
  - 1000+: Low priority custom sources
63
64
 
65
+ Token Authentication:
66
+ - Direct token: "ghp_xxxxx" (stored in config, not recommended)
67
+ - Env var reference: "$PRIVATE_REPO_TOKEN" (resolved at runtime)
68
+ - If None, falls back to GITHUB_TOKEN or GH_TOKEN env vars
69
+ - Priority: source.token > GITHUB_TOKEN > GH_TOKEN
70
+
64
71
  Example:
65
72
  >>> source = SkillSource(
66
73
  ... id="system",
@@ -70,6 +77,12 @@ class SkillSource:
70
77
  ... )
71
78
  >>> source.validate()
72
79
  []
80
+ >>> private_source = SkillSource(
81
+ ... id="private",
82
+ ... type="git",
83
+ ... url="https://github.com/myorg/private-skills",
84
+ ... token="$PRIVATE_REPO_TOKEN"
85
+ ... )
73
86
  """
74
87
 
75
88
  id: str
@@ -78,6 +91,7 @@ class SkillSource:
78
91
  branch: str = "main"
79
92
  priority: int = 100
80
93
  enabled: bool = True
94
+ token: Optional[str] = None
81
95
 
82
96
  def __post_init__(self):
83
97
  """Validate skill source configuration after initialization.
@@ -262,6 +276,7 @@ class SkillSourceConfiguration:
262
276
  branch=source_data.get("branch", "main"),
263
277
  priority=source_data.get("priority", 100),
264
278
  enabled=source_data.get("enabled", True),
279
+ token=source_data.get("token"),
265
280
  )
266
281
  sources.append(source)
267
282
  except (KeyError, ValueError) as e:
@@ -326,6 +341,7 @@ class SkillSourceConfiguration:
326
341
  "branch": source.branch,
327
342
  "priority": source.priority,
328
343
  "enabled": source.enabled,
344
+ **({"token": source.token} if source.token else {}),
329
345
  }
330
346
  for source in sources
331
347
  ]
claude_mpm/core/config.py CHANGED
@@ -12,11 +12,10 @@ import threading
12
12
  from pathlib import Path
13
13
  from typing import Any, Dict, List, Optional, Tuple, Union
14
14
 
15
- import yaml
16
-
17
15
  from claude_mpm.core.logging_utils import get_logger
18
16
 
19
- from ..utils.config_manager import ConfigurationManager
17
+ # Lazy import ConfigurationManager to avoid importing yaml at module level
18
+ # This prevents hook errors when yaml isn't available in the execution environment
20
19
  from .exceptions import ConfigurationError, FileOperationError
21
20
  from .unified_paths import get_path_manager
22
21
 
@@ -104,6 +103,9 @@ class Config:
104
103
  Config._initialized = True
105
104
  logger.debug("Initializing Config singleton for the first time")
106
105
 
106
+ # Lazy import ConfigurationManager at runtime to avoid yaml import at module level
107
+ from ..utils.config_manager import ConfigurationManager
108
+
107
109
  # Initialize instance variables inside the lock to ensure thread safety
108
110
  self._config: Dict[str, Any] = {}
109
111
  self._env_prefix = env_prefix
@@ -224,21 +226,6 @@ class Config:
224
226
  f"Response logging format: {response_logging.get('format', 'json')}"
225
227
  )
226
228
 
227
- except yaml.YAMLError as e:
228
- logger.error(f"YAML syntax error in {file_path}: {e}")
229
- if hasattr(e, "problem_mark"):
230
- mark = e.problem_mark
231
- logger.error(f"Error at line {mark.line + 1}, column {mark.column + 1}")
232
- logger.info(
233
- "TIP: Validate your YAML at https://www.yamllint.com/ or run: python scripts/validate_configuration.py"
234
- )
235
- logger.info(
236
- "TIP: Common issue - YAML requires spaces, not tabs. Fix with: sed -i '' 's/\t/ /g' "
237
- + str(file_path)
238
- )
239
- # Store error for later retrieval
240
- self._config["_load_error"] = str(e)
241
-
242
229
  except json.JSONDecodeError as e:
243
230
  logger.error(f"JSON syntax error in {file_path}: {e}")
244
231
  logger.error(f"Error at line {e.lineno}, column {e.colno}")
@@ -255,7 +242,28 @@ class Config:
255
242
  },
256
243
  ) from e
257
244
  except Exception as e:
258
- # Catch any remaining unexpected errors and wrap them as configuration errors
245
+ # Handle YAML errors without importing yaml at module level
246
+ # ConfigurationManager.load_yaml raises yaml.YAMLError, but we don't want to import yaml
247
+ # Check if it's a YAML error by class name to avoid import
248
+ if e.__class__.__name__ == "YAMLError":
249
+ logger.error(f"YAML syntax error in {file_path}: {e}")
250
+ if hasattr(e, "problem_mark"):
251
+ mark = e.problem_mark
252
+ logger.error(
253
+ f"Error at line {mark.line + 1}, column {mark.column + 1}"
254
+ )
255
+ logger.info(
256
+ "TIP: Validate your YAML at https://www.yamllint.com/ or run: python scripts/validate_configuration.py"
257
+ )
258
+ logger.info(
259
+ "TIP: Common issue - YAML requires spaces, not tabs. Fix with: sed -i '' 's/\t/ /g' "
260
+ + str(file_path)
261
+ )
262
+ # Store error for later retrieval
263
+ self._config["_load_error"] = str(e)
264
+ return # Don't re-raise, we handled it
265
+
266
+ # Not a YAML error, wrap as configuration error
259
267
  raise ConfigurationError(
260
268
  f"Unexpected error loading configuration from {file_path}: {e}",
261
269
  context={
@@ -1 +1 @@
1
- export const env={}
1
+ export const env={}
@@ -43,10 +43,10 @@ https://github.com/jquery/jquery/blob/master/src/event.js
43
43
  vec2 v = ab*vec2(-cs.y,cs.x);
44
44
  w = w + dot(p-u,v)/(dot(p-u,u)+dot(v,v));
45
45
  }
46
-
46
+
47
47
  // compute final point and distance
48
48
  float d = length(p-ab*vec2(cos(w),sin(w)));
49
-
49
+
50
50
  // return signed distance
51
51
  return (dot(p/ab,p/ab)>1.0) ? d : -d;
52
52
  }
@@ -55,16 +55,16 @@ https://github.com/jquery/jquery/blob/master/src/event.js
55
55
 
56
56
  uniform mat3 uPanZoomMatrix;
57
57
  uniform int uAtlasSize;
58
-
58
+
59
59
  // instanced
60
60
  in vec2 aPosition; // a vertex from the unit square
61
-
61
+
62
62
  in mat3 aTransform; // used to transform verticies, eg into a bounding box
63
63
  in int aVertType; // the type of thing we are rendering
64
64
 
65
65
  // the z-index that is output when using picking mode
66
66
  in vec4 aIndex;
67
-
67
+
68
68
  // For textures
69
69
  in int aAtlasId; // which shader unit/atlas to use
70
70
  in vec4 aTex; // x/y/w/h of texture in atlas
@@ -84,7 +84,7 @@ https://github.com/jquery/jquery/blob/master/src/event.js
84
84
  out vec4 vColor;
85
85
  out vec2 vPosition;
86
86
  // flat values are not interpolated
87
- flat out int vAtlasId;
87
+ flat out int vAtlasId;
88
88
  flat out int vVertType;
89
89
  flat out vec2 vTopRight;
90
90
  flat out vec2 vBotLeft;
@@ -92,7 +92,7 @@ https://github.com/jquery/jquery/blob/master/src/event.js
92
92
  flat out vec4 vBorderColor;
93
93
  flat out vec2 vBorderWidth;
94
94
  flat out vec4 vIndex;
95
-
95
+
96
96
  void main(void) {
97
97
  int vid = gl_VertexID;
98
98
  vec2 position = aPosition; // TODO make this a vec3, simplifies some code below
@@ -115,7 +115,7 @@ https://github.com/jquery/jquery/blob/master/src/event.js
115
115
 
116
116
  gl_Position = vec4(uPanZoomMatrix * aTransform * vec3(position, 1.0), 1.0);
117
117
  }
118
- else if(aVertType == `).concat(_t," || aVertType == ").concat(pa,`
118
+ else if(aVertType == `).concat(_t," || aVertType == ").concat(pa,`
119
119
  || aVertType == `).concat(sn," || aVertType == ").concat(ga,`) { // simple shapes
120
120
 
121
121
  // the bounding box is needed by the fragment shader
@@ -145,7 +145,7 @@ https://github.com/jquery/jquery/blob/master/src/event.js
145
145
 
146
146
  gl_Position = vec4(uPanZoomMatrix * vec3(point, 1.0), 1.0);
147
147
  vColor = aColor;
148
- }
148
+ }
149
149
  else if(aVertType == `).concat(Xl,`) {
150
150
  vec2 pointA = aPointAPointB.xy;
151
151
  vec2 pointB = aPointAPointB.zw;
@@ -194,7 +194,7 @@ https://github.com/jquery/jquery/blob/master/src/event.js
194
194
  }
195
195
 
196
196
  vColor = aColor;
197
- }
197
+ }
198
198
  else if(aVertType == `).concat(Ts,` && vid < 3) {
199
199
  // massage the first triangle into an edge arrow
200
200
  if(vid == 0)
@@ -246,16 +246,16 @@ https://github.com/jquery/jquery/blob/master/src/event.js
246
246
  `).concat(vm,`
247
247
 
248
248
  vec4 blend(vec4 top, vec4 bot) { // blend colors with premultiplied alpha
249
- return vec4(
249
+ return vec4(
250
250
  top.rgb + (bot.rgb * (1.0 - top.a)),
251
- top.a + (bot.a * (1.0 - top.a))
251
+ top.a + (bot.a * (1.0 - top.a))
252
252
  );
253
253
  }
254
254
 
255
255
  vec4 distInterp(vec4 cA, vec4 cB, float d) { // interpolate color using Signed Distance
256
256
  // scale to the zoom level so that borders don't look blurry when zoomed in
257
257
  // note 1.5 is an aribitrary value chosen because it looks good
258
- return mix(cA, cB, 1.0 - smoothstep(0.0, 1.5 / uZoom, abs(d)));
258
+ return mix(cA, cB, 1.0 - smoothstep(0.0, 1.5 / uZoom, abs(d)));
259
259
  }
260
260
 
261
261
  void main(void) {
@@ -263,7 +263,7 @@ https://github.com/jquery/jquery/blob/master/src/event.js
263
263
  // look up the texel from the texture unit
264
264
  `).concat(i.map(function(u){return"if(vAtlasId == ".concat(u,") outColor = texture(uTexture").concat(u,", vTexCoord);")}).join(`
265
265
  else `),`
266
- }
266
+ }
267
267
  else if(vVertType == `).concat(Ts,`) {
268
268
  // mimics how canvas renderer uses context.globalCompositeOperation = 'destination-out';
269
269
  outColor = blend(vColor, uBGColor);
@@ -272,7 +272,7 @@ https://github.com/jquery/jquery/blob/master/src/event.js
272
272
  else if(vVertType == `).concat(_t,` && vBorderWidth == vec2(0.0)) { // simple rectangle with no border
273
273
  outColor = vColor; // unit square is already transformed to the rectangle, nothing else needs to be done
274
274
  }
275
- else if(vVertType == `).concat(_t," || vVertType == ").concat(pa,`
275
+ else if(vVertType == `).concat(_t," || vVertType == ").concat(pa,`
276
276
  || vVertType == `).concat(sn," || vVertType == ").concat(ga,`) { // use SDF
277
277
 
278
278
  float outerBorder = vBorderWidth[0];
@@ -307,7 +307,7 @@ https://github.com/jquery/jquery/blob/master/src/event.js
307
307
  vec4 outerColor = outerBorder == 0.0 ? vec4(0) : vBorderColor;
308
308
  vec4 innerBorderColor = blend(vBorderColor, vColor);
309
309
  outColor = distInterp(innerBorderColor, outerColor, d);
310
- }
310
+ }
311
311
  else {
312
312
  vec4 outerColor;
313
313
  if(innerBorder == 0.0 && outerBorder == 0.0) {
@@ -22,7 +22,7 @@ USAGE:
22
22
  threshold_crossed = auto_pause.on_usage_update(metadata["usage"])
23
23
  if threshold_crossed:
24
24
  warning = auto_pause.emit_threshold_warning(threshold_crossed)
25
- print(f"\n⚠️ {warning}", file=sys.stderr)
25
+ _log(f"\n⚠️ {warning}")
26
26
 
27
27
  # Record actions during pause mode
28
28
  if auto_pause.is_pause_active():
@@ -34,7 +34,6 @@ USAGE:
34
34
  """
35
35
 
36
36
  import os
37
- import sys
38
37
  from datetime import datetime, timezone
39
38
  from pathlib import Path
40
39
  from typing import Any, Dict, Optional
@@ -45,6 +44,15 @@ from claude_mpm.services.infrastructure.context_usage_tracker import (
45
44
  ContextUsageTracker,
46
45
  )
47
46
 
47
+ # Try to import _log from hook_handler, fall back to no-op
48
+ try:
49
+ from claude_mpm.hooks.claude_hooks.hook_handler import _log
50
+ except ImportError:
51
+
52
+ def _log(msg: str) -> None:
53
+ pass # Silent fallback
54
+
55
+
48
56
  logger = get_logger(__name__)
49
57
 
50
58
  # Debug mode
@@ -100,11 +108,10 @@ class AutoPauseHandler:
100
108
  self._previous_threshold = current_state.threshold_reached
101
109
 
102
110
  if DEBUG:
103
- print(
111
+ _log(
104
112
  f"AutoPauseHandler initialized: "
105
113
  f"{current_state.percentage_used:.1f}% context used, "
106
- f"threshold: {current_state.threshold_reached}",
107
- file=sys.stderr,
114
+ f"threshold: {current_state.threshold_reached}"
108
115
  )
109
116
  except Exception as e:
110
117
  logger.error(f"Failed to initialize AutoPauseHandler: {e}")
@@ -169,10 +176,9 @@ class AutoPauseHandler:
169
176
  self._previous_threshold = current_threshold
170
177
 
171
178
  if DEBUG:
172
- print(
179
+ _log(
173
180
  f"Context threshold crossed: {current_threshold} "
174
- f"({state.percentage_used:.1f}%)",
175
- file=sys.stderr,
181
+ f"({state.percentage_used:.1f}%)"
176
182
  )
177
183
 
178
184
  # Trigger auto-pause if threshold reached
@@ -184,7 +190,7 @@ class AutoPauseHandler:
184
190
  except Exception as e:
185
191
  logger.error(f"Failed to update usage: {e}")
186
192
  if DEBUG:
187
- print(f"❌ Usage update failed: {e}", file=sys.stderr)
193
+ _log(f"❌ Usage update failed: {e}")
188
194
  # Don't propagate error - auto-pause is optional
189
195
  return None
190
196
 
@@ -220,12 +226,12 @@ class AutoPauseHandler:
220
226
  )
221
227
 
222
228
  if DEBUG:
223
- print(f"Recorded tool call during pause: {tool_name}", file=sys.stderr)
229
+ _log(f"Recorded tool call during pause: {tool_name}")
224
230
 
225
231
  except Exception as e:
226
232
  logger.error(f"Failed to record tool call: {e}")
227
233
  if DEBUG:
228
- print(f"❌ Failed to record tool call: {e}", file=sys.stderr)
234
+ _log(f"❌ Failed to record tool call: {e}")
229
235
 
230
236
  def on_assistant_response(self, response_summary: str) -> None:
231
237
  """Record an assistant response if auto-pause is active.
@@ -257,15 +263,14 @@ class AutoPauseHandler:
257
263
  )
258
264
 
259
265
  if DEBUG:
260
- print(
261
- f"Recorded assistant response during pause (length: {len(summary)})",
262
- file=sys.stderr,
266
+ _log(
267
+ f"Recorded assistant response during pause (length: {len(summary)})"
263
268
  )
264
269
 
265
270
  except Exception as e:
266
271
  logger.error(f"Failed to record assistant response: {e}")
267
272
  if DEBUG:
268
- print(f"❌ Failed to record assistant response: {e}", file=sys.stderr)
273
+ _log(f"❌ Failed to record assistant response: {e}")
269
274
 
270
275
  def on_user_message(self, message_summary: str) -> None:
271
276
  """Record a user message if auto-pause is active.
@@ -297,15 +302,12 @@ class AutoPauseHandler:
297
302
  )
298
303
 
299
304
  if DEBUG:
300
- print(
301
- f"Recorded user message during pause (length: {len(summary)})",
302
- file=sys.stderr,
303
- )
305
+ _log(f"Recorded user message during pause (length: {len(summary)})")
304
306
 
305
307
  except Exception as e:
306
308
  logger.error(f"Failed to record user message: {e}")
307
309
  if DEBUG:
308
- print(f"❌ Failed to record user message: {e}", file=sys.stderr)
310
+ _log(f"❌ Failed to record user message: {e}")
309
311
 
310
312
  def on_session_end(self) -> Optional[Path]:
311
313
  """Called when session ends. Finalizes any active pause.
@@ -318,7 +320,7 @@ class AutoPauseHandler:
318
320
  """
319
321
  if not self.is_pause_active():
320
322
  if DEBUG:
321
- print("No active pause to finalize", file=sys.stderr)
323
+ _log("No active pause to finalize")
322
324
  return None
323
325
 
324
326
  try:
@@ -326,14 +328,14 @@ class AutoPauseHandler:
326
328
  session_path = self.pause_manager.finalize_pause(create_full_snapshot=True)
327
329
 
328
330
  if session_path and DEBUG:
329
- print(f"✅ Session finalized: {session_path.name}", file=sys.stderr)
331
+ _log(f"✅ Session finalized: {session_path.name}")
330
332
 
331
333
  return session_path
332
334
 
333
335
  except Exception as e:
334
336
  logger.error(f"Failed to finalize pause session: {e}")
335
337
  if DEBUG:
336
- print(f"❌ Failed to finalize pause: {e}", file=sys.stderr)
338
+ _log(f"❌ Failed to finalize pause: {e}")
337
339
  raise
338
340
 
339
341
  def is_pause_active(self) -> bool:
@@ -417,9 +419,7 @@ class AutoPauseHandler:
417
419
  # Check if pause is already active
418
420
  if self.is_pause_active():
419
421
  if DEBUG:
420
- print(
421
- "Auto-pause already active, skipping trigger", file=sys.stderr
422
- )
422
+ _log("Auto-pause already active, skipping trigger")
423
423
  return
424
424
 
425
425
  # Start incremental pause
@@ -429,16 +429,15 @@ class AutoPauseHandler:
429
429
  )
430
430
 
431
431
  if DEBUG:
432
- print(
432
+ _log(
433
433
  f"✅ Auto-pause triggered: {session_id} "
434
- f"({state.percentage_used:.1f}% context used)",
435
- file=sys.stderr,
434
+ f"({state.percentage_used:.1f}% context used)"
436
435
  )
437
436
 
438
437
  except Exception as e:
439
438
  logger.error(f"Failed to trigger auto-pause: {e}")
440
439
  if DEBUG:
441
- print(f"❌ Failed to trigger auto-pause: {e}", file=sys.stderr)
440
+ _log(f"❌ Failed to trigger auto-pause: {e}")
442
441
  # Don't propagate - auto-pause is optional
443
442
 
444
443
  def _summarize_dict(