htmlgraph 0.26.23__py3-none-any.whl → 0.26.24__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/__init__.py +1 -1
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/db/schema.py +34 -1
- htmlgraph/models.py +4 -1
- htmlgraph/sdk.py +91 -0
- htmlgraph/session_manager.py +160 -2
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +749 -0
- {htmlgraph-0.26.23.dist-info → htmlgraph-0.26.24.dist-info}/METADATA +1 -1
- {htmlgraph-0.26.23.dist-info → htmlgraph-0.26.24.dist-info}/RECORD +17 -14
- {htmlgraph-0.26.23.data → htmlgraph-0.26.24.data}/data/htmlgraph/dashboard.html +0 -0
- {htmlgraph-0.26.23.data → htmlgraph-0.26.24.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.26.23.data → htmlgraph-0.26.24.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.26.23.data → htmlgraph-0.26.24.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.26.23.data → htmlgraph-0.26.24.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.26.23.dist-info → htmlgraph-0.26.24.dist-info}/WHEEL +0 -0
- {htmlgraph-0.26.23.dist-info → htmlgraph-0.26.24.dist-info}/entry_points.txt +0 -0
htmlgraph/db/schema.py
CHANGED
|
@@ -163,6 +163,12 @@ class HtmlGraphDB:
|
|
|
163
163
|
("completed_at", "DATETIME"),
|
|
164
164
|
("last_user_query_at", "DATETIME"),
|
|
165
165
|
("last_user_query", "TEXT"),
|
|
166
|
+
# Phase 2 Feature 3: Cross-Session Continuity handoff fields
|
|
167
|
+
("handoff_notes", "TEXT"),
|
|
168
|
+
("recommended_next", "TEXT"),
|
|
169
|
+
("blockers", "TEXT"), # JSON array of blocker strings
|
|
170
|
+
("recommended_context", "TEXT"), # JSON array of file paths
|
|
171
|
+
("continued_from", "TEXT"), # Previous session ID
|
|
166
172
|
]
|
|
167
173
|
|
|
168
174
|
# Refresh columns after potential rename
|
|
@@ -291,8 +297,14 @@ class HtmlGraphDB:
|
|
|
291
297
|
metadata JSON,
|
|
292
298
|
last_user_query_at DATETIME,
|
|
293
299
|
last_user_query TEXT,
|
|
300
|
+
handoff_notes TEXT,
|
|
301
|
+
recommended_next TEXT,
|
|
302
|
+
blockers JSON,
|
|
303
|
+
recommended_context JSON,
|
|
304
|
+
continued_from TEXT,
|
|
294
305
|
FOREIGN KEY (parent_session_id) REFERENCES sessions(session_id) ON DELETE SET NULL ON UPDATE CASCADE,
|
|
295
|
-
FOREIGN KEY (parent_event_id) REFERENCES agent_events(event_id) ON DELETE SET NULL ON UPDATE CASCADE
|
|
306
|
+
FOREIGN KEY (parent_event_id) REFERENCES agent_events(event_id) ON DELETE SET NULL ON UPDATE CASCADE,
|
|
307
|
+
FOREIGN KEY (continued_from) REFERENCES sessions(session_id) ON DELETE SET NULL ON UPDATE CASCADE
|
|
296
308
|
)
|
|
297
309
|
""")
|
|
298
310
|
|
|
@@ -407,6 +419,23 @@ class HtmlGraphDB:
|
|
|
407
419
|
)
|
|
408
420
|
""")
|
|
409
421
|
|
|
422
|
+
# 10. HANDOFF_TRACKING TABLE - Phase 2 Feature 3: Track handoff effectiveness
|
|
423
|
+
cursor.execute("""
|
|
424
|
+
CREATE TABLE IF NOT EXISTS handoff_tracking (
|
|
425
|
+
handoff_id TEXT PRIMARY KEY,
|
|
426
|
+
from_session_id TEXT NOT NULL,
|
|
427
|
+
to_session_id TEXT,
|
|
428
|
+
items_in_context INTEGER DEFAULT 0,
|
|
429
|
+
items_accessed INTEGER DEFAULT 0,
|
|
430
|
+
time_to_resume_seconds INTEGER DEFAULT 0,
|
|
431
|
+
user_rating INTEGER CHECK(user_rating BETWEEN 1 AND 5),
|
|
432
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
433
|
+
resumed_at DATETIME,
|
|
434
|
+
FOREIGN KEY (from_session_id) REFERENCES sessions(session_id) ON DELETE CASCADE,
|
|
435
|
+
FOREIGN KEY (to_session_id) REFERENCES sessions(session_id) ON DELETE SET NULL
|
|
436
|
+
)
|
|
437
|
+
""")
|
|
438
|
+
|
|
410
439
|
# 9. Create indexes for performance
|
|
411
440
|
self._create_indexes(cursor)
|
|
412
441
|
|
|
@@ -496,6 +525,10 @@ class HtmlGraphDB:
|
|
|
496
525
|
# live_events indexes - optimized for real-time WebSocket streaming
|
|
497
526
|
"CREATE INDEX IF NOT EXISTS idx_live_events_pending ON live_events(broadcast_at) WHERE broadcast_at IS NULL",
|
|
498
527
|
"CREATE INDEX IF NOT EXISTS idx_live_events_created ON live_events(created_at DESC)",
|
|
528
|
+
# handoff_tracking indexes - optimized for handoff effectiveness queries
|
|
529
|
+
"CREATE INDEX IF NOT EXISTS idx_handoff_from_session ON handoff_tracking(from_session_id, created_at DESC)",
|
|
530
|
+
"CREATE INDEX IF NOT EXISTS idx_handoff_to_session ON handoff_tracking(to_session_id, resumed_at DESC)",
|
|
531
|
+
"CREATE INDEX IF NOT EXISTS idx_handoff_rating ON handoff_tracking(user_rating, created_at DESC)",
|
|
499
532
|
]
|
|
500
533
|
|
|
501
534
|
for index_sql in indexes:
|
htmlgraph/models.py
CHANGED
|
@@ -975,10 +975,13 @@ class Session(BaseModel):
|
|
|
975
975
|
parent_activity: str | None = None # Parent activity ID
|
|
976
976
|
nesting_depth: int = 0 # Depth of nesting (0 = top-level)
|
|
977
977
|
|
|
978
|
-
# Handoff context
|
|
978
|
+
# Handoff context (Phase 2 Feature 3: Cross-Session Continuity)
|
|
979
979
|
handoff_notes: str | None = None
|
|
980
980
|
recommended_next: str | None = None
|
|
981
981
|
blockers: list[str] = Field(default_factory=list)
|
|
982
|
+
recommended_context: list[str] = Field(
|
|
983
|
+
default_factory=list
|
|
984
|
+
) # File paths to keep context for
|
|
982
985
|
|
|
983
986
|
# High-frequency activity log
|
|
984
987
|
activity_log: list[ActivityEntry] = Field(default_factory=list)
|
htmlgraph/sdk.py
CHANGED
|
@@ -333,6 +333,11 @@ class SDK:
|
|
|
333
333
|
# Context analytics interface (Context usage tracking)
|
|
334
334
|
self.context = ContextAnalytics(self)
|
|
335
335
|
|
|
336
|
+
# Pattern learning interface (Phase 2: Behavior Pattern Learning)
|
|
337
|
+
from htmlgraph.analytics.pattern_learning import PatternLearner
|
|
338
|
+
|
|
339
|
+
self.pattern_learning = PatternLearner(self._directory)
|
|
340
|
+
|
|
336
341
|
# Lazy-loaded orchestrator for subagent management
|
|
337
342
|
self._orchestrator: Any = None
|
|
338
343
|
|
|
@@ -776,6 +781,92 @@ class SDK:
|
|
|
776
781
|
blockers=blockers,
|
|
777
782
|
)
|
|
778
783
|
|
|
784
|
+
def continue_from_last(
|
|
785
|
+
self,
|
|
786
|
+
agent: str | None = None,
|
|
787
|
+
auto_create_session: bool = True,
|
|
788
|
+
) -> tuple[Any, Any]:
|
|
789
|
+
"""
|
|
790
|
+
Continue work from the last completed session.
|
|
791
|
+
|
|
792
|
+
Loads context from previous session including handoff notes,
|
|
793
|
+
recommended files, blockers, and recent commits.
|
|
794
|
+
|
|
795
|
+
Args:
|
|
796
|
+
agent: Filter by agent (None = current SDK agent)
|
|
797
|
+
auto_create_session: Create new session if True
|
|
798
|
+
|
|
799
|
+
Returns:
|
|
800
|
+
Tuple of (new_session, resume_info) or (None, None)
|
|
801
|
+
|
|
802
|
+
Example:
|
|
803
|
+
>>> sdk = SDK(agent="claude")
|
|
804
|
+
>>> session, resume = sdk.continue_from_last()
|
|
805
|
+
>>> if resume:
|
|
806
|
+
... print(resume.summary)
|
|
807
|
+
... print(resume.next_focus)
|
|
808
|
+
... for file in resume.recommended_files:
|
|
809
|
+
... print(f" - {file}")
|
|
810
|
+
"""
|
|
811
|
+
if not agent:
|
|
812
|
+
agent = self._agent_id
|
|
813
|
+
|
|
814
|
+
return self.session_manager.continue_from_last(
|
|
815
|
+
agent=agent,
|
|
816
|
+
auto_create_session=auto_create_session,
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
def end_session_with_handoff(
|
|
820
|
+
self,
|
|
821
|
+
session_id: str | None = None,
|
|
822
|
+
summary: str | None = None,
|
|
823
|
+
next_focus: str | None = None,
|
|
824
|
+
blockers: list[str] | None = None,
|
|
825
|
+
keep_context: list[str] | None = None,
|
|
826
|
+
auto_recommend_context: bool = True,
|
|
827
|
+
) -> Any:
|
|
828
|
+
"""
|
|
829
|
+
End session with handoff information for next session.
|
|
830
|
+
|
|
831
|
+
Args:
|
|
832
|
+
session_id: Session to end (None = active session)
|
|
833
|
+
summary: What was accomplished
|
|
834
|
+
next_focus: What should be done next
|
|
835
|
+
blockers: List of blockers
|
|
836
|
+
keep_context: List of files to keep context for
|
|
837
|
+
auto_recommend_context: Auto-recommend files from git
|
|
838
|
+
|
|
839
|
+
Returns:
|
|
840
|
+
Updated Session or None
|
|
841
|
+
|
|
842
|
+
Example:
|
|
843
|
+
>>> sdk.end_session_with_handoff(
|
|
844
|
+
... summary="Completed OAuth integration",
|
|
845
|
+
... next_focus="Implement JWT token refresh",
|
|
846
|
+
... blockers=["Waiting for security review"],
|
|
847
|
+
... keep_context=["src/auth/oauth.py"]
|
|
848
|
+
... )
|
|
849
|
+
"""
|
|
850
|
+
if not session_id:
|
|
851
|
+
if self._agent_id:
|
|
852
|
+
active = self.session_manager.get_active_session_for_agent(
|
|
853
|
+
self._agent_id
|
|
854
|
+
)
|
|
855
|
+
else:
|
|
856
|
+
active = self.session_manager.get_active_session()
|
|
857
|
+
if not active:
|
|
858
|
+
return None
|
|
859
|
+
session_id = active.id
|
|
860
|
+
|
|
861
|
+
return self.session_manager.end_session_with_handoff(
|
|
862
|
+
session_id=session_id,
|
|
863
|
+
summary=summary,
|
|
864
|
+
next_focus=next_focus,
|
|
865
|
+
blockers=blockers,
|
|
866
|
+
keep_context=keep_context,
|
|
867
|
+
auto_recommend_context=auto_recommend_context,
|
|
868
|
+
)
|
|
869
|
+
|
|
779
870
|
def start_session(
|
|
780
871
|
self,
|
|
781
872
|
session_id: str | None = None,
|
htmlgraph/session_manager.py
CHANGED
|
@@ -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
|
|
@@ -742,7 +742,7 @@ class SessionManager:
|
|
|
742
742
|
ActivityEntry(
|
|
743
743
|
tool="SessionEnd",
|
|
744
744
|
summary="Session ended",
|
|
745
|
-
timestamp=datetime.now(),
|
|
745
|
+
timestamp=datetime.now(timezone.utc),
|
|
746
746
|
)
|
|
747
747
|
)
|
|
748
748
|
|
|
@@ -792,6 +792,164 @@ class SessionManager:
|
|
|
792
792
|
|
|
793
793
|
return session
|
|
794
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
|
+
|
|
933
|
+
# Store recommended_context as JSON-serializable list
|
|
934
|
+
# (Session model expects list[str], converter will handle serialization)
|
|
935
|
+
if hasattr(session, "__dict__"):
|
|
936
|
+
session.__dict__["recommended_context"] = handoff_data[
|
|
937
|
+
"recommended_context"
|
|
938
|
+
]
|
|
939
|
+
|
|
940
|
+
# Persist handoff data to database before ending session
|
|
941
|
+
self.session_converter.save(session)
|
|
942
|
+
|
|
943
|
+
# End the session
|
|
944
|
+
self.end_session(session_id)
|
|
945
|
+
|
|
946
|
+
# Track handoff effectiveness (optional - only if database available)
|
|
947
|
+
# Note: SessionManager doesn't have direct database access,
|
|
948
|
+
# handoff tracking is primarily done through SDK
|
|
949
|
+
# Users should use SDK.end_session_with_handoff() for full tracking
|
|
950
|
+
|
|
951
|
+
return session
|
|
952
|
+
|
|
795
953
|
def release_session_features(self, session_id: str) -> list[str]:
|
|
796
954
|
"""
|
|
797
955
|
Release all features claimed by a specific session.
|
|
@@ -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
|
+
]
|