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,300 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SessionStart Hook Integration - Initialize session registry with repo awareness.
|
|
3
|
+
|
|
4
|
+
Integrates:
|
|
5
|
+
- SessionRegistry: File-based session tracking
|
|
6
|
+
- RepoHash: Git awareness and repository identification
|
|
7
|
+
- AtomicFileWriter: Crash-safe writes
|
|
8
|
+
|
|
9
|
+
Called by SessionStart hook to:
|
|
10
|
+
1. Register new session with repo info
|
|
11
|
+
2. Export session IDs to CLAUDE_ENV_FILE
|
|
12
|
+
3. Detect parent sessions from environment
|
|
13
|
+
4. Initialize heartbeat mechanism
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import uuid
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def initialize_session_from_hook(env_file: str | None = None) -> str:
|
|
26
|
+
"""
|
|
27
|
+
Initialize session registry from SessionStart hook.
|
|
28
|
+
|
|
29
|
+
Called automatically by SessionStart hook to set up session tracking.
|
|
30
|
+
Registers session with repository information and exports environment variables.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
env_file: Path to CLAUDE_ENV_FILE (from hook environment).
|
|
34
|
+
Allows exporting session IDs to parent process.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
session_id: Generated session ID for logging/tracking
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
OSError: If registry initialization fails
|
|
41
|
+
RuntimeError: If session registration fails
|
|
42
|
+
"""
|
|
43
|
+
from htmlgraph.repo_hash import RepoHash
|
|
44
|
+
from htmlgraph.session_registry import SessionRegistry
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
# Initialize registry (creates .htmlgraph/sessions/registry structure)
|
|
48
|
+
registry = SessionRegistry()
|
|
49
|
+
logger.debug(f"Initialized SessionRegistry at {registry.registry_dir}")
|
|
50
|
+
|
|
51
|
+
# Get repo information
|
|
52
|
+
try:
|
|
53
|
+
repo_hash = RepoHash()
|
|
54
|
+
git_info = repo_hash.get_git_info()
|
|
55
|
+
repo_hash_value = repo_hash.compute_repo_hash()
|
|
56
|
+
|
|
57
|
+
repo_info = {
|
|
58
|
+
"path": str(repo_hash.repo_path),
|
|
59
|
+
"hash": repo_hash_value,
|
|
60
|
+
"branch": git_info.get("branch"),
|
|
61
|
+
"commit": git_info.get("commit"),
|
|
62
|
+
"remote": git_info.get("remote"),
|
|
63
|
+
"dirty": git_info.get("dirty", False),
|
|
64
|
+
"is_monorepo": repo_hash.is_monorepo(),
|
|
65
|
+
"monorepo_project": repo_hash.get_monorepo_project(),
|
|
66
|
+
}
|
|
67
|
+
logger.debug(f"Repo info: {repo_info}")
|
|
68
|
+
except OSError as e:
|
|
69
|
+
logger.warning(f"Failed to get repo info: {e}")
|
|
70
|
+
repo_info = {
|
|
71
|
+
"path": str(Path.cwd()),
|
|
72
|
+
"hash": "unknown",
|
|
73
|
+
"branch": None,
|
|
74
|
+
"commit": None,
|
|
75
|
+
"remote": None,
|
|
76
|
+
"dirty": False,
|
|
77
|
+
"is_monorepo": False,
|
|
78
|
+
"monorepo_project": None,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Get instance information
|
|
82
|
+
instance_info = {
|
|
83
|
+
"pid": os.getpid(),
|
|
84
|
+
"hostname": _get_hostname(),
|
|
85
|
+
"start_time": _get_utc_timestamp(),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Generate session ID
|
|
89
|
+
session_id = f"sess-{uuid.uuid4().hex[:8]}"
|
|
90
|
+
|
|
91
|
+
# Register session atomically
|
|
92
|
+
try:
|
|
93
|
+
registry_file = registry.register_session(
|
|
94
|
+
session_id=session_id,
|
|
95
|
+
repo_info=repo_info,
|
|
96
|
+
instance_info=instance_info,
|
|
97
|
+
)
|
|
98
|
+
logger.info(f"Registered session {session_id} at {registry_file}")
|
|
99
|
+
except OSError as e:
|
|
100
|
+
logger.error(f"Failed to register session: {e}")
|
|
101
|
+
raise RuntimeError(f"Session registration failed: {e}") from e
|
|
102
|
+
|
|
103
|
+
# Export to CLAUDE_ENV_FILE if provided
|
|
104
|
+
if env_file:
|
|
105
|
+
try:
|
|
106
|
+
_export_to_env_file(
|
|
107
|
+
env_file=env_file,
|
|
108
|
+
session_id=session_id,
|
|
109
|
+
instance_id=registry.get_instance_id(),
|
|
110
|
+
repo_hash=repo_hash_value
|
|
111
|
+
if "repo_hash_value" in locals()
|
|
112
|
+
else "unknown",
|
|
113
|
+
)
|
|
114
|
+
logger.debug(f"Exported session environment to {env_file}")
|
|
115
|
+
except OSError as e:
|
|
116
|
+
logger.warning(f"Failed to export environment: {e}")
|
|
117
|
+
# Don't fail - session is registered even if export fails
|
|
118
|
+
|
|
119
|
+
# Check for parent session
|
|
120
|
+
parent_session_id = _get_parent_session_id()
|
|
121
|
+
if parent_session_id:
|
|
122
|
+
logger.info(f"Parent session detected: {parent_session_id}")
|
|
123
|
+
# Store parent relationship in environment for subprocesses
|
|
124
|
+
os.environ["HTMLGRAPH_PARENT_SESSION_ID"] = parent_session_id
|
|
125
|
+
|
|
126
|
+
return session_id
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error(f"Failed to initialize session: {e}", exc_info=True)
|
|
130
|
+
raise
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def finalize_session(session_id: str, status: str = "ended") -> bool:
|
|
134
|
+
"""
|
|
135
|
+
Finalize session (called by SessionEnd hook).
|
|
136
|
+
|
|
137
|
+
Archives the session and updates last activity timestamp.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
session_id: Session ID to finalize
|
|
141
|
+
status: Final status (ended, failed, etc.)
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
True if finalization succeeded, False otherwise
|
|
145
|
+
"""
|
|
146
|
+
from htmlgraph.session_registry import SessionRegistry
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
registry = SessionRegistry()
|
|
150
|
+
instance_id = registry.get_instance_id()
|
|
151
|
+
|
|
152
|
+
# Archive the session
|
|
153
|
+
success = registry.archive_session(instance_id)
|
|
154
|
+
if success:
|
|
155
|
+
logger.info(f"Archived session {session_id} (status: {status})")
|
|
156
|
+
else:
|
|
157
|
+
logger.warning(f"Failed to archive session {session_id}")
|
|
158
|
+
|
|
159
|
+
return success
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.error(f"Failed to finalize session {session_id}: {e}")
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def heartbeat(session_id: str | None = None) -> bool:
|
|
166
|
+
"""
|
|
167
|
+
Update session activity timestamp (liveness heartbeat).
|
|
168
|
+
|
|
169
|
+
Called periodically to indicate the session is still active.
|
|
170
|
+
Should be called on each tool use or periodically (e.g., every 5 minutes).
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
session_id: Optional session ID (uses current instance if None)
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
True if heartbeat succeeded, False otherwise
|
|
177
|
+
"""
|
|
178
|
+
from htmlgraph.session_registry import SessionRegistry
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
registry = SessionRegistry()
|
|
182
|
+
instance_id = registry.get_instance_id()
|
|
183
|
+
|
|
184
|
+
success = registry.update_activity(instance_id)
|
|
185
|
+
if success:
|
|
186
|
+
logger.debug(f"Updated activity for instance {instance_id}")
|
|
187
|
+
else:
|
|
188
|
+
logger.warning(f"Failed to update activity for instance {instance_id}")
|
|
189
|
+
|
|
190
|
+
return success
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.error(f"Failed to update activity: {e}")
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def get_current_session() -> dict | None:
|
|
197
|
+
"""
|
|
198
|
+
Get current session for this instance.
|
|
199
|
+
|
|
200
|
+
Returns the registration data for the current instance's session.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Session dict with instance_id, session_id, repo, etc., or None if not found
|
|
204
|
+
"""
|
|
205
|
+
from htmlgraph.session_registry import SessionRegistry
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
registry = SessionRegistry()
|
|
209
|
+
instance_id = registry.get_instance_id()
|
|
210
|
+
session = registry.read_session(instance_id)
|
|
211
|
+
return session
|
|
212
|
+
except Exception as e:
|
|
213
|
+
logger.error(f"Failed to get current session: {e}")
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def get_parent_session_id() -> str | None:
|
|
218
|
+
"""
|
|
219
|
+
Get parent session ID if this is a spawned task.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Parent session ID from environment, or None if not a spawned task
|
|
223
|
+
"""
|
|
224
|
+
return _get_parent_session_id()
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# Private helpers
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _get_hostname() -> str:
|
|
231
|
+
"""Get hostname safely."""
|
|
232
|
+
try:
|
|
233
|
+
import socket
|
|
234
|
+
|
|
235
|
+
return socket.gethostname()
|
|
236
|
+
except Exception:
|
|
237
|
+
return "unknown"
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _get_utc_timestamp() -> str:
|
|
241
|
+
"""Get current UTC timestamp in ISO 8601 format."""
|
|
242
|
+
now = datetime.now(timezone.utc)
|
|
243
|
+
return now.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _export_to_env_file(
|
|
247
|
+
env_file: str,
|
|
248
|
+
session_id: str,
|
|
249
|
+
instance_id: str,
|
|
250
|
+
repo_hash: str,
|
|
251
|
+
) -> None:
|
|
252
|
+
"""
|
|
253
|
+
Export session environment variables to CLAUDE_ENV_FILE.
|
|
254
|
+
|
|
255
|
+
Appends to the file to preserve any existing variables.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
env_file: Path to environment file
|
|
259
|
+
session_id: Session ID to export
|
|
260
|
+
instance_id: Instance ID to export
|
|
261
|
+
repo_hash: Repository hash to export
|
|
262
|
+
|
|
263
|
+
Raises:
|
|
264
|
+
OSError: If file write fails
|
|
265
|
+
"""
|
|
266
|
+
env_path = Path(env_file)
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
# Append to environment file
|
|
270
|
+
with open(env_path, "a") as f:
|
|
271
|
+
f.write(f"export HTMLGRAPH_SESSION_ID={session_id}\n")
|
|
272
|
+
f.write(f"export HTMLGRAPH_INSTANCE_ID={instance_id}\n")
|
|
273
|
+
f.write(f"export HTMLGRAPH_REPO_HASH={repo_hash}\n")
|
|
274
|
+
|
|
275
|
+
logger.debug(f"Exported environment variables to {env_file}")
|
|
276
|
+
except OSError as e:
|
|
277
|
+
logger.error(f"Failed to export environment to {env_file}: {e}")
|
|
278
|
+
raise
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _get_parent_session_id() -> str | None:
|
|
282
|
+
"""
|
|
283
|
+
Detect parent session from environment.
|
|
284
|
+
|
|
285
|
+
Checks:
|
|
286
|
+
1. HTMLGRAPH_PARENT_SESSION_ID env var (set by Task spawning)
|
|
287
|
+
2. HTMLGRAPH_PARENT_SESSION env var (alternate name)
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
Parent session ID if found, None otherwise
|
|
291
|
+
"""
|
|
292
|
+
parent_id = os.environ.get("HTMLGRAPH_PARENT_SESSION_ID")
|
|
293
|
+
if parent_id:
|
|
294
|
+
return parent_id
|
|
295
|
+
|
|
296
|
+
parent_id = os.environ.get("HTMLGRAPH_PARENT_SESSION")
|
|
297
|
+
if parent_id:
|
|
298
|
+
return parent_id
|
|
299
|
+
|
|
300
|
+
return None
|
htmlgraph/session_manager.py
CHANGED
|
@@ -27,7 +27,7 @@ from htmlgraph.event_log import EventRecord, JsonlEventLog
|
|
|
27
27
|
from htmlgraph.exceptions import SessionNotFoundError
|
|
28
28
|
from htmlgraph.graph import HtmlGraph
|
|
29
29
|
from htmlgraph.ids import generate_id
|
|
30
|
-
from htmlgraph.models import ActivityEntry, Node, Session
|
|
30
|
+
from htmlgraph.models import ActivityEntry, ErrorEntry, Node, Session
|
|
31
31
|
from htmlgraph.services import ClaimingService
|
|
32
32
|
from htmlgraph.spike_index import ActiveAutoSpikeIndex
|
|
33
33
|
|
|
@@ -186,7 +186,7 @@ class SessionManager:
|
|
|
186
186
|
"""Mark a session as stale (kept for history but not considered active)."""
|
|
187
187
|
if session.status != "active":
|
|
188
188
|
return
|
|
189
|
-
now = datetime.now()
|
|
189
|
+
now = datetime.now(timezone.utc)
|
|
190
190
|
session.status = "stale"
|
|
191
191
|
session.ended_at = now
|
|
192
192
|
session.last_activity = now
|
|
@@ -230,6 +230,7 @@ class SessionManager:
|
|
|
230
230
|
continued_from: str | None = None,
|
|
231
231
|
start_commit: str | None = None,
|
|
232
232
|
title: str | None = None,
|
|
233
|
+
parent_session_id: str | None = None,
|
|
233
234
|
) -> Session:
|
|
234
235
|
"""
|
|
235
236
|
Start a new session.
|
|
@@ -241,6 +242,7 @@ class SessionManager:
|
|
|
241
242
|
continued_from: Previous session ID if continuing
|
|
242
243
|
start_commit: Git commit hash at session start
|
|
243
244
|
title: Optional human-readable title
|
|
245
|
+
parent_session_id: ID of parent session (for subagents)
|
|
244
246
|
|
|
245
247
|
Returns:
|
|
246
248
|
New Session instance
|
|
@@ -309,6 +311,7 @@ class SessionManager:
|
|
|
309
311
|
started_at=now,
|
|
310
312
|
last_activity=now,
|
|
311
313
|
title=title or "",
|
|
314
|
+
parent_session=parent_session_id,
|
|
312
315
|
)
|
|
313
316
|
|
|
314
317
|
# Add session start event
|
|
@@ -320,6 +323,12 @@ class SessionManager:
|
|
|
320
323
|
)
|
|
321
324
|
)
|
|
322
325
|
|
|
326
|
+
# Set parent session in environment for subsequent subprocesses (e.g. HeadlessSpawner)
|
|
327
|
+
# This ensures that any tools spawned by this session link back to it
|
|
328
|
+
import os
|
|
329
|
+
|
|
330
|
+
os.environ["HTMLGRAPH_PARENT_SESSION"] = session.id
|
|
331
|
+
|
|
323
332
|
# Save to disk
|
|
324
333
|
self.session_converter.save(session)
|
|
325
334
|
self._sessions_cache_dirty = True
|
|
@@ -733,7 +742,7 @@ class SessionManager:
|
|
|
733
742
|
ActivityEntry(
|
|
734
743
|
tool="SessionEnd",
|
|
735
744
|
summary="Session ended",
|
|
736
|
-
timestamp=datetime.now(),
|
|
745
|
+
timestamp=datetime.now(timezone.utc),
|
|
737
746
|
)
|
|
738
747
|
)
|
|
739
748
|
|
|
@@ -783,6 +792,158 @@ class SessionManager:
|
|
|
783
792
|
|
|
784
793
|
return session
|
|
785
794
|
|
|
795
|
+
def continue_from_last(
|
|
796
|
+
self,
|
|
797
|
+
agent: str | None = None,
|
|
798
|
+
auto_create_session: bool = True,
|
|
799
|
+
) -> tuple[Session | None, Any]: # Returns (new_session, resume_info)
|
|
800
|
+
"""
|
|
801
|
+
Continue work from the last completed session.
|
|
802
|
+
|
|
803
|
+
Loads context from the previous session including:
|
|
804
|
+
- Handoff notes and next focus
|
|
805
|
+
- Blockers
|
|
806
|
+
- Recommended context files
|
|
807
|
+
- Recent commits
|
|
808
|
+
- Features worked on
|
|
809
|
+
|
|
810
|
+
Args:
|
|
811
|
+
agent: Filter by agent (None = current agent)
|
|
812
|
+
auto_create_session: Create new session if True
|
|
813
|
+
|
|
814
|
+
Returns:
|
|
815
|
+
Tuple of (new_session, resume_info) or (None, None) if no previous session
|
|
816
|
+
|
|
817
|
+
Example:
|
|
818
|
+
>>> manager = SessionManager(".htmlgraph")
|
|
819
|
+
>>> new_session, resume = manager.continue_from_last(agent="claude")
|
|
820
|
+
>>> if resume:
|
|
821
|
+
... print(resume.summary)
|
|
822
|
+
... print(resume.recommended_files)
|
|
823
|
+
"""
|
|
824
|
+
# Import handoff module
|
|
825
|
+
from typing import Any
|
|
826
|
+
|
|
827
|
+
from htmlgraph.sessions.handoff import SessionResume
|
|
828
|
+
|
|
829
|
+
# Create a minimal SDK-like object with just the directory
|
|
830
|
+
# to avoid circular dependency and database initialization issues
|
|
831
|
+
class MinimalSDK:
|
|
832
|
+
def __init__(self, directory: Path) -> None:
|
|
833
|
+
self._directory = directory
|
|
834
|
+
|
|
835
|
+
sdk: Any = MinimalSDK(self.graph_dir)
|
|
836
|
+
resume = SessionResume(sdk)
|
|
837
|
+
|
|
838
|
+
# Get last session
|
|
839
|
+
last_session = resume.get_last_session(agent=agent)
|
|
840
|
+
if not last_session:
|
|
841
|
+
return None, None
|
|
842
|
+
|
|
843
|
+
# Build resume info
|
|
844
|
+
resume_info = resume.build_resume_info(last_session)
|
|
845
|
+
|
|
846
|
+
# Create new session if requested
|
|
847
|
+
new_session = None
|
|
848
|
+
if auto_create_session:
|
|
849
|
+
from htmlgraph.ids import generate_id
|
|
850
|
+
|
|
851
|
+
session_id = generate_id("sess")
|
|
852
|
+
new_session = self.start_session(
|
|
853
|
+
session_id=session_id,
|
|
854
|
+
agent=agent or last_session.agent,
|
|
855
|
+
title=f"Continuing from {last_session.id}",
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
# Link to previous session
|
|
859
|
+
new_session.continued_from = last_session.id
|
|
860
|
+
self.session_converter.save(new_session)
|
|
861
|
+
|
|
862
|
+
return new_session, resume_info
|
|
863
|
+
|
|
864
|
+
def end_session_with_handoff(
|
|
865
|
+
self,
|
|
866
|
+
session_id: str,
|
|
867
|
+
summary: str | None = None,
|
|
868
|
+
next_focus: str | None = None,
|
|
869
|
+
blockers: list[str] | None = None,
|
|
870
|
+
keep_context: list[str] | None = None,
|
|
871
|
+
auto_recommend_context: bool = True,
|
|
872
|
+
) -> Session | None:
|
|
873
|
+
"""
|
|
874
|
+
End session with handoff information for next session.
|
|
875
|
+
|
|
876
|
+
Args:
|
|
877
|
+
session_id: Session to end
|
|
878
|
+
summary: What was accomplished (handoff notes)
|
|
879
|
+
next_focus: What should be done next
|
|
880
|
+
blockers: List of blockers preventing progress
|
|
881
|
+
keep_context: List of files to keep context for
|
|
882
|
+
auto_recommend_context: Auto-recommend files from git history
|
|
883
|
+
|
|
884
|
+
Returns:
|
|
885
|
+
Updated session or None
|
|
886
|
+
|
|
887
|
+
Example:
|
|
888
|
+
>>> manager.end_session_with_handoff(
|
|
889
|
+
... session_id="sess-123",
|
|
890
|
+
... summary="Completed OAuth integration",
|
|
891
|
+
... next_focus="Implement JWT token refresh",
|
|
892
|
+
... blockers=["Waiting for security review"],
|
|
893
|
+
... keep_context=["src/auth/oauth.py"]
|
|
894
|
+
... )
|
|
895
|
+
"""
|
|
896
|
+
from htmlgraph.sessions.handoff import (
|
|
897
|
+
ContextRecommender,
|
|
898
|
+
HandoffBuilder,
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
# Get session
|
|
902
|
+
session = self.get_session(session_id)
|
|
903
|
+
if not session:
|
|
904
|
+
return None
|
|
905
|
+
|
|
906
|
+
# Build handoff using HandoffBuilder
|
|
907
|
+
builder = HandoffBuilder(session)
|
|
908
|
+
|
|
909
|
+
if summary:
|
|
910
|
+
builder.add_summary(summary)
|
|
911
|
+
|
|
912
|
+
if next_focus:
|
|
913
|
+
builder.add_next_focus(next_focus)
|
|
914
|
+
|
|
915
|
+
if blockers:
|
|
916
|
+
builder.add_blockers(blockers)
|
|
917
|
+
|
|
918
|
+
if keep_context:
|
|
919
|
+
builder.add_context_files(keep_context)
|
|
920
|
+
|
|
921
|
+
# Auto-recommend context files
|
|
922
|
+
if auto_recommend_context:
|
|
923
|
+
recommender = ContextRecommender()
|
|
924
|
+
builder.auto_recommend_context(recommender, max_files=10)
|
|
925
|
+
|
|
926
|
+
handoff_data = builder.build()
|
|
927
|
+
|
|
928
|
+
# Update session with handoff data
|
|
929
|
+
session.handoff_notes = handoff_data["handoff_notes"]
|
|
930
|
+
session.recommended_next = handoff_data["recommended_next"]
|
|
931
|
+
session.blockers = handoff_data["blockers"]
|
|
932
|
+
session.recommended_context = handoff_data["recommended_context"]
|
|
933
|
+
|
|
934
|
+
# Persist handoff data to database before ending session
|
|
935
|
+
self.session_converter.save(session)
|
|
936
|
+
|
|
937
|
+
# End the session
|
|
938
|
+
self.end_session(session_id)
|
|
939
|
+
|
|
940
|
+
# Track handoff effectiveness (optional - only if database available)
|
|
941
|
+
# Note: SessionManager doesn't have direct database access,
|
|
942
|
+
# handoff tracking is primarily done through SDK
|
|
943
|
+
# Users should use SDK.end_session_with_handoff() for full tracking
|
|
944
|
+
|
|
945
|
+
return session
|
|
946
|
+
|
|
786
947
|
def release_session_features(self, session_id: str) -> list[str]:
|
|
787
948
|
"""
|
|
788
949
|
Release all features claimed by a specific session.
|
|
@@ -795,6 +956,126 @@ class SessionManager:
|
|
|
795
956
|
"""
|
|
796
957
|
return self.claiming_service.release_session_features(session_id)
|
|
797
958
|
|
|
959
|
+
def log_error(
|
|
960
|
+
self,
|
|
961
|
+
session_id: str,
|
|
962
|
+
error: Exception,
|
|
963
|
+
traceback_str: str,
|
|
964
|
+
context: dict[str, Any] | None = None,
|
|
965
|
+
) -> None:
|
|
966
|
+
"""
|
|
967
|
+
Log error with full traceback to session.
|
|
968
|
+
|
|
969
|
+
Stores complete error details for later retrieval via debug command.
|
|
970
|
+
Minimizes console output for better token efficiency.
|
|
971
|
+
|
|
972
|
+
Args:
|
|
973
|
+
session_id: Session ID to log error to
|
|
974
|
+
error: The exception object
|
|
975
|
+
traceback_str: Full traceback string
|
|
976
|
+
context: Optional context dict (e.g. current file, line number)
|
|
977
|
+
"""
|
|
978
|
+
session = self.get_session(session_id)
|
|
979
|
+
if not session:
|
|
980
|
+
return
|
|
981
|
+
|
|
982
|
+
error_entry = ErrorEntry(
|
|
983
|
+
timestamp=datetime.now(),
|
|
984
|
+
error_type=error.__class__.__name__,
|
|
985
|
+
message=str(error),
|
|
986
|
+
traceback=traceback_str,
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
# Append error record to error_log
|
|
990
|
+
session.error_log.append(error_entry)
|
|
991
|
+
|
|
992
|
+
# Save updated session
|
|
993
|
+
self.session_converter.save(session)
|
|
994
|
+
|
|
995
|
+
def get_session_errors(self, session_id: str) -> list[dict[str, Any]]:
|
|
996
|
+
"""
|
|
997
|
+
Retrieve all errors logged for a session.
|
|
998
|
+
|
|
999
|
+
Args:
|
|
1000
|
+
session_id: Session ID
|
|
1001
|
+
|
|
1002
|
+
Returns:
|
|
1003
|
+
List of error records, or empty list if none
|
|
1004
|
+
"""
|
|
1005
|
+
session = self.get_session(session_id)
|
|
1006
|
+
if not session:
|
|
1007
|
+
return []
|
|
1008
|
+
return [error.model_dump() for error in session.error_log]
|
|
1009
|
+
|
|
1010
|
+
def search_errors(
|
|
1011
|
+
self,
|
|
1012
|
+
session_id: str,
|
|
1013
|
+
error_type: str | None = None,
|
|
1014
|
+
pattern: str | None = None,
|
|
1015
|
+
) -> list[dict[str, Any]]:
|
|
1016
|
+
"""
|
|
1017
|
+
Search errors in a session by type and/or pattern.
|
|
1018
|
+
|
|
1019
|
+
Args:
|
|
1020
|
+
session_id: Session ID to search
|
|
1021
|
+
error_type: Filter by exception type (e.g., "ValueError")
|
|
1022
|
+
pattern: Regex pattern to match in error message
|
|
1023
|
+
|
|
1024
|
+
Returns:
|
|
1025
|
+
List of matching error records
|
|
1026
|
+
"""
|
|
1027
|
+
session = self.get_session(session_id)
|
|
1028
|
+
if not session:
|
|
1029
|
+
return []
|
|
1030
|
+
|
|
1031
|
+
errors = [error.model_dump() for error in session.error_log]
|
|
1032
|
+
|
|
1033
|
+
# Filter by error type
|
|
1034
|
+
if error_type:
|
|
1035
|
+
errors = [e for e in errors if e.get("error_type") == error_type]
|
|
1036
|
+
|
|
1037
|
+
# Filter by pattern in message
|
|
1038
|
+
if pattern:
|
|
1039
|
+
compiled_pattern = re.compile(pattern, re.IGNORECASE)
|
|
1040
|
+
errors = [
|
|
1041
|
+
e for e in errors if compiled_pattern.search(e.get("message", ""))
|
|
1042
|
+
]
|
|
1043
|
+
|
|
1044
|
+
return errors
|
|
1045
|
+
|
|
1046
|
+
def get_error_summary(self, session_id: str) -> dict[str, Any]:
|
|
1047
|
+
"""
|
|
1048
|
+
Get summary statistics of errors in a session.
|
|
1049
|
+
|
|
1050
|
+
Args:
|
|
1051
|
+
session_id: Session ID
|
|
1052
|
+
|
|
1053
|
+
Returns:
|
|
1054
|
+
Dictionary with error summary statistics
|
|
1055
|
+
"""
|
|
1056
|
+
session = self.get_session(session_id)
|
|
1057
|
+
if not session or not session.error_log:
|
|
1058
|
+
return {
|
|
1059
|
+
"total_errors": 0,
|
|
1060
|
+
"error_types": {},
|
|
1061
|
+
"first_error": None,
|
|
1062
|
+
"last_error": None,
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
errors = session.error_log
|
|
1066
|
+
error_types: dict[str, int] = {}
|
|
1067
|
+
|
|
1068
|
+
for error in errors:
|
|
1069
|
+
error_type = error.error_type
|
|
1070
|
+
error_types[error_type] = error_types.get(error_type, 0) + 1
|
|
1071
|
+
|
|
1072
|
+
return {
|
|
1073
|
+
"total_errors": len(errors),
|
|
1074
|
+
"error_types": error_types,
|
|
1075
|
+
"first_error": errors[0].model_dump() if errors else None,
|
|
1076
|
+
"last_error": errors[-1].model_dump() if errors else None,
|
|
1077
|
+
}
|
|
1078
|
+
|
|
798
1079
|
# =========================================================================
|
|
799
1080
|
# Activity Tracking
|
|
800
1081
|
# =========================================================================
|
|
@@ -925,6 +1206,7 @@ class SessionManager:
|
|
|
925
1206
|
work_type=work_type,
|
|
926
1207
|
session_status=session.status,
|
|
927
1208
|
file_paths=file_paths,
|
|
1209
|
+
parent_session_id=session.parent_session,
|
|
928
1210
|
payload=entry.payload
|
|
929
1211
|
if isinstance(entry.payload, dict)
|
|
930
1212
|
else payload,
|