htmlgraph 0.20.1__py3-none-any.whl → 0.27.5__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.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +51 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/__init__.py +8 -1
- htmlgraph/analytics/cli.py +86 -20
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +67 -27
- htmlgraph/analytics_index.py +53 -20
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/base.py +57 -2
- htmlgraph/builders/bug.py +19 -3
- htmlgraph/builders/chore.py +19 -3
- htmlgraph/builders/epic.py +19 -3
- htmlgraph/builders/feature.py +27 -3
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +19 -3
- htmlgraph/builders/spike.py +29 -3
- htmlgraph/builders/track.py +42 -1
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +2 -0
- htmlgraph/collections/base.py +197 -14
- htmlgraph/collections/bug.py +2 -1
- htmlgraph/collections/chore.py +2 -1
- htmlgraph/collections/epic.py +2 -1
- htmlgraph/collections/feature.py +2 -1
- htmlgraph/collections/insight.py +2 -1
- htmlgraph/collections/metric.py +2 -1
- htmlgraph/collections/pattern.py +2 -1
- htmlgraph/collections/phase.py +2 -1
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +13 -2
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +14 -1
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/converter.py +116 -7
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2246 -248
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/deploy.py +26 -27
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +2 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +86 -37
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +67 -9
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +8 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +790 -99
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +327 -76
- htmlgraph/hooks/orchestrator_reflector.py +31 -4
- htmlgraph/hooks/post_tool_use_failure.py +32 -7
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +92 -19
- htmlgraph/hooks/pretooluse.py +527 -7
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +99 -4
- htmlgraph/hooks/validator.py +212 -91
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +125 -100
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/models.py +217 -18
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +115 -4
- htmlgraph/parallel.py +2 -1
- htmlgraph/parser.py +86 -6
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +2 -1
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +295 -107
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +285 -3
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +33 -1
- htmlgraph/track_manager.py +38 -0
- htmlgraph/transcript.py +18 -5
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -4839
- htmlgraph/sdk.py +0 -2359
- htmlgraph-0.20.1.dist-info/RECORD +0 -118
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SessionStateManager - Automatic session state and environment variable management.
|
|
3
|
+
|
|
4
|
+
Provides automatic detection and management of:
|
|
5
|
+
- Current session state (ID, source, compaction status)
|
|
6
|
+
- Environment variable setup (CLAUDE_SESSION_ID, CLAUDE_SESSION_SOURCE, etc.)
|
|
7
|
+
- Delegation status determination
|
|
8
|
+
- Session metadata recording
|
|
9
|
+
|
|
10
|
+
Integration with SessionStart hook:
|
|
11
|
+
from htmlgraph import SDK
|
|
12
|
+
|
|
13
|
+
sdk = SDK()
|
|
14
|
+
state = sdk.sessions.get_current_state()
|
|
15
|
+
sdk.sessions.setup_environment_variables(state)
|
|
16
|
+
|
|
17
|
+
# All environment variables now set automatically
|
|
18
|
+
# Delegation status available via state['delegation_enabled']
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
import os
|
|
24
|
+
import subprocess
|
|
25
|
+
from datetime import datetime, timezone
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Any, TypedDict
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SessionState(TypedDict, total=False):
|
|
33
|
+
"""Current session state information."""
|
|
34
|
+
|
|
35
|
+
session_id: str
|
|
36
|
+
session_source: str # "startup", "resume", "compact", "clear"
|
|
37
|
+
is_post_compact: bool
|
|
38
|
+
previous_session_id: str | None
|
|
39
|
+
delegation_enabled: bool
|
|
40
|
+
prompt_injected: bool
|
|
41
|
+
session_valid: bool
|
|
42
|
+
timestamp: str
|
|
43
|
+
compact_metadata: dict[str, Any]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SessionStateManager:
|
|
47
|
+
"""
|
|
48
|
+
Manages session state detection and environment variable setup.
|
|
49
|
+
|
|
50
|
+
Automatically detects:
|
|
51
|
+
- Current session ID and source
|
|
52
|
+
- Post-compact state
|
|
53
|
+
- Delegation status
|
|
54
|
+
- Session validity
|
|
55
|
+
|
|
56
|
+
Provides environment variable management:
|
|
57
|
+
- CLAUDE_SESSION_ID
|
|
58
|
+
- CLAUDE_SESSION_SOURCE
|
|
59
|
+
- CLAUDE_SESSION_COMPACTED
|
|
60
|
+
- CLAUDE_DELEGATION_ENABLED
|
|
61
|
+
- CLAUDE_ORCHESTRATOR_ACTIVE
|
|
62
|
+
- CLAUDE_PROMPT_PERSISTENCE_VERSION
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
# Session state metadata file
|
|
66
|
+
CURRENT_SESSION_FILE = "current.json"
|
|
67
|
+
SESSION_STATE_FILE = "session_state.json"
|
|
68
|
+
COMPACT_MARKER_FILE = ".compacted"
|
|
69
|
+
|
|
70
|
+
def __init__(self, graph_dir: str | Path):
|
|
71
|
+
"""
|
|
72
|
+
Initialize SessionStateManager.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
graph_dir: Path to .htmlgraph directory
|
|
76
|
+
"""
|
|
77
|
+
self.graph_dir = Path(graph_dir)
|
|
78
|
+
self.sessions_dir = self.graph_dir / "sessions"
|
|
79
|
+
self.sessions_dir.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
|
|
81
|
+
def get_current_state(self) -> SessionState:
|
|
82
|
+
"""
|
|
83
|
+
Get current session state with automatic detection.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
SessionState dict with:
|
|
87
|
+
- session_id: Current session identifier
|
|
88
|
+
- session_source: "startup", "resume", "compact", "clear"
|
|
89
|
+
- is_post_compact: True if this is post-compact
|
|
90
|
+
- previous_session_id: Previous session ID if available
|
|
91
|
+
- delegation_enabled: Should delegation be active
|
|
92
|
+
- prompt_injected: Was orchestrator prompt injected
|
|
93
|
+
- session_valid: Is session valid for work
|
|
94
|
+
- timestamp: Current UTC timestamp
|
|
95
|
+
- compact_metadata: Compact detection details
|
|
96
|
+
"""
|
|
97
|
+
# Get current session ID from environment or generate
|
|
98
|
+
session_id = os.environ.get("CLAUDE_SESSION_ID")
|
|
99
|
+
if not session_id:
|
|
100
|
+
session_id = self._generate_session_id()
|
|
101
|
+
|
|
102
|
+
# Load previous state
|
|
103
|
+
prev_state = self._load_previous_state()
|
|
104
|
+
|
|
105
|
+
# Detect session source and compaction
|
|
106
|
+
source, is_post_compact, compact_metadata = self._detect_session_source(
|
|
107
|
+
session_id, prev_state
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Determine delegation status
|
|
111
|
+
delegation_enabled = self._should_enable_delegation(is_post_compact, prev_state)
|
|
112
|
+
|
|
113
|
+
# Check if session is valid
|
|
114
|
+
session_valid = self._is_session_valid(session_id, prev_state)
|
|
115
|
+
|
|
116
|
+
# Get previous session ID
|
|
117
|
+
previous_session_id = prev_state.get("session_id") if prev_state else None
|
|
118
|
+
|
|
119
|
+
# Check if orchestrator prompt was injected
|
|
120
|
+
prompt_injected = os.environ.get("CLAUDE_ORCHESTRATOR_ACTIVE") == "true"
|
|
121
|
+
|
|
122
|
+
state: SessionState = {
|
|
123
|
+
"session_id": session_id,
|
|
124
|
+
"session_source": source,
|
|
125
|
+
"is_post_compact": is_post_compact,
|
|
126
|
+
"previous_session_id": previous_session_id,
|
|
127
|
+
"delegation_enabled": delegation_enabled,
|
|
128
|
+
"prompt_injected": prompt_injected,
|
|
129
|
+
"session_valid": session_valid,
|
|
130
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
131
|
+
"compact_metadata": compact_metadata,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return state
|
|
135
|
+
|
|
136
|
+
def setup_environment_variables(
|
|
137
|
+
self,
|
|
138
|
+
session_state: SessionState | None = None,
|
|
139
|
+
auto_detect_compact: bool = True,
|
|
140
|
+
) -> dict[str, str]:
|
|
141
|
+
"""
|
|
142
|
+
Automatically set up environment variables for session state.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
session_state: Session state dict (auto-detected if not provided)
|
|
146
|
+
auto_detect_compact: Whether to auto-detect post-compact state
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Dict of environment variables that were set
|
|
150
|
+
|
|
151
|
+
Sets the following environment variables:
|
|
152
|
+
- CLAUDE_SESSION_ID: Current session identifier
|
|
153
|
+
- CLAUDE_SESSION_SOURCE: "startup|resume|compact|clear"
|
|
154
|
+
- CLAUDE_SESSION_COMPACTED: "true|false"
|
|
155
|
+
- CLAUDE_DELEGATION_ENABLED: "true|false"
|
|
156
|
+
- CLAUDE_PREVIOUS_SESSION_ID: Previous session ID
|
|
157
|
+
- CLAUDE_ORCHESTRATOR_ACTIVE: "true|false"
|
|
158
|
+
- CLAUDE_PROMPT_PERSISTENCE_VERSION: "1.0"
|
|
159
|
+
"""
|
|
160
|
+
if session_state is None:
|
|
161
|
+
session_state = self.get_current_state()
|
|
162
|
+
|
|
163
|
+
env_vars = {}
|
|
164
|
+
|
|
165
|
+
# CLAUDE_SESSION_ID - always set
|
|
166
|
+
session_id = session_state.get("session_id", "unknown")
|
|
167
|
+
os.environ["CLAUDE_SESSION_ID"] = session_id
|
|
168
|
+
env_vars["CLAUDE_SESSION_ID"] = session_id
|
|
169
|
+
|
|
170
|
+
# CLAUDE_SESSION_SOURCE - session type detection
|
|
171
|
+
source = session_state.get("session_source", "startup")
|
|
172
|
+
os.environ["CLAUDE_SESSION_SOURCE"] = source
|
|
173
|
+
env_vars["CLAUDE_SESSION_SOURCE"] = source
|
|
174
|
+
|
|
175
|
+
# CLAUDE_SESSION_COMPACTED - post-compact detection
|
|
176
|
+
is_post_compact = session_state.get("is_post_compact", False)
|
|
177
|
+
os.environ["CLAUDE_SESSION_COMPACTED"] = str(is_post_compact).lower()
|
|
178
|
+
env_vars["CLAUDE_SESSION_COMPACTED"] = str(is_post_compact).lower()
|
|
179
|
+
|
|
180
|
+
# CLAUDE_DELEGATION_ENABLED - orchestrator delegation
|
|
181
|
+
delegation_enabled = session_state.get("delegation_enabled", False)
|
|
182
|
+
os.environ["CLAUDE_DELEGATION_ENABLED"] = str(delegation_enabled).lower()
|
|
183
|
+
env_vars["CLAUDE_DELEGATION_ENABLED"] = str(delegation_enabled).lower()
|
|
184
|
+
|
|
185
|
+
# CLAUDE_PREVIOUS_SESSION_ID - for tracking continuity
|
|
186
|
+
prev_session = session_state.get("previous_session_id")
|
|
187
|
+
if prev_session:
|
|
188
|
+
os.environ["CLAUDE_PREVIOUS_SESSION_ID"] = prev_session
|
|
189
|
+
env_vars["CLAUDE_PREVIOUS_SESSION_ID"] = prev_session
|
|
190
|
+
|
|
191
|
+
# CLAUDE_ORCHESTRATOR_ACTIVE - skill activation
|
|
192
|
+
orchestrator_active = session_state.get("delegation_enabled", False)
|
|
193
|
+
os.environ["CLAUDE_ORCHESTRATOR_ACTIVE"] = str(orchestrator_active).lower()
|
|
194
|
+
env_vars["CLAUDE_ORCHESTRATOR_ACTIVE"] = str(orchestrator_active).lower()
|
|
195
|
+
|
|
196
|
+
# CLAUDE_PROMPT_PERSISTENCE_VERSION - version management
|
|
197
|
+
os.environ["CLAUDE_PROMPT_PERSISTENCE_VERSION"] = "1.0"
|
|
198
|
+
env_vars["CLAUDE_PROMPT_PERSISTENCE_VERSION"] = "1.0"
|
|
199
|
+
|
|
200
|
+
# Record state metadata
|
|
201
|
+
self.record_state(
|
|
202
|
+
session_id=session_id,
|
|
203
|
+
source=source,
|
|
204
|
+
is_post_compact=is_post_compact,
|
|
205
|
+
delegation_enabled=delegation_enabled,
|
|
206
|
+
environment_vars=env_vars,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return env_vars
|
|
210
|
+
|
|
211
|
+
def record_state(
|
|
212
|
+
self,
|
|
213
|
+
session_id: str,
|
|
214
|
+
source: str,
|
|
215
|
+
is_post_compact: bool,
|
|
216
|
+
delegation_enabled: bool,
|
|
217
|
+
environment_vars: dict[str, str] | None = None,
|
|
218
|
+
) -> None:
|
|
219
|
+
"""
|
|
220
|
+
Store session state metadata for future reference.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
session_id: Current session ID
|
|
224
|
+
source: Session source ("startup", "resume", "compact", "clear")
|
|
225
|
+
is_post_compact: Whether this is post-compact
|
|
226
|
+
delegation_enabled: Whether delegation is enabled
|
|
227
|
+
environment_vars: Environment variables that were set
|
|
228
|
+
"""
|
|
229
|
+
state_file = self.sessions_dir / self.SESSION_STATE_FILE
|
|
230
|
+
|
|
231
|
+
state_data = {
|
|
232
|
+
"session_id": session_id,
|
|
233
|
+
"source": source,
|
|
234
|
+
"is_post_compact": is_post_compact,
|
|
235
|
+
"delegation_enabled": delegation_enabled,
|
|
236
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
237
|
+
"environment_vars": environment_vars or {},
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
state_file.write_text(json.dumps(state_data, indent=2))
|
|
242
|
+
except Exception as e:
|
|
243
|
+
logger.warning(f"Could not record session state: {e}")
|
|
244
|
+
|
|
245
|
+
def detect_compact_automatically(self) -> bool:
|
|
246
|
+
"""
|
|
247
|
+
Auto-detect if this is post-compact by comparing session IDs.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
True if this is a post-compact session
|
|
251
|
+
"""
|
|
252
|
+
current_id = os.environ.get("CLAUDE_SESSION_ID")
|
|
253
|
+
prev_state = self._load_previous_state()
|
|
254
|
+
|
|
255
|
+
if not current_id or not prev_state:
|
|
256
|
+
return False
|
|
257
|
+
|
|
258
|
+
prev_id = prev_state.get("session_id")
|
|
259
|
+
if current_id == prev_id:
|
|
260
|
+
return False # Same session, not post-compact
|
|
261
|
+
|
|
262
|
+
# Check if previous session was ended gracefully (SessionEnd hook called)
|
|
263
|
+
# This is a heuristic: if previous session exists and is marked as "ended",
|
|
264
|
+
# then this new session is likely post-compact
|
|
265
|
+
prev_session_ended = bool(prev_state.get("is_ended", False))
|
|
266
|
+
return prev_session_ended
|
|
267
|
+
|
|
268
|
+
# ========================================================================
|
|
269
|
+
# Private Methods
|
|
270
|
+
# ========================================================================
|
|
271
|
+
|
|
272
|
+
def _generate_session_id(self) -> str:
|
|
273
|
+
"""Generate a stable session ID."""
|
|
274
|
+
from htmlgraph.ids import generate_id
|
|
275
|
+
|
|
276
|
+
return generate_id("session", "auto")
|
|
277
|
+
|
|
278
|
+
def _load_previous_state(self) -> dict[str, Any] | None:
|
|
279
|
+
"""Load previous session state from file."""
|
|
280
|
+
state_file = self.sessions_dir / self.SESSION_STATE_FILE
|
|
281
|
+
|
|
282
|
+
if not state_file.exists():
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
data = json.loads(state_file.read_text())
|
|
287
|
+
assert isinstance(data, dict)
|
|
288
|
+
return data
|
|
289
|
+
except Exception as e:
|
|
290
|
+
logger.debug(f"Could not load previous state: {e}")
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
def _detect_session_source(
|
|
294
|
+
self, current_id: str, prev_state: dict[str, Any] | None
|
|
295
|
+
) -> tuple[str, bool, dict[str, Any]]:
|
|
296
|
+
"""
|
|
297
|
+
Detect session source and compaction status.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
(source, is_post_compact, metadata)
|
|
301
|
+
where source is one of: "startup", "resume", "compact", "clear"
|
|
302
|
+
"""
|
|
303
|
+
metadata: dict[str, Any] = {}
|
|
304
|
+
|
|
305
|
+
# No previous state = startup or fresh session
|
|
306
|
+
if not prev_state:
|
|
307
|
+
return "startup", False, metadata
|
|
308
|
+
|
|
309
|
+
prev_id = prev_state.get("session_id")
|
|
310
|
+
|
|
311
|
+
# Same session ID = resume (context switch within same Claude session)
|
|
312
|
+
if current_id == prev_id:
|
|
313
|
+
metadata["reason"] = "same_session_id"
|
|
314
|
+
return "resume", False, metadata
|
|
315
|
+
|
|
316
|
+
# Different session ID - check if previous was ended
|
|
317
|
+
prev_was_ended = prev_state.get("is_ended", False)
|
|
318
|
+
|
|
319
|
+
# Check for compact marker file
|
|
320
|
+
compact_marker = self.sessions_dir / self.COMPACT_MARKER_FILE
|
|
321
|
+
has_compact_marker = compact_marker.exists()
|
|
322
|
+
|
|
323
|
+
if has_compact_marker or prev_was_ended:
|
|
324
|
+
# This is post-compact
|
|
325
|
+
metadata["reason"] = "previous_session_ended"
|
|
326
|
+
metadata["previous_session_id"] = prev_id
|
|
327
|
+
metadata["had_compact_marker"] = has_compact_marker
|
|
328
|
+
return "compact", True, metadata
|
|
329
|
+
|
|
330
|
+
# Check if this looks like a /clear command
|
|
331
|
+
if self._detect_clear_command():
|
|
332
|
+
metadata["reason"] = "clear_command_detected"
|
|
333
|
+
return "clear", False, metadata
|
|
334
|
+
|
|
335
|
+
# Different session ID but previous wasn't ended - likely resume after context switch
|
|
336
|
+
metadata["reason"] = "different_session_id_no_end"
|
|
337
|
+
return "resume", False, metadata
|
|
338
|
+
|
|
339
|
+
def _should_enable_delegation(
|
|
340
|
+
self, is_post_compact: bool, prev_state: dict[str, Any] | None
|
|
341
|
+
) -> bool:
|
|
342
|
+
"""
|
|
343
|
+
Determine if delegation should be enabled.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
True if delegation should be active in this session
|
|
347
|
+
"""
|
|
348
|
+
# Check environment variable override
|
|
349
|
+
if os.environ.get("HTMLGRAPH_DELEGATION_DISABLE") == "1":
|
|
350
|
+
return False
|
|
351
|
+
|
|
352
|
+
# Enable delegation if:
|
|
353
|
+
# 1. This is post-compact (context carry-over needed)
|
|
354
|
+
# 2. Previous session had delegation enabled
|
|
355
|
+
# 3. Features are available to work on
|
|
356
|
+
if is_post_compact:
|
|
357
|
+
return True
|
|
358
|
+
|
|
359
|
+
if prev_state:
|
|
360
|
+
if prev_state.get("delegation_enabled", False):
|
|
361
|
+
return True
|
|
362
|
+
|
|
363
|
+
# Check if there are features available
|
|
364
|
+
if self._has_available_work():
|
|
365
|
+
return True
|
|
366
|
+
|
|
367
|
+
return False
|
|
368
|
+
|
|
369
|
+
def _is_session_valid(
|
|
370
|
+
self, session_id: str, prev_state: dict[str, Any] | None
|
|
371
|
+
) -> bool:
|
|
372
|
+
"""
|
|
373
|
+
Check if session is valid for work tracking.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
True if this is a valid session for work
|
|
377
|
+
"""
|
|
378
|
+
# Check if session directory exists
|
|
379
|
+
sessions_dir = self.graph_dir / "sessions"
|
|
380
|
+
if not sessions_dir.exists():
|
|
381
|
+
return True # Valid - directory will be created
|
|
382
|
+
|
|
383
|
+
# Check if we can write to sessions directory
|
|
384
|
+
try:
|
|
385
|
+
test_file = sessions_dir / ".test"
|
|
386
|
+
test_file.touch()
|
|
387
|
+
test_file.unlink()
|
|
388
|
+
return True
|
|
389
|
+
except Exception:
|
|
390
|
+
logger.warning("Cannot write to sessions directory")
|
|
391
|
+
return False
|
|
392
|
+
|
|
393
|
+
def _detect_clear_command(self) -> bool:
|
|
394
|
+
"""
|
|
395
|
+
Detect if /clear command was run (clears .htmlgraph state).
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
True if /clear was likely run
|
|
399
|
+
"""
|
|
400
|
+
# Check if clear marker exists
|
|
401
|
+
clear_marker = self.graph_dir / ".clear_marker"
|
|
402
|
+
return clear_marker.exists()
|
|
403
|
+
|
|
404
|
+
def _has_available_work(self) -> bool:
|
|
405
|
+
"""
|
|
406
|
+
Check if there are features available to work on.
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
True if there are features in todo or in-progress state
|
|
410
|
+
"""
|
|
411
|
+
features_dir = self.graph_dir / "features"
|
|
412
|
+
if not features_dir.exists():
|
|
413
|
+
return False
|
|
414
|
+
|
|
415
|
+
try:
|
|
416
|
+
html_files = list(features_dir.glob("*.html"))
|
|
417
|
+
return len(html_files) > 0
|
|
418
|
+
except Exception:
|
|
419
|
+
return False
|
|
420
|
+
|
|
421
|
+
@staticmethod
|
|
422
|
+
def _get_git_status() -> dict[str, Any]:
|
|
423
|
+
"""Get current git status (for compaction detection)."""
|
|
424
|
+
try:
|
|
425
|
+
result = subprocess.run(
|
|
426
|
+
["git", "status", "--porcelain"],
|
|
427
|
+
capture_output=True,
|
|
428
|
+
text=True,
|
|
429
|
+
timeout=5,
|
|
430
|
+
)
|
|
431
|
+
return {
|
|
432
|
+
"has_changes": bool(result.stdout.strip()),
|
|
433
|
+
"status_output": result.stdout.strip(),
|
|
434
|
+
}
|
|
435
|
+
except Exception:
|
|
436
|
+
return {"has_changes": False, "status_output": ""}
|
htmlgraph/session_warning.py
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session management and continuity.
|
|
3
|
+
|
|
4
|
+
Provides session lifecycle, handoff, and resumption features.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from htmlgraph.sessions.handoff import (
|
|
8
|
+
ContextRecommender,
|
|
9
|
+
HandoffBuilder,
|
|
10
|
+
HandoffMetrics,
|
|
11
|
+
HandoffTracker,
|
|
12
|
+
SessionResume,
|
|
13
|
+
SessionResumeInfo,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"HandoffBuilder",
|
|
18
|
+
"SessionResume",
|
|
19
|
+
"SessionResumeInfo",
|
|
20
|
+
"HandoffTracker",
|
|
21
|
+
"HandoffMetrics",
|
|
22
|
+
"ContextRecommender",
|
|
23
|
+
]
|