htmlgraph 0.9.3__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 +173 -17
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +127 -0
- htmlgraph/agent_registry.py +45 -30
- htmlgraph/agents.py +160 -107
- htmlgraph/analytics/__init__.py +9 -2
- htmlgraph/analytics/cli.py +190 -51
- 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 +192 -100
- 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 +190 -14
- htmlgraph/analytics_index.py +135 -51
- 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 +208 -0
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/__init__.py +14 -0
- htmlgraph/builders/base.py +118 -29
- htmlgraph/builders/bug.py +150 -0
- htmlgraph/builders/chore.py +119 -0
- htmlgraph/builders/epic.py +150 -0
- htmlgraph/builders/feature.py +31 -6
- htmlgraph/builders/insight.py +195 -0
- htmlgraph/builders/metric.py +217 -0
- htmlgraph/builders/pattern.py +202 -0
- htmlgraph/builders/phase.py +162 -0
- htmlgraph/builders/spike.py +52 -19
- htmlgraph/builders/track.py +148 -72
- 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 +18 -0
- htmlgraph/collections/base.py +415 -98
- htmlgraph/collections/bug.py +53 -0
- htmlgraph/collections/chore.py +53 -0
- htmlgraph/collections/epic.py +53 -0
- htmlgraph/collections/feature.py +12 -26
- htmlgraph/collections/insight.py +100 -0
- htmlgraph/collections/metric.py +92 -0
- htmlgraph/collections/pattern.py +97 -0
- htmlgraph/collections/phase.py +53 -0
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +56 -16
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +511 -0
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +344 -0
- htmlgraph/converter.py +216 -28
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2406 -307
- 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 +19 -2
- htmlgraph/deploy.py +142 -125
- htmlgraph/deployment_models.py +474 -0
- 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 +182 -27
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +100 -52
- htmlgraph/event_migration.py +13 -4
- htmlgraph/exceptions.py +49 -0
- htmlgraph/file_watcher.py +101 -28
- htmlgraph/find_api.py +75 -63
- htmlgraph/git_events.py +145 -63
- htmlgraph/graph.py +1122 -106
- 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 +45 -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 +1314 -0
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/hooks-config.example.json +12 -0
- htmlgraph/hooks/installer.py +343 -0
- htmlgraph/hooks/orchestrator.py +674 -0
- htmlgraph/hooks/orchestrator_reflector.py +223 -0
- htmlgraph/hooks/post-checkout.sh +28 -0
- htmlgraph/hooks/post-commit.sh +24 -0
- htmlgraph/hooks/post-merge.sh +26 -0
- htmlgraph/hooks/post_tool_use_failure.py +273 -0
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +408 -0
- htmlgraph/hooks/pre-commit.sh +94 -0
- htmlgraph/hooks/pre-push.sh +28 -0
- htmlgraph/hooks/pretooluse.py +819 -0
- 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 +255 -0
- htmlgraph/hooks/task_validator.py +177 -0
- htmlgraph/hooks/validator.py +628 -0
- htmlgraph/ids.py +41 -27
- htmlgraph/index.d.ts +286 -0
- htmlgraph/learning.py +767 -0
- htmlgraph/mcp_server.py +69 -23
- htmlgraph/models.py +1586 -87
- 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/task_coordination.py +343 -0
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +669 -0
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +328 -0
- htmlgraph/orchestrator_validator.py +133 -0
- htmlgraph/parallel.py +646 -0
- htmlgraph/parser.py +160 -35
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/planning.py +147 -52
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +109 -72
- 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/routing.py +8 -19
- htmlgraph/scripts/deploy.py +1 -2
- 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 +685 -180
- htmlgraph/services/__init__.py +10 -0
- htmlgraph/services/claiming.py +199 -0
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +1392 -175
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +201 -0
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/setup.py +34 -17
- htmlgraph/spike_index.py +143 -0
- htmlgraph/sync_docs.py +12 -15
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph/templates/GEMINI.md.template +87 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +146 -15
- htmlgraph/track_manager.py +69 -21
- htmlgraph/transcript.py +890 -0
- htmlgraph/transcript_analytics.py +699 -0
- htmlgraph/types.py +323 -0
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +8 -5
- htmlgraph/work_type_utils.py +3 -2
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
- htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -2688
- htmlgraph/sdk.py +0 -709
- htmlgraph-0.9.3.dist-info/RECORD +0 -61
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.9.3.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": ""}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Session Warning System for AI Agents.
|
|
5
|
+
|
|
6
|
+
Provides a mechanism to show critical instructions to AI agents at session start,
|
|
7
|
+
working around Claude Code's SessionStart hook bug (#10373) where additionalContext
|
|
8
|
+
is not injected for new conversations.
|
|
9
|
+
|
|
10
|
+
The warning:
|
|
11
|
+
1. Shows on every new session (first SDK usage)
|
|
12
|
+
2. Contains orchestrator instructions
|
|
13
|
+
3. Requires explicit dismissal (confirming agent read it)
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
from htmlgraph import SDK
|
|
17
|
+
|
|
18
|
+
sdk = SDK(agent="claude")
|
|
19
|
+
# Warning automatically shown if not dismissed
|
|
20
|
+
|
|
21
|
+
# Agent dismisses after reading (as first action)
|
|
22
|
+
sdk.dismiss_session_warning()
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
import json
|
|
27
|
+
import sys
|
|
28
|
+
from datetime import datetime
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
# The orchestrator instructions that agents MUST see
|
|
33
|
+
ORCHESTRATOR_WARNING = """
|
|
34
|
+
╔══════════════════════════════════════════════════════════════════════════════╗
|
|
35
|
+
║ HTMLGRAPH ORCHESTRATOR MODE ║
|
|
36
|
+
╠══════════════════════════════════════════════════════════════════════════════╣
|
|
37
|
+
║ ║
|
|
38
|
+
║ YOU ARE THE ORCHESTRATOR. Follow these directives: ║
|
|
39
|
+
║ ║
|
|
40
|
+
║ 1. DELEGATE exploration → Task(subagent_type="Explore") ║
|
|
41
|
+
║ 2. DELEGATE implementation → sdk.spawn_coder(feature_id, context) ║
|
|
42
|
+
║ 3. CREATE work items BEFORE code changes → sdk.features.create().save() ║
|
|
43
|
+
║ 4. PARALLELIZE independent tasks → Multiple Task() calls in ONE message ║
|
|
44
|
+
║ 5. USE SDK METHODS not raw prompts → sdk.orchestrate(), sdk.spawn_coder() ║
|
|
45
|
+
║ ║
|
|
46
|
+
║ SDK QUICK REFERENCE: ║
|
|
47
|
+
║ sdk.spawn_coder(feature_id, context) # Generate coder prompt ║
|
|
48
|
+
║ sdk.spawn_explorer(task, scope) # Generate explorer prompt ║
|
|
49
|
+
║ sdk.orchestrate(feature_id, scope) # Full orchestration workflow ║
|
|
50
|
+
║ sdk.plan_parallel_work(max_agents) # Get parallelizable work ║
|
|
51
|
+
║ ║
|
|
52
|
+
║ ANTI-PATTERNS TO AVOID: ║
|
|
53
|
+
║ ❌ Raw Task prompts without sdk.spawn_coder() ║
|
|
54
|
+
║ ❌ Sequential Task calls (use ONE message for parallelism) ║
|
|
55
|
+
║ ❌ Code changes without creating a feature first ║
|
|
56
|
+
║ ❌ Manual file edits on .htmlgraph/ (use SDK instead) ║
|
|
57
|
+
║ ║
|
|
58
|
+
║ FIRST ACTION: Dismiss this warning to confirm you've read it: ║
|
|
59
|
+
║ >>> sdk.dismiss_session_warning() ║
|
|
60
|
+
║ ║
|
|
61
|
+
╚══════════════════════════════════════════════════════════════════════════════╝
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class SessionWarning:
|
|
66
|
+
"""
|
|
67
|
+
Manages session warning state for AI agents.
|
|
68
|
+
|
|
69
|
+
Shows critical orchestrator instructions on first SDK usage,
|
|
70
|
+
requires explicit dismissal to confirm agent read them.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
WARNING_FILE = ".session-warning-state.json"
|
|
74
|
+
|
|
75
|
+
def __init__(self, graph_dir: Path):
|
|
76
|
+
self.graph_dir = Path(graph_dir)
|
|
77
|
+
self.state_file = self.graph_dir / self.WARNING_FILE
|
|
78
|
+
self._state: dict[str, Any] = self._load_state()
|
|
79
|
+
|
|
80
|
+
def _load_state(self) -> dict[str, Any]:
|
|
81
|
+
"""Load warning state from file."""
|
|
82
|
+
if self.state_file.exists():
|
|
83
|
+
try:
|
|
84
|
+
data = json.loads(self.state_file.read_text())
|
|
85
|
+
if isinstance(data, dict):
|
|
86
|
+
return data
|
|
87
|
+
except (json.JSONDecodeError, OSError):
|
|
88
|
+
pass
|
|
89
|
+
return {
|
|
90
|
+
"dismissed_at": None,
|
|
91
|
+
"dismissed_by": None,
|
|
92
|
+
"session_id": None,
|
|
93
|
+
"show_count": 0,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
def _save_state(self) -> None:
|
|
97
|
+
"""Save warning state to file."""
|
|
98
|
+
try:
|
|
99
|
+
self.state_file.write_text(json.dumps(self._state, indent=2))
|
|
100
|
+
except OSError:
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
def should_show(self, session_id: str | None = None) -> bool:
|
|
104
|
+
"""
|
|
105
|
+
Check if warning should be shown.
|
|
106
|
+
|
|
107
|
+
Shows warning if:
|
|
108
|
+
- Never dismissed, OR
|
|
109
|
+
- Different session than last dismissal
|
|
110
|
+
"""
|
|
111
|
+
# Always show if never dismissed
|
|
112
|
+
if not self._state.get("dismissed_at"):
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
# Show if different session
|
|
116
|
+
if session_id and self._state.get("session_id") != session_id:
|
|
117
|
+
return True
|
|
118
|
+
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
def show(self, agent: str | None = None, session_id: str | None = None) -> None:
|
|
122
|
+
"""
|
|
123
|
+
Show the warning to stderr (visible to agent).
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
agent: Agent identifier
|
|
127
|
+
session_id: Current session ID
|
|
128
|
+
"""
|
|
129
|
+
self._state["show_count"] = self._state.get("show_count", 0) + 1
|
|
130
|
+
self._save_state()
|
|
131
|
+
|
|
132
|
+
# Print to stderr so it's visible to the agent
|
|
133
|
+
print(ORCHESTRATOR_WARNING, file=sys.stderr)
|
|
134
|
+
|
|
135
|
+
# Also print dismissal reminder
|
|
136
|
+
print(
|
|
137
|
+
f"\n⚠️ WARNING: Orchestrator instructions shown ({self._state['show_count']} times). "
|
|
138
|
+
f"Dismiss with: sdk.dismiss_session_warning()\n",
|
|
139
|
+
file=sys.stderr,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def dismiss(self, agent: str | None = None, session_id: str | None = None) -> bool:
|
|
143
|
+
"""
|
|
144
|
+
Dismiss the warning for this session.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
agent: Agent that dismissed
|
|
148
|
+
session_id: Current session ID
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
True if dismissed, False if already dismissed for this session
|
|
152
|
+
"""
|
|
153
|
+
was_new = self.should_show(session_id)
|
|
154
|
+
|
|
155
|
+
self._state["dismissed_at"] = datetime.now().isoformat()
|
|
156
|
+
self._state["dismissed_by"] = agent
|
|
157
|
+
self._state["session_id"] = session_id
|
|
158
|
+
self._save_state()
|
|
159
|
+
|
|
160
|
+
if was_new:
|
|
161
|
+
print(
|
|
162
|
+
"✅ Orchestrator warning dismissed. You may now proceed with delegating work.",
|
|
163
|
+
file=sys.stderr,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return was_new
|
|
167
|
+
|
|
168
|
+
def get_status(self) -> dict[str, Any]:
|
|
169
|
+
"""Get current warning status."""
|
|
170
|
+
return {
|
|
171
|
+
"dismissed": bool(self._state.get("dismissed_at")),
|
|
172
|
+
"dismissed_at": self._state.get("dismissed_at"),
|
|
173
|
+
"dismissed_by": self._state.get("dismissed_by"),
|
|
174
|
+
"show_count": self._state.get("show_count", 0),
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def check_and_show_warning(
|
|
179
|
+
graph_dir: Path,
|
|
180
|
+
agent: str | None = None,
|
|
181
|
+
session_id: str | None = None,
|
|
182
|
+
) -> SessionWarning:
|
|
183
|
+
"""
|
|
184
|
+
Check if warning should be shown and show it if needed.
|
|
185
|
+
|
|
186
|
+
Called automatically from SDK.__init__.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
graph_dir: Path to .htmlgraph directory
|
|
190
|
+
agent: Agent identifier
|
|
191
|
+
session_id: Current session ID
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
SessionWarning instance for dismissal
|
|
195
|
+
"""
|
|
196
|
+
warning = SessionWarning(graph_dir)
|
|
197
|
+
|
|
198
|
+
if warning.should_show(session_id):
|
|
199
|
+
warning.show(agent=agent, session_id=session_id)
|
|
200
|
+
|
|
201
|
+
return warning
|
|
@@ -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
|
+
]
|