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
htmlgraph/hooks/pretooluse.py
CHANGED
|
@@ -1,27 +1,38 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Unified PreToolUse Hook - Parallel Orchestrator + Validator
|
|
2
|
+
Unified PreToolUse Hook - Parallel Orchestrator + Validator + Event Tracing
|
|
3
3
|
|
|
4
|
-
This module provides a unified PreToolUse hook that runs
|
|
5
|
-
enforcement
|
|
4
|
+
This module provides a unified PreToolUse hook that runs orchestrator
|
|
5
|
+
enforcement, work validation checks, and event tracing in parallel using asyncio.
|
|
6
6
|
|
|
7
7
|
Architecture:
|
|
8
|
-
- Runs orchestrator check
|
|
8
|
+
- Runs orchestrator check, validator check, and event tracing simultaneously
|
|
9
9
|
- Combines results into Claude Code standard format
|
|
10
10
|
- Returns blocking response only if both checks agree
|
|
11
11
|
- Provides combined guidance from both systems
|
|
12
|
+
- Generates tool_use_id and initiates event tracing for correlation
|
|
12
13
|
|
|
13
14
|
Performance:
|
|
14
15
|
- ~40-50% faster than sequential subprocess execution
|
|
15
16
|
- Single Python process (no subprocess overhead)
|
|
16
17
|
- Parallel execution via asyncio.gather()
|
|
18
|
+
|
|
19
|
+
Event Tracing:
|
|
20
|
+
- Generates UUID v4 for tool_use_id
|
|
21
|
+
- Captures tool name, input, start time (ISO8601 UTC), session_id
|
|
22
|
+
- Inserts start event into tool_traces table for PostToolUse correlation
|
|
23
|
+
- Non-blocking - errors gracefully degrade to allow tool execution
|
|
17
24
|
"""
|
|
18
25
|
|
|
19
26
|
import asyncio
|
|
20
27
|
import json
|
|
28
|
+
import logging
|
|
21
29
|
import os
|
|
22
30
|
import sys
|
|
31
|
+
import uuid
|
|
32
|
+
from datetime import datetime, timezone
|
|
23
33
|
from typing import Any
|
|
24
34
|
|
|
35
|
+
from htmlgraph.db.schema import HtmlGraphDB
|
|
25
36
|
from htmlgraph.hooks.orchestrator import enforce_orchestrator_mode
|
|
26
37
|
from htmlgraph.hooks.task_enforcer import enforce_task_saving
|
|
27
38
|
from htmlgraph.hooks.validator import (
|
|
@@ -32,6 +43,497 @@ from htmlgraph.hooks.validator import (
|
|
|
32
43
|
validate_tool_call,
|
|
33
44
|
)
|
|
34
45
|
|
|
46
|
+
logger = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def generate_tool_use_id() -> str:
|
|
50
|
+
"""
|
|
51
|
+
Generate UUID v4 for tool_use_id.
|
|
52
|
+
|
|
53
|
+
Used for trace correlation between PreToolUse and PostToolUse hooks.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
UUID v4 string (36 chars)
|
|
57
|
+
"""
|
|
58
|
+
return str(uuid.uuid4())
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_current_session_id() -> str | None:
|
|
62
|
+
"""
|
|
63
|
+
Query current session_id from environment or session files.
|
|
64
|
+
|
|
65
|
+
Reads from:
|
|
66
|
+
1. Environment variable HTMLGRAPH_SESSION_ID (set by SessionStart hook)
|
|
67
|
+
2. Latest session HTML file (fallback if env var not set)
|
|
68
|
+
3. Session registry file (fallback if HTML file not found)
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Session ID string or None if not found
|
|
72
|
+
"""
|
|
73
|
+
# First try environment variable
|
|
74
|
+
session_id = os.environ.get("HTMLGRAPH_SESSION_ID")
|
|
75
|
+
if session_id:
|
|
76
|
+
logger.debug(f"Session ID from environment: {session_id}")
|
|
77
|
+
return session_id
|
|
78
|
+
|
|
79
|
+
# Fallback: Read from latest session HTML file
|
|
80
|
+
try:
|
|
81
|
+
import re
|
|
82
|
+
from pathlib import Path
|
|
83
|
+
|
|
84
|
+
graph_dir = Path.cwd() / ".htmlgraph"
|
|
85
|
+
sessions_dir = graph_dir / "sessions"
|
|
86
|
+
|
|
87
|
+
logger.debug(f"Looking for session files in: {sessions_dir}")
|
|
88
|
+
|
|
89
|
+
if sessions_dir.exists():
|
|
90
|
+
# Get the most recent session HTML file
|
|
91
|
+
session_files = sorted(
|
|
92
|
+
sessions_dir.glob("sess-*.html"),
|
|
93
|
+
key=lambda p: p.stat().st_mtime,
|
|
94
|
+
reverse=True,
|
|
95
|
+
)
|
|
96
|
+
logger.debug(f"Found {len(session_files)} session files")
|
|
97
|
+
|
|
98
|
+
for session_file in session_files:
|
|
99
|
+
try:
|
|
100
|
+
# Extract session_id from filename (sess-XXXXX.html)
|
|
101
|
+
match = re.search(r"sess-([a-f0-9]+)", session_file.name)
|
|
102
|
+
if match:
|
|
103
|
+
session_id = f"sess-{match.group(1)}"
|
|
104
|
+
logger.debug(f"Found session ID from file: {session_id}")
|
|
105
|
+
return session_id
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.debug(f"Error reading session file {session_file}: {e}")
|
|
108
|
+
continue
|
|
109
|
+
logger.debug("No valid session files found")
|
|
110
|
+
else:
|
|
111
|
+
logger.debug(f"Sessions directory not found: {sessions_dir}")
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.debug(f"Could not read from session files: {e}")
|
|
114
|
+
|
|
115
|
+
# Fallback: Read from session registry
|
|
116
|
+
try:
|
|
117
|
+
import json
|
|
118
|
+
from pathlib import Path
|
|
119
|
+
|
|
120
|
+
graph_dir = Path.cwd() / ".htmlgraph"
|
|
121
|
+
registry_dir = graph_dir / "sessions" / "registry" / "active"
|
|
122
|
+
|
|
123
|
+
if registry_dir.exists():
|
|
124
|
+
# Get the most recent session file
|
|
125
|
+
session_files = sorted(
|
|
126
|
+
registry_dir.glob("*.json"),
|
|
127
|
+
key=lambda p: p.stat().st_mtime,
|
|
128
|
+
reverse=True,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
for session_file in session_files:
|
|
132
|
+
try:
|
|
133
|
+
with open(session_file) as f:
|
|
134
|
+
data = json.load(f)
|
|
135
|
+
if data.get("status") == "active":
|
|
136
|
+
session_id = data.get("session_id")
|
|
137
|
+
if isinstance(session_id, str):
|
|
138
|
+
return session_id
|
|
139
|
+
except Exception:
|
|
140
|
+
continue
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.debug(f"Could not read from session registry: {e}")
|
|
143
|
+
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def sanitize_tool_input(tool_input: dict[str, Any]) -> dict[str, Any]:
|
|
148
|
+
"""
|
|
149
|
+
Sanitize tool input to remove sensitive data before storage.
|
|
150
|
+
|
|
151
|
+
Removes or truncates:
|
|
152
|
+
- Passwords and tokens (any field with 'password', 'token', 'secret', 'key')
|
|
153
|
+
- Large binary data
|
|
154
|
+
- Deeply nested structures
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
tool_input: Raw tool input to sanitize
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Sanitized copy of tool_input
|
|
161
|
+
"""
|
|
162
|
+
try:
|
|
163
|
+
sanitized = {}
|
|
164
|
+
sensitive_keys = {"password", "token", "secret", "key", "auth", "api_key"}
|
|
165
|
+
|
|
166
|
+
for key, value in tool_input.items():
|
|
167
|
+
# Remove sensitive fields
|
|
168
|
+
if any(sens in key.lower() for sens in sensitive_keys):
|
|
169
|
+
sanitized[key] = "[REDACTED]"
|
|
170
|
+
# Truncate very large values
|
|
171
|
+
elif isinstance(value, str) and len(value) > 10000:
|
|
172
|
+
sanitized[key] = f"{value[:10000]}... [TRUNCATED]"
|
|
173
|
+
# Keep other values
|
|
174
|
+
else:
|
|
175
|
+
sanitized[key] = value
|
|
176
|
+
|
|
177
|
+
return sanitized
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.warning(f"Error sanitizing tool input: {e}")
|
|
180
|
+
return tool_input
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def extract_subagent_type(tool_input: dict[str, Any]) -> str | None:
|
|
184
|
+
"""
|
|
185
|
+
Extract subagent_type from Task() tool input.
|
|
186
|
+
|
|
187
|
+
Looks for patterns like:
|
|
188
|
+
- "subagent_type": "gemini-spawner"
|
|
189
|
+
- Task with specific naming patterns
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
tool_input: Task() tool input parameters
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Subagent type string or None if not found
|
|
196
|
+
"""
|
|
197
|
+
try:
|
|
198
|
+
# Check for explicit subagent_type parameter
|
|
199
|
+
if "subagent_type" in tool_input:
|
|
200
|
+
return str(tool_input.get("subagent_type"))
|
|
201
|
+
|
|
202
|
+
# Check in prompt for agent references
|
|
203
|
+
prompt = str(tool_input.get("prompt", "")).lower()
|
|
204
|
+
if "gemini" in prompt:
|
|
205
|
+
return "gemini-spawner"
|
|
206
|
+
if "codex" in prompt:
|
|
207
|
+
return "codex-spawner"
|
|
208
|
+
if "researcher" in prompt:
|
|
209
|
+
return "researcher"
|
|
210
|
+
if "debugger" in prompt:
|
|
211
|
+
return "debugger"
|
|
212
|
+
|
|
213
|
+
return None
|
|
214
|
+
except Exception:
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def create_task_parent_event(
|
|
219
|
+
db: HtmlGraphDB,
|
|
220
|
+
tool_input: dict[str, Any],
|
|
221
|
+
session_id: str,
|
|
222
|
+
start_time: str,
|
|
223
|
+
) -> str | None:
|
|
224
|
+
"""
|
|
225
|
+
Create a parent event for Task() delegations.
|
|
226
|
+
|
|
227
|
+
Inserts into agent_events with:
|
|
228
|
+
- event_type: 'task_delegation'
|
|
229
|
+
- subagent_type: Extracted from tool input
|
|
230
|
+
- status: 'started'
|
|
231
|
+
- parent_event_id: UserQuery event ID (links back to conversation root)
|
|
232
|
+
|
|
233
|
+
This event will be linked to child events created by the subagent
|
|
234
|
+
and updated when SubagentStop fires.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
db: Database connection
|
|
238
|
+
tool_input: Task() tool input parameters
|
|
239
|
+
session_id: Current session ID
|
|
240
|
+
start_time: ISO8601 UTC timestamp
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Parent event_id if successful, None otherwise
|
|
244
|
+
"""
|
|
245
|
+
try:
|
|
246
|
+
if not db.connection:
|
|
247
|
+
db.connect()
|
|
248
|
+
|
|
249
|
+
parent_event_id = f"evt-{str(uuid.uuid4())[:8]}"
|
|
250
|
+
subagent_type = extract_subagent_type(tool_input)
|
|
251
|
+
prompt = str(tool_input.get("prompt", ""))[:200]
|
|
252
|
+
|
|
253
|
+
# Load UserQuery event ID for parent-child linking from database
|
|
254
|
+
user_query_event_id = None
|
|
255
|
+
try:
|
|
256
|
+
from htmlgraph.hooks.event_tracker import get_parent_user_query
|
|
257
|
+
|
|
258
|
+
user_query_event_id = get_parent_user_query(db, session_id)
|
|
259
|
+
except Exception:
|
|
260
|
+
pass
|
|
261
|
+
|
|
262
|
+
# Build input summary
|
|
263
|
+
input_summary = json.dumps(
|
|
264
|
+
{
|
|
265
|
+
"subagent_type": subagent_type or "general-purpose",
|
|
266
|
+
"prompt": prompt,
|
|
267
|
+
}
|
|
268
|
+
)[:500]
|
|
269
|
+
|
|
270
|
+
cursor = db.connection.cursor() # type: ignore[union-attr]
|
|
271
|
+
|
|
272
|
+
# Insert parent event
|
|
273
|
+
cursor.execute(
|
|
274
|
+
"""
|
|
275
|
+
INSERT INTO agent_events
|
|
276
|
+
(event_id, agent_id, event_type, timestamp, tool_name,
|
|
277
|
+
input_summary, session_id, status, subagent_type, parent_event_id)
|
|
278
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
279
|
+
""",
|
|
280
|
+
(
|
|
281
|
+
parent_event_id,
|
|
282
|
+
"claude-code", # Main orchestrator agent
|
|
283
|
+
"task_delegation",
|
|
284
|
+
start_time,
|
|
285
|
+
"Task",
|
|
286
|
+
input_summary,
|
|
287
|
+
session_id,
|
|
288
|
+
"started",
|
|
289
|
+
subagent_type or "general-purpose",
|
|
290
|
+
user_query_event_id, # Link to UserQuery event
|
|
291
|
+
),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
db.connection.commit() # type: ignore[union-attr]
|
|
295
|
+
|
|
296
|
+
# Export to environment for subagent reference
|
|
297
|
+
os.environ["HTMLGRAPH_PARENT_EVENT"] = parent_event_id
|
|
298
|
+
os.environ["HTMLGRAPH_PARENT_QUERY_EVENT"] = (
|
|
299
|
+
user_query_event_id or ""
|
|
300
|
+
) # For spawners to use
|
|
301
|
+
os.environ["HTMLGRAPH_SUBAGENT_TYPE"] = subagent_type or "general-purpose"
|
|
302
|
+
|
|
303
|
+
logger.debug(
|
|
304
|
+
f"Created parent event for Task delegation: "
|
|
305
|
+
f"event_id={parent_event_id}, subagent_type={subagent_type}, "
|
|
306
|
+
f"parent_query_event={user_query_event_id}"
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
return parent_event_id
|
|
310
|
+
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logger.warning(f"Error creating parent event: {e}")
|
|
313
|
+
return None
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def create_start_event(
|
|
317
|
+
tool_name: str, tool_input: dict[str, Any], session_id: str
|
|
318
|
+
) -> str | None:
|
|
319
|
+
"""
|
|
320
|
+
Capture and store tool execution start event.
|
|
321
|
+
|
|
322
|
+
Inserts into tool_traces table with:
|
|
323
|
+
- tool_use_id: UUID v4 for correlation
|
|
324
|
+
- trace_id: Parent trace ID (from context)
|
|
325
|
+
- session_id: Current session
|
|
326
|
+
- tool_name: Tool being executed
|
|
327
|
+
- tool_input: Sanitized input parameters
|
|
328
|
+
- start_time: ISO8601 UTC timestamp
|
|
329
|
+
- status: 'started'
|
|
330
|
+
|
|
331
|
+
For Task() calls, also creates a parent event for event nesting.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
tool_name: Name of tool being executed
|
|
335
|
+
tool_input: Tool input parameters (will be sanitized)
|
|
336
|
+
session_id: Current session ID
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
tool_use_id on success, None on error
|
|
340
|
+
"""
|
|
341
|
+
tool_use_id = None
|
|
342
|
+
try:
|
|
343
|
+
tool_use_id = generate_tool_use_id()
|
|
344
|
+
trace_id = os.environ.get("HTMLGRAPH_TRACE_ID", tool_use_id)
|
|
345
|
+
start_time = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
|
346
|
+
|
|
347
|
+
# Sanitize input before storing
|
|
348
|
+
sanitized_input = sanitize_tool_input(tool_input)
|
|
349
|
+
|
|
350
|
+
# Connect to database (use project's .htmlgraph/htmlgraph.db, not home directory)
|
|
351
|
+
from htmlgraph.config import get_database_path
|
|
352
|
+
|
|
353
|
+
db_path = str(get_database_path())
|
|
354
|
+
db = HtmlGraphDB(db_path)
|
|
355
|
+
|
|
356
|
+
# Ensure session exists (create placeholder if needed)
|
|
357
|
+
if not db._ensure_session_exists(session_id, "system"):
|
|
358
|
+
logger.warning(f"Could not ensure session {session_id} exists in database")
|
|
359
|
+
|
|
360
|
+
# Insert start event into tool_traces
|
|
361
|
+
if not db.connection:
|
|
362
|
+
db.connect()
|
|
363
|
+
|
|
364
|
+
cursor = db.connection.cursor() # type: ignore[union-attr]
|
|
365
|
+
|
|
366
|
+
# Determine parent event ID with proper hierarchy:
|
|
367
|
+
# 1. FIRST check HTMLGRAPH_PARENT_EVENT env var (set by Task delegation for subagents)
|
|
368
|
+
# 2. For Task() tool, create a new task_delegation event
|
|
369
|
+
# 3. Fall back to UserQuery only if no parent context available
|
|
370
|
+
#
|
|
371
|
+
# This ensures tool events executed within Task() subagents are properly
|
|
372
|
+
# nested under the Task delegation event, not flattened to UserQuery.
|
|
373
|
+
env_parent_event = os.environ.get("HTMLGRAPH_PARENT_EVENT")
|
|
374
|
+
|
|
375
|
+
# Get UserQuery event ID as fallback (for top-level tool calls)
|
|
376
|
+
user_query_event_id = None
|
|
377
|
+
try:
|
|
378
|
+
from htmlgraph.hooks.event_tracker import get_parent_user_query
|
|
379
|
+
|
|
380
|
+
user_query_event_id = get_parent_user_query(db, session_id)
|
|
381
|
+
except Exception:
|
|
382
|
+
pass
|
|
383
|
+
|
|
384
|
+
# Check if this is a Task() call for parent event creation
|
|
385
|
+
task_parent_event_id = None
|
|
386
|
+
if tool_name == "Task":
|
|
387
|
+
task_parent_event_id = create_task_parent_event(
|
|
388
|
+
db, tool_input, session_id, start_time
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Insert into agent_events table (for dashboard display)
|
|
392
|
+
import uuid
|
|
393
|
+
|
|
394
|
+
event_id = f"evt-{str(uuid.uuid4())[:8]}"
|
|
395
|
+
|
|
396
|
+
# Determine parent with proper hierarchy:
|
|
397
|
+
# - Task() tools: Use the newly created task_delegation event
|
|
398
|
+
# - Tools in subagent context: Use HTMLGRAPH_PARENT_EVENT (Task delegation)
|
|
399
|
+
# - Top-level tools: Fall back to UserQuery
|
|
400
|
+
if tool_name == "Task":
|
|
401
|
+
parent_event_id = task_parent_event_id
|
|
402
|
+
elif env_parent_event:
|
|
403
|
+
# Subagent context: tools should be children of Task delegation
|
|
404
|
+
parent_event_id = env_parent_event
|
|
405
|
+
logger.debug(
|
|
406
|
+
f"Using parent from environment: {env_parent_event} for {tool_name}"
|
|
407
|
+
)
|
|
408
|
+
else:
|
|
409
|
+
# Top-level context: tools are children of UserQuery
|
|
410
|
+
parent_event_id = user_query_event_id
|
|
411
|
+
|
|
412
|
+
cursor.execute(
|
|
413
|
+
"""
|
|
414
|
+
INSERT INTO agent_events
|
|
415
|
+
(event_id, agent_id, event_type, timestamp, tool_name,
|
|
416
|
+
input_summary, session_id, status, parent_event_id)
|
|
417
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
418
|
+
""",
|
|
419
|
+
(
|
|
420
|
+
event_id,
|
|
421
|
+
"claude-code", # Agent executing the tool
|
|
422
|
+
"tool_call",
|
|
423
|
+
start_time,
|
|
424
|
+
tool_name,
|
|
425
|
+
json.dumps(sanitized_input)[:500], # Truncate for summary
|
|
426
|
+
session_id,
|
|
427
|
+
"recorded",
|
|
428
|
+
parent_event_id, # Link to UserQuery or Task parent
|
|
429
|
+
),
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
# Export Bash event as parent for child processes (e.g., spawner executables)
|
|
433
|
+
if tool_name == "Bash":
|
|
434
|
+
os.environ["HTMLGRAPH_PARENT_EVENT"] = event_id
|
|
435
|
+
logger.debug(
|
|
436
|
+
f"Exported HTMLGRAPH_PARENT_EVENT={event_id} for Bash tool call"
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
# Also insert into tool_traces for correlation (if table exists)
|
|
440
|
+
try:
|
|
441
|
+
cursor.execute(
|
|
442
|
+
"""
|
|
443
|
+
INSERT INTO tool_traces
|
|
444
|
+
(tool_use_id, trace_id, session_id, tool_name, tool_input,
|
|
445
|
+
start_time, status, parent_tool_use_id)
|
|
446
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
447
|
+
""",
|
|
448
|
+
(
|
|
449
|
+
tool_use_id,
|
|
450
|
+
trace_id,
|
|
451
|
+
session_id,
|
|
452
|
+
tool_name,
|
|
453
|
+
json.dumps(sanitized_input),
|
|
454
|
+
start_time,
|
|
455
|
+
"started",
|
|
456
|
+
None, # Will be set by SubagentStop hook
|
|
457
|
+
),
|
|
458
|
+
)
|
|
459
|
+
except Exception as e:
|
|
460
|
+
logger.debug(f"Could not insert into tool_traces: {e}")
|
|
461
|
+
|
|
462
|
+
db.connection.commit() # type: ignore[union-attr]
|
|
463
|
+
db.disconnect()
|
|
464
|
+
|
|
465
|
+
logger.debug(
|
|
466
|
+
f"Created start event: tool_use_id={tool_use_id}, "
|
|
467
|
+
f"tool={tool_name}, session={session_id}, parent_event={parent_event_id}"
|
|
468
|
+
)
|
|
469
|
+
return tool_use_id
|
|
470
|
+
|
|
471
|
+
except Exception as e:
|
|
472
|
+
logger.warning(f"Error creating start event: {e}")
|
|
473
|
+
# Graceful degradation - return None but don't block tool
|
|
474
|
+
return None
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
async def run_event_tracing(
|
|
478
|
+
tool_input: dict[str, Any],
|
|
479
|
+
) -> dict[str, Any]:
|
|
480
|
+
"""
|
|
481
|
+
Run event tracing (async wrapper).
|
|
482
|
+
|
|
483
|
+
Generates tool_use_id and creates start event in database.
|
|
484
|
+
Non-blocking - errors don't prevent tool execution.
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
tool_input: Hook input with tool name and parameters
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
Event tracing response: {"hookSpecificOutput": {"tool_use_id": "...", ...}}
|
|
491
|
+
"""
|
|
492
|
+
try:
|
|
493
|
+
from htmlgraph.hooks.context import HookContext
|
|
494
|
+
|
|
495
|
+
loop = asyncio.get_event_loop()
|
|
496
|
+
tool_name = tool_input.get("name", "") or tool_input.get("tool_name", "")
|
|
497
|
+
|
|
498
|
+
# Use HookContext to properly extract session_id (same as UserPromptSubmit)
|
|
499
|
+
context = HookContext.from_input(tool_input)
|
|
500
|
+
|
|
501
|
+
try:
|
|
502
|
+
session_id = context.session_id
|
|
503
|
+
|
|
504
|
+
# Skip if no session ID
|
|
505
|
+
if not session_id or session_id == "unknown":
|
|
506
|
+
logger.debug("No session ID found, skipping event tracing")
|
|
507
|
+
return {}
|
|
508
|
+
|
|
509
|
+
# Run in thread pool since it involves I/O
|
|
510
|
+
tool_use_id = await loop.run_in_executor(
|
|
511
|
+
None,
|
|
512
|
+
create_start_event,
|
|
513
|
+
tool_name,
|
|
514
|
+
tool_input,
|
|
515
|
+
session_id,
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
if tool_use_id:
|
|
519
|
+
# Store in environment for PostToolUse correlation
|
|
520
|
+
os.environ["HTMLGRAPH_TOOL_USE_ID"] = tool_use_id
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
"hookSpecificOutput": {
|
|
524
|
+
"tool_use_id": tool_use_id,
|
|
525
|
+
"additionalContext": f"Event tracing started: {tool_use_id}",
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return {}
|
|
530
|
+
finally:
|
|
531
|
+
# Ensure context resources are properly closed
|
|
532
|
+
context.close()
|
|
533
|
+
except Exception:
|
|
534
|
+
# Graceful degradation - allow on error
|
|
535
|
+
return {}
|
|
536
|
+
|
|
35
537
|
|
|
36
538
|
async def run_orchestrator_check(tool_input: dict[str, Any]) -> dict[str, Any]:
|
|
37
539
|
"""
|
|
@@ -75,10 +577,13 @@ async def run_validation_check(tool_input: dict[str, Any]) -> dict[str, Any]:
|
|
|
75
577
|
|
|
76
578
|
tool_name = tool_input.get("name", "") or tool_input.get("tool", "")
|
|
77
579
|
tool_params = tool_input.get("input", {}) or tool_input.get("params", {})
|
|
580
|
+
session_id = tool_input.get("session_id", "unknown")
|
|
78
581
|
|
|
79
582
|
# Load config and history in thread pool
|
|
80
583
|
config = await loop.run_in_executor(None, load_validation_config)
|
|
81
|
-
history = await loop.run_in_executor(
|
|
584
|
+
history = await loop.run_in_executor(
|
|
585
|
+
None, lambda: validator_load_history(session_id)
|
|
586
|
+
)
|
|
82
587
|
|
|
83
588
|
# Run validation
|
|
84
589
|
return await loop.run_in_executor(
|
|
@@ -183,17 +688,20 @@ async def pretooluse_hook(tool_input: dict[str, Any]) -> dict[str, Any]:
|
|
|
183
688
|
"hookSpecificOutput": {
|
|
184
689
|
"hookEventName": "PreToolUse",
|
|
185
690
|
"updatedInput": {...}, # If task enforcer modified input
|
|
186
|
-
"additionalContext": "Combined guidance"
|
|
691
|
+
"additionalContext": "Combined guidance",
|
|
692
|
+
"tool_use_id": "..." # For PostToolUse correlation
|
|
187
693
|
}
|
|
188
694
|
}
|
|
189
695
|
"""
|
|
190
|
-
# Run all
|
|
696
|
+
# Run all five checks in parallel using asyncio.gather
|
|
191
697
|
(
|
|
698
|
+
event_tracing_response,
|
|
192
699
|
orch_response,
|
|
193
700
|
validate_response,
|
|
194
701
|
task_response,
|
|
195
702
|
debug_guidance,
|
|
196
703
|
) = await asyncio.gather(
|
|
704
|
+
run_event_tracing(tool_input),
|
|
197
705
|
run_orchestrator_check(tool_input),
|
|
198
706
|
run_validation_check(tool_input),
|
|
199
707
|
run_task_enforcement(tool_input),
|
|
@@ -209,6 +717,12 @@ async def pretooluse_hook(tool_input: dict[str, Any]) -> dict[str, Any]:
|
|
|
209
717
|
# Collect guidance from all systems
|
|
210
718
|
guidance_parts = []
|
|
211
719
|
|
|
720
|
+
# Event tracing guidance
|
|
721
|
+
if "hookSpecificOutput" in event_tracing_response:
|
|
722
|
+
ctx = event_tracing_response["hookSpecificOutput"].get("additionalContext", "")
|
|
723
|
+
if ctx:
|
|
724
|
+
guidance_parts.append(f"[EventTrace] {ctx}")
|
|
725
|
+
|
|
212
726
|
# Orchestrator guidance
|
|
213
727
|
if "hookSpecificOutput" in orch_response:
|
|
214
728
|
ctx = orch_response["hookSpecificOutput"].get("additionalContext", "")
|
|
@@ -245,6 +759,12 @@ async def pretooluse_hook(tool_input: dict[str, Any]) -> dict[str, Any]:
|
|
|
245
759
|
}
|
|
246
760
|
}
|
|
247
761
|
|
|
762
|
+
# Add tool_use_id for PostToolUse correlation if available
|
|
763
|
+
if "hookSpecificOutput" in event_tracing_response:
|
|
764
|
+
tool_use_id = event_tracing_response["hookSpecificOutput"].get("tool_use_id")
|
|
765
|
+
if tool_use_id:
|
|
766
|
+
response["hookSpecificOutput"]["tool_use_id"] = tool_use_id
|
|
767
|
+
|
|
248
768
|
# Check if task enforcer provided updatedInput
|
|
249
769
|
updated_input = None
|
|
250
770
|
if "hookSpecificOutput" in task_response:
|