claude-mpm 5.4.84__py3-none-any.whl → 5.4.87__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md +11 -0
- claude_mpm/agents/PM_INSTRUCTIONS.md +47 -5
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B7S5qgOx.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.D3t4z6uz.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → 14Ru8gxt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 7ZAeO_Uj.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/AeivYILh.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → B06ALsCS.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → BCWDw8BF.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → BS0ej2w8.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → BVFqgd56.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → BXs4CVzO.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → B_fnSNFx.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → BfMC7wDI.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BfXd4Xj4.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BnFPFynJ.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → BwpSELyW.js} +3 -3
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → C5Dg_JxJ.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → C7i47te_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → C86quetY.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → CDNOxKrg.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → CDi5wzaD.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CQ94FMOU.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → CT5eAo1x.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C_l0vq62.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cn4nXAfg.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → CvWciI1W.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CyrTH56Q.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CzkNB1Vu.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → D0Fj1OdD.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → D1ARDjz0.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → DJN4AVXS.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DJuK4-OP.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DLeM8wSV.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → DNI1jw9S.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DOViuQX_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → DOeJfApz.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → DTgfNBV9.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → DWDi9IaK.js} +5 -5
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D_vpdI7l.js +325 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → DdIDcQsD.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DnL7ky1O.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → Dqtg3hb8.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → EKp_wsKE.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → MJf6AOIJ.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → NsEh4Ivo.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/URSAF6IJ.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → WiqB4NUY.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → s04HIjWg.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → vJiSSdpk.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.CnXU_fEX.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.CUaAfoQJ.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.znyTz9u3.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.CLVHDDxl.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
- claude_mpm/dashboard/static/svelte-build/index.html +7 -7
- claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
- claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +486 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +74 -1
- claude_mpm/hooks/claude_hooks/hook_handler.py +25 -3
- claude_mpm/hooks/claude_hooks/response_tracking.py +17 -1
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +30 -6
- claude_mpm/hooks/session_resume_hook.py +85 -1
- claude_mpm/services/cli/__init__.py +3 -0
- claude_mpm/services/cli/incremental_pause_manager.py +561 -0
- claude_mpm/services/infrastructure/__init__.py +4 -0
- claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
- {claude_mpm-5.4.84.dist-info → claude_mpm-5.4.87.dist-info}/METADATA +27 -9
- {claude_mpm-5.4.84.dist-info → claude_mpm-5.4.87.dist-info}/RECORD +94 -74
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/RJiighC3.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
- /claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.BuxSUm_s.js} +0 -0
- {claude_mpm-5.4.84.dist-info → claude_mpm-5.4.87.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.84.dist-info → claude_mpm-5.4.87.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.84.dist-info → claude_mpm-5.4.87.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.84.dist-info → claude_mpm-5.4.87.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.84.dist-info → claude_mpm-5.4.87.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""Context Usage Tracker Service.
|
|
2
|
+
|
|
3
|
+
WHY: Track cumulative token usage across Claude Code hook invocations to prevent
|
|
4
|
+
context window exhaustion and enable intelligent auto-pause behavior.
|
|
5
|
+
|
|
6
|
+
DESIGN DECISIONS:
|
|
7
|
+
- File-based persistence (hooks run in separate processes)
|
|
8
|
+
- Atomic file operations using StateStorage
|
|
9
|
+
- Threshold detection at 70% (caution), 85% (warning), 90% (auto_pause), 95% (critical)
|
|
10
|
+
- Session-scoped tracking with reset capability
|
|
11
|
+
- Compatible with 200k context budget for Claude Sonnet 4.5
|
|
12
|
+
|
|
13
|
+
USAGE:
|
|
14
|
+
tracker = ContextUsageTracker()
|
|
15
|
+
state = tracker.update_usage(input_tokens=15000, output_tokens=2000)
|
|
16
|
+
if tracker.should_auto_pause():
|
|
17
|
+
# Trigger auto-pause workflow
|
|
18
|
+
pass
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from dataclasses import asdict, dataclass, field
|
|
22
|
+
from datetime import datetime, timezone
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Optional
|
|
25
|
+
|
|
26
|
+
from claude_mpm.core.logger import get_logger
|
|
27
|
+
from claude_mpm.storage.state_storage import StateStorage
|
|
28
|
+
|
|
29
|
+
logger = get_logger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class ContextUsageState:
|
|
34
|
+
"""State tracking for cumulative context/token usage.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
session_id: Unique session identifier
|
|
38
|
+
cumulative_input_tokens: Total input tokens across all hook invocations
|
|
39
|
+
cumulative_output_tokens: Total output tokens across all hook invocations
|
|
40
|
+
cache_creation_tokens: Total tokens spent creating prompt cache
|
|
41
|
+
cache_read_tokens: Total tokens read from prompt cache
|
|
42
|
+
percentage_used: Percentage of 200k context budget used (0.0-100.0)
|
|
43
|
+
threshold_reached: Highest threshold crossed ('caution', 'warning', 'auto_pause', 'critical')
|
|
44
|
+
auto_pause_active: Whether auto-pause has been triggered
|
|
45
|
+
last_updated: ISO timestamp of last update
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
session_id: str
|
|
49
|
+
cumulative_input_tokens: int = 0
|
|
50
|
+
cumulative_output_tokens: int = 0
|
|
51
|
+
cache_creation_tokens: int = 0
|
|
52
|
+
cache_read_tokens: int = 0
|
|
53
|
+
percentage_used: float = 0.0
|
|
54
|
+
threshold_reached: Optional[str] = None
|
|
55
|
+
auto_pause_active: bool = False
|
|
56
|
+
last_updated: str = field(
|
|
57
|
+
default_factory=lambda: datetime.now(timezone.utc).isoformat()
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ContextUsageTracker:
|
|
62
|
+
"""Track cumulative context/token usage across hook invocations.
|
|
63
|
+
|
|
64
|
+
Features:
|
|
65
|
+
- Cumulative tracking across multiple API calls
|
|
66
|
+
- File-based persistence for cross-process state sharing
|
|
67
|
+
- Atomic file operations for concurrent safety
|
|
68
|
+
- Threshold detection (70%, 85%, 90%, 95%)
|
|
69
|
+
- Auto-pause triggering at 90%+ usage
|
|
70
|
+
- Session management with reset capability
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
# Claude Sonnet 4.5 context window
|
|
74
|
+
CONTEXT_BUDGET = 200000
|
|
75
|
+
|
|
76
|
+
# Threshold levels for warnings and auto-pause
|
|
77
|
+
THRESHOLDS = {
|
|
78
|
+
"caution": 0.70, # Yellow warning
|
|
79
|
+
"warning": 0.85, # Orange warning
|
|
80
|
+
"auto_pause": 0.90, # Trigger auto-pause
|
|
81
|
+
"critical": 0.95, # Red critical alert
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def __init__(self, project_path: Optional[Path] = None):
|
|
85
|
+
"""Initialize context usage tracker.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
project_path: Project root path (default: current directory)
|
|
89
|
+
"""
|
|
90
|
+
self.project_path = (project_path or Path.cwd()).resolve()
|
|
91
|
+
self.state_dir = self.project_path / ".claude-mpm" / "state"
|
|
92
|
+
self.state_dir.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
self.state_file = self.state_dir / "context-usage.json"
|
|
94
|
+
|
|
95
|
+
# Use StateStorage for atomic operations
|
|
96
|
+
self.storage = StateStorage(self.state_dir)
|
|
97
|
+
|
|
98
|
+
logger.debug(f"ContextUsageTracker initialized: {self.state_file}")
|
|
99
|
+
|
|
100
|
+
def update_usage(
|
|
101
|
+
self,
|
|
102
|
+
input_tokens: int,
|
|
103
|
+
output_tokens: int,
|
|
104
|
+
cache_creation: int = 0,
|
|
105
|
+
cache_read: int = 0,
|
|
106
|
+
) -> ContextUsageState:
|
|
107
|
+
"""Update cumulative usage from API response.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
input_tokens: Input tokens from this API call
|
|
111
|
+
output_tokens: Output tokens from this API call
|
|
112
|
+
cache_creation: Cache creation tokens (optional)
|
|
113
|
+
cache_read: Cache read tokens (optional)
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Updated context usage state
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
ValueError: If token counts are negative
|
|
120
|
+
"""
|
|
121
|
+
if any(
|
|
122
|
+
t < 0 for t in [input_tokens, output_tokens, cache_creation, cache_read]
|
|
123
|
+
):
|
|
124
|
+
raise ValueError("Token counts cannot be negative")
|
|
125
|
+
|
|
126
|
+
# Load current state
|
|
127
|
+
state = self._load_state()
|
|
128
|
+
|
|
129
|
+
# Update cumulative counters
|
|
130
|
+
state.cumulative_input_tokens += input_tokens
|
|
131
|
+
state.cumulative_output_tokens += output_tokens
|
|
132
|
+
state.cache_creation_tokens += cache_creation
|
|
133
|
+
state.cache_read_tokens += cache_read
|
|
134
|
+
|
|
135
|
+
# Calculate total effective tokens (input + output, cache read is "free")
|
|
136
|
+
total_tokens = state.cumulative_input_tokens + state.cumulative_output_tokens
|
|
137
|
+
state.percentage_used = (total_tokens / self.CONTEXT_BUDGET) * 100
|
|
138
|
+
|
|
139
|
+
# Check thresholds
|
|
140
|
+
state.threshold_reached = self.check_thresholds(state)
|
|
141
|
+
|
|
142
|
+
# Activate auto-pause if threshold reached
|
|
143
|
+
if state.threshold_reached in {"auto_pause", "critical"}:
|
|
144
|
+
state.auto_pause_active = True
|
|
145
|
+
|
|
146
|
+
# Update timestamp
|
|
147
|
+
state.last_updated = datetime.now(timezone.utc).isoformat()
|
|
148
|
+
|
|
149
|
+
# Persist state atomically
|
|
150
|
+
self._save_state(state)
|
|
151
|
+
|
|
152
|
+
logger.debug(
|
|
153
|
+
f"Usage updated: {total_tokens}/{self.CONTEXT_BUDGET} tokens "
|
|
154
|
+
f"({state.percentage_used:.1f}%), threshold: {state.threshold_reached}"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return state
|
|
158
|
+
|
|
159
|
+
def check_thresholds(
|
|
160
|
+
self, state: Optional[ContextUsageState] = None
|
|
161
|
+
) -> Optional[str]:
|
|
162
|
+
"""Check which threshold (if any) has been exceeded.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
state: Optional state to check (uses current state if None)
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Highest threshold exceeded ('caution', 'warning', 'auto_pause', 'critical')
|
|
169
|
+
or None if no thresholds exceeded
|
|
170
|
+
"""
|
|
171
|
+
if state is None:
|
|
172
|
+
state = self.get_current_state()
|
|
173
|
+
|
|
174
|
+
percentage = state.percentage_used / 100 # Convert to 0.0-1.0
|
|
175
|
+
|
|
176
|
+
# Check thresholds in descending order (highest first)
|
|
177
|
+
for threshold_name in ["critical", "auto_pause", "warning", "caution"]:
|
|
178
|
+
if percentage >= self.THRESHOLDS[threshold_name]:
|
|
179
|
+
return threshold_name
|
|
180
|
+
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
def should_auto_pause(self) -> bool:
|
|
184
|
+
"""Check if auto-pause should be triggered.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
True if 90%+ context budget used
|
|
188
|
+
"""
|
|
189
|
+
state = self.get_current_state()
|
|
190
|
+
return state.percentage_used >= (self.THRESHOLDS["auto_pause"] * 100)
|
|
191
|
+
|
|
192
|
+
def get_current_state(self) -> ContextUsageState:
|
|
193
|
+
"""Get current usage state without modifying.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Current context usage state
|
|
197
|
+
"""
|
|
198
|
+
return self._load_state()
|
|
199
|
+
|
|
200
|
+
def reset_session(self, new_session_id: str) -> None:
|
|
201
|
+
"""Reset tracking for a new session.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
new_session_id: New session identifier
|
|
205
|
+
"""
|
|
206
|
+
state = ContextUsageState(session_id=new_session_id)
|
|
207
|
+
self._save_state(state)
|
|
208
|
+
logger.info(f"Context usage reset for new session: {new_session_id}")
|
|
209
|
+
|
|
210
|
+
def _load_state(self) -> ContextUsageState:
|
|
211
|
+
"""Load state from persistence file.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Loaded state or default state if file doesn't exist/is corrupted
|
|
215
|
+
"""
|
|
216
|
+
try:
|
|
217
|
+
if not self.state_file.exists():
|
|
218
|
+
# Generate initial session ID
|
|
219
|
+
session_id = (
|
|
220
|
+
f"session-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}"
|
|
221
|
+
)
|
|
222
|
+
logger.debug("No state file found, creating default state")
|
|
223
|
+
return ContextUsageState(session_id=session_id)
|
|
224
|
+
|
|
225
|
+
# Load JSON state
|
|
226
|
+
data = self.storage.read_json(self.state_file)
|
|
227
|
+
|
|
228
|
+
if data is None:
|
|
229
|
+
logger.warning("Failed to read state file, using default state")
|
|
230
|
+
session_id = (
|
|
231
|
+
f"session-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}"
|
|
232
|
+
)
|
|
233
|
+
return ContextUsageState(session_id=session_id)
|
|
234
|
+
|
|
235
|
+
# Reconstruct ContextUsageState from dict
|
|
236
|
+
return ContextUsageState(**data)
|
|
237
|
+
|
|
238
|
+
except Exception as e:
|
|
239
|
+
logger.error(f"Error loading state, using default: {e}")
|
|
240
|
+
session_id = (
|
|
241
|
+
f"session-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}"
|
|
242
|
+
)
|
|
243
|
+
return ContextUsageState(session_id=session_id)
|
|
244
|
+
|
|
245
|
+
def _save_state(self, state: ContextUsageState) -> None:
|
|
246
|
+
"""Save state to persistence file atomically.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
state: Context usage state to persist
|
|
250
|
+
|
|
251
|
+
Raises:
|
|
252
|
+
RuntimeError: If atomic write fails
|
|
253
|
+
"""
|
|
254
|
+
try:
|
|
255
|
+
# Convert dataclass to dict
|
|
256
|
+
state_dict = asdict(state)
|
|
257
|
+
|
|
258
|
+
# Atomic write using StateStorage
|
|
259
|
+
if not self.storage.write_json(state_dict, self.state_file, atomic=True):
|
|
260
|
+
raise RuntimeError(f"Failed to write state to {self.state_file}")
|
|
261
|
+
|
|
262
|
+
logger.debug(f"State saved: {self.state_file}")
|
|
263
|
+
|
|
264
|
+
except Exception as e:
|
|
265
|
+
logger.error(f"Error saving state: {e}")
|
|
266
|
+
raise RuntimeError(f"Failed to persist context usage state: {e}") from e
|
|
267
|
+
|
|
268
|
+
def get_usage_summary(self) -> dict:
|
|
269
|
+
"""Get human-readable usage summary.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Dictionary with usage statistics
|
|
273
|
+
"""
|
|
274
|
+
state = self.get_current_state()
|
|
275
|
+
total_tokens = state.cumulative_input_tokens + state.cumulative_output_tokens
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
"session_id": state.session_id,
|
|
279
|
+
"total_tokens": total_tokens,
|
|
280
|
+
"budget": self.CONTEXT_BUDGET,
|
|
281
|
+
"percentage_used": round(state.percentage_used, 2),
|
|
282
|
+
"threshold_reached": state.threshold_reached,
|
|
283
|
+
"auto_pause_active": state.auto_pause_active,
|
|
284
|
+
"breakdown": {
|
|
285
|
+
"input_tokens": state.cumulative_input_tokens,
|
|
286
|
+
"output_tokens": state.cumulative_output_tokens,
|
|
287
|
+
"cache_creation_tokens": state.cache_creation_tokens,
|
|
288
|
+
"cache_read_tokens": state.cache_read_tokens,
|
|
289
|
+
},
|
|
290
|
+
"last_updated": state.last_updated,
|
|
291
|
+
}
|
|
@@ -6,7 +6,8 @@ Integrates with session management and response tracking infrastructure.
|
|
|
6
6
|
Triggers:
|
|
7
7
|
- model_context_window_exceeded (stop_reason)
|
|
8
8
|
- Manual pause command
|
|
9
|
-
- 95% token threshold reached
|
|
9
|
+
- 95% token threshold reached (critical)
|
|
10
|
+
- 90% token threshold reached (auto-pause)
|
|
10
11
|
- Session end with high token usage (>85%)
|
|
11
12
|
|
|
12
13
|
Design Principles:
|
|
@@ -71,6 +72,7 @@ class ResumeLogGenerator:
|
|
|
71
72
|
thresholds = self.config.get("context_management", {}).get("thresholds", {})
|
|
72
73
|
self.threshold_caution = thresholds.get("caution", 0.70)
|
|
73
74
|
self.threshold_warning = thresholds.get("warning", 0.85)
|
|
75
|
+
self.threshold_auto_pause = thresholds.get("auto_pause", 0.90)
|
|
74
76
|
self.threshold_critical = thresholds.get("critical", 0.95)
|
|
75
77
|
|
|
76
78
|
logger.info(
|
|
@@ -96,14 +98,14 @@ class ResumeLogGenerator:
|
|
|
96
98
|
if not self.enabled or not self.auto_generate:
|
|
97
99
|
return manual_trigger # Only generate on manual trigger if auto is disabled
|
|
98
100
|
|
|
99
|
-
# Trigger conditions
|
|
101
|
+
# Trigger conditions (ordered by severity)
|
|
100
102
|
triggers = [
|
|
101
103
|
stop_reason == "max_tokens",
|
|
102
104
|
stop_reason == "model_context_window_exceeded",
|
|
103
105
|
manual_trigger,
|
|
104
|
-
token_usage_pct and token_usage_pct >= self.threshold_critical,
|
|
105
|
-
token_usage_pct
|
|
106
|
-
and token_usage_pct >= self.threshold_warning, #
|
|
106
|
+
token_usage_pct and token_usage_pct >= self.threshold_critical, # 95%
|
|
107
|
+
token_usage_pct and token_usage_pct >= self.threshold_auto_pause, # 90%
|
|
108
|
+
token_usage_pct and token_usage_pct >= self.threshold_warning, # 85%
|
|
107
109
|
]
|
|
108
110
|
|
|
109
111
|
should_gen = any(triggers)
|
|
@@ -121,6 +123,22 @@ class ResumeLogGenerator:
|
|
|
121
123
|
|
|
122
124
|
return should_gen
|
|
123
125
|
|
|
126
|
+
def should_auto_pause(self, token_usage_pct: Optional[float]) -> bool:
|
|
127
|
+
"""Check if auto-pause threshold (90%) has been reached.
|
|
128
|
+
|
|
129
|
+
This is a convenience method to check specifically for the 90% threshold
|
|
130
|
+
which triggers automatic session pausing.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
token_usage_pct: Current token usage percentage (0.0-1.0)
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
True if auto-pause threshold has been reached
|
|
137
|
+
"""
|
|
138
|
+
if token_usage_pct is None:
|
|
139
|
+
return False
|
|
140
|
+
return token_usage_pct >= self.threshold_auto_pause
|
|
141
|
+
|
|
124
142
|
def generate_from_session_state(
|
|
125
143
|
self,
|
|
126
144
|
session_id: str,
|
|
@@ -427,6 +445,7 @@ class ResumeLogGenerator:
|
|
|
427
445
|
"thresholds": {
|
|
428
446
|
"caution": f"{self.threshold_caution:.0%}",
|
|
429
447
|
"warning": f"{self.threshold_warning:.0%}",
|
|
448
|
+
"auto_pause": f"{self.threshold_auto_pause:.0%}",
|
|
430
449
|
"critical": f"{self.threshold_critical:.0%}",
|
|
431
450
|
},
|
|
432
451
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-mpm
|
|
3
|
-
Version: 5.4.
|
|
3
|
+
Version: 5.4.87
|
|
4
4
|
Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
|
|
5
5
|
Author-email: Bob Matsuoka <bob@matsuoka.com>
|
|
6
6
|
Maintainer: Claude MPM Team
|
|
@@ -118,6 +118,12 @@ A powerful orchestration framework for **Claude Code (CLI)** that enables multi-
|
|
|
118
118
|
|
|
119
119
|
> **Quick Start**: See [docs/user/getting-started.md](docs/getting-started/README.md) to get running in 5 minutes!
|
|
120
120
|
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
> **🆕 NEW for Founders!** Not a software engineer? **[Start with our Absolute Beginner's Guide](docs/absolute-beginners-guide.md)** — designed specifically for non-technical founders and business leaders who want to understand and oversee their technical projects. No coding experience required!
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
121
127
|
## 🚀 What's New in v5.0
|
|
122
128
|
|
|
123
129
|
### Git Repository Integration for Agents & Skills
|
|
@@ -234,7 +240,7 @@ See [docs/features/hierarchical-base-agents.md](docs/features/hierarchical-base-
|
|
|
234
240
|
|
|
235
241
|
**Before installing Claude MPM**, ensure you have:
|
|
236
242
|
|
|
237
|
-
1. **Python 3.
|
|
243
|
+
1. **Python 3.10+** (3.11+ recommended for optimal compatibility)
|
|
238
244
|
2. **Claude Code CLI v1.0.92+** (required!)
|
|
239
245
|
|
|
240
246
|
```bash
|
|
@@ -247,19 +253,18 @@ claude --version
|
|
|
247
253
|
|
|
248
254
|
### Install Claude MPM
|
|
249
255
|
|
|
256
|
+
Choose your preferred installation method:
|
|
257
|
+
|
|
258
|
+
**Homebrew (macOS):**
|
|
250
259
|
```bash
|
|
251
260
|
# Basic installation
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
# Or with uv (faster)
|
|
255
|
-
uv tool install claude-mpm
|
|
261
|
+
brew install claude-mpm
|
|
256
262
|
|
|
257
263
|
# Install with monitoring dashboard (recommended)
|
|
258
|
-
|
|
259
|
-
uv tool install "claude-mpm[monitor]"
|
|
264
|
+
brew install claude-mpm --with-monitor
|
|
260
265
|
```
|
|
261
266
|
|
|
262
|
-
|
|
267
|
+
**pipx/uvx (recommended for isolated installation):**
|
|
263
268
|
```bash
|
|
264
269
|
# Basic installation
|
|
265
270
|
pipx install claude-mpm
|
|
@@ -275,6 +280,19 @@ pipx install "claude-mpm[monitor]"
|
|
|
275
280
|
uv tool install "claude-mpm[monitor]"
|
|
276
281
|
```
|
|
277
282
|
|
|
283
|
+
**pip/uv (system-wide installation):**
|
|
284
|
+
```bash
|
|
285
|
+
# Basic installation
|
|
286
|
+
pip install claude-mpm
|
|
287
|
+
|
|
288
|
+
# Or with uv (faster)
|
|
289
|
+
uv tool install claude-mpm
|
|
290
|
+
|
|
291
|
+
# Install with monitoring dashboard (recommended)
|
|
292
|
+
pip install "claude-mpm[monitor]"
|
|
293
|
+
uv tool install "claude-mpm[monitor]"
|
|
294
|
+
```
|
|
295
|
+
|
|
278
296
|
### Verify Installation
|
|
279
297
|
|
|
280
298
|
```bash
|