claude-smart 0.2.46 → 0.2.48
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +19 -11
- package/bin/claude-smart.js +290 -68
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +11 -10
- package/plugin/dashboard/app/layout.tsx +20 -0
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +36 -2
- package/plugin/dashboard/package-lock.json +61 -390
- package/plugin/dashboard/package.json +2 -2
- package/plugin/opencode/dist/server.mjs +76 -2
- package/plugin/opencode/server.mts +79 -2
- package/plugin/pyproject.toml +6 -2
- package/plugin/scripts/smart-install.sh +7 -1
- package/plugin/src/claude_smart/cli.py +210 -22
- package/plugin/src/claude_smart/context_format.py +9 -9
- package/plugin/src/claude_smart/cs_cite.py +66 -19
- package/plugin/uv.lock +5 -5
- package/plugin/vendor/reflexio/.env.example +7 -0
- package/plugin/vendor/reflexio/README.md +3 -3
- package/plugin/vendor/reflexio/pyproject.toml +2 -1
- package/plugin/vendor/reflexio/reflexio/README.md +11 -6
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +1 -1
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +2 -2
- package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
- package/plugin/vendor/reflexio/reflexio/client/client.py +97 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +8 -0
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +23 -18
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +9 -8
- package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +16 -1
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +27 -16
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +27 -5
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +9 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +45 -3
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +37 -0
- package/plugin/vendor/reflexio/reflexio/server/README.md +38 -9
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +21 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +274 -3267
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +4 -3
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +16 -1
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/{_auth.py → auth.py} +2 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +62 -36
- package/plugin/vendor/reflexio/reflexio/server/deployment_profile.py +69 -0
- package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +424 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_json_extraction.py +249 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_structured_output.py +195 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +152 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +980 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_types.py +110 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +73 -1819
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +57 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/middleware.py +244 -0
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +9 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +14 -2
- package/plugin/vendor/reflexio/reflexio/server/rate_limit.py +79 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/_common.py +25 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/_metering.py +98 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/braintrust.py +129 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/config.py +210 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +549 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +259 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/playbooks.py +578 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/profiles.py +423 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/provenance.py +345 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/search.py +349 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/system.py +261 -0
- package/plugin/vendor/reflexio/reflexio/server/scheduling.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +5 -2
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/__init__.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_batch_progress.py +298 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_config_filter.py +152 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +243 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +299 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_status_change.py +273 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +258 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +17 -1183
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +8 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +26 -41
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +232 -123
- package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +385 -78
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +9 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +121 -525
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_clustering.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_postprocessing.py +130 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_prompt_formatting.py +212 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +258 -69
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +9 -9
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/publish_learning_worker.py +288 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +29 -13
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +43 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +10 -1167
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +58 -351
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +49 -19
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +452 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +11 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1 -2133
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +7 -1126
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +73 -33
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_agent_run_store.py +506 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_pending_tool_call_store.py +704 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_run_tool_dependency_store.py +123 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_audit.py +122 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +465 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_purge.py +387 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_rebuild_hide.py +332 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +511 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +955 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +844 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +896 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_search.py +270 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +50 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +64 -370
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +0 -909
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +20 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py +86 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +195 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_pending_tool_call_store.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_run_tool_dependency_store.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_audit.py +29 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_erase_execution.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_purge.py +74 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_rebuild_hide.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_subject_barrier.py +49 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +73 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/{_profiles.py → profiles/_profile_store.py} +57 -86
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_search.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +153 -12
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +0 -84
|
@@ -127,6 +127,21 @@ class ReflexioBase:
|
|
|
127
127
|
raise RuntimeError(STORAGE_NOT_CONFIGURED_MSG)
|
|
128
128
|
return storage
|
|
129
129
|
|
|
130
|
+
def get_storage(self) -> BaseStorage:
|
|
131
|
+
"""Return the configured storage backend.
|
|
132
|
+
|
|
133
|
+
Public accessor for consumers (including enterprise) that need the
|
|
134
|
+
underlying ``BaseStorage``. Prefer ``request_context.storage`` inside
|
|
135
|
+
OSS pipeline code; this exists for callers that hold a ``Reflexio``.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
BaseStorage: The configured storage backend.
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
RuntimeError: If storage is not configured.
|
|
142
|
+
"""
|
|
143
|
+
return self._get_storage()
|
|
144
|
+
|
|
130
145
|
def _get_query_reformulator(self) -> QueryReformulator:
|
|
131
146
|
"""Lazily create and cache a QueryReformulator instance.
|
|
132
147
|
|
|
@@ -29,32 +29,37 @@ class ConfigMixin(ReflexioBase):
|
|
|
29
29
|
# security). Without this, the marker reaches the readiness check as
|
|
30
30
|
# an unfillable StorageConfigManagedSupabase and fails with
|
|
31
31
|
# "Storage configuration is incomplete".
|
|
32
|
+
current_storage_config = configurator.get_current_storage_configuration()
|
|
32
33
|
storage_config = config.storage_config
|
|
33
34
|
if storage_config is None or isinstance(
|
|
34
35
|
storage_config, StorageConfigManagedSupabase
|
|
35
36
|
):
|
|
36
|
-
storage_config =
|
|
37
|
+
storage_config = current_storage_config
|
|
37
38
|
config.storage_config = storage_config
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
if
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
storage_config_changed = storage_config != current_storage_config
|
|
41
|
+
if storage_config_changed or current_storage_config is None:
|
|
42
|
+
# Storage validation initializes the backend and may run
|
|
43
|
+
# migrations, so only do it when the storage target changes.
|
|
44
|
+
if not configurator.is_storage_config_ready_to_test(
|
|
45
|
+
storage_config=storage_config
|
|
46
|
+
):
|
|
47
|
+
return SetConfigResponse(
|
|
48
|
+
success=False, msg="Storage configuration is incomplete"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
(
|
|
52
|
+
success,
|
|
53
|
+
error_msg,
|
|
54
|
+
) = configurator.test_and_init_storage_config(
|
|
55
|
+
storage_config=storage_config
|
|
45
56
|
)
|
|
46
57
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if not success:
|
|
54
|
-
return SetConfigResponse(
|
|
55
|
-
success=False,
|
|
56
|
-
msg=f"Failed to validate storage connection: {error_msg}",
|
|
57
|
-
)
|
|
58
|
+
if not success:
|
|
59
|
+
return SetConfigResponse(
|
|
60
|
+
success=False,
|
|
61
|
+
msg=f"Failed to validate storage connection: {error_msg}",
|
|
62
|
+
)
|
|
58
63
|
|
|
59
64
|
# Only set config if validation passed
|
|
60
65
|
configurator.set_config(config)
|
|
@@ -38,22 +38,23 @@ class GenerationMixin(ReflexioBase):
|
|
|
38
38
|
"""
|
|
39
39
|
if not self._is_storage_configured():
|
|
40
40
|
raise ValueError(STORAGE_NOT_CONFIGURED_MSG)
|
|
41
|
+
from reflexio.server.extensions import get_service
|
|
42
|
+
from reflexio.server.services.playbook.aggregation_prompt_processing import (
|
|
43
|
+
AGGREGATION_PROMPT_PROCESSOR,
|
|
44
|
+
)
|
|
41
45
|
from reflexio.server.services.playbook.components.aggregator import (
|
|
42
46
|
PlaybookAggregator,
|
|
43
47
|
)
|
|
44
48
|
from reflexio.server.services.playbook.playbook_service_utils import (
|
|
45
49
|
PlaybookAggregatorRequest,
|
|
46
50
|
)
|
|
47
|
-
from reflexio.server.services.playbook.user_detail_stripping import (
|
|
48
|
-
create_aggregation_user_detail_stripper,
|
|
49
|
-
)
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
self.request_context.configurator
|
|
53
|
-
)
|
|
52
|
+
aggregation_prompt_processor = get_service(AGGREGATION_PROMPT_PROCESSOR)
|
|
54
53
|
aggregator_kwargs = {}
|
|
55
|
-
if
|
|
56
|
-
aggregator_kwargs["
|
|
54
|
+
if aggregation_prompt_processor is not None:
|
|
55
|
+
aggregator_kwargs["aggregation_prompt_processor"] = (
|
|
56
|
+
aggregation_prompt_processor
|
|
57
|
+
)
|
|
57
58
|
|
|
58
59
|
playbook_aggregator = PlaybookAggregator(
|
|
59
60
|
llm_client=self.llm_client,
|
|
@@ -53,11 +53,21 @@ class InteractionsMixin(ReflexioBase):
|
|
|
53
53
|
def publish_interaction(
|
|
54
54
|
self,
|
|
55
55
|
request: PublishUserInteractionRequest | dict,
|
|
56
|
+
*,
|
|
57
|
+
use_publish_limiter: bool = True,
|
|
58
|
+
publish_limiter_wait_forever: bool = True,
|
|
59
|
+
defer_learning: bool = False,
|
|
56
60
|
) -> PublishUserInteractionResponse:
|
|
57
61
|
"""Publish user interactions.
|
|
58
62
|
|
|
59
63
|
Args:
|
|
60
64
|
request (Union[PublishUserInteractionRequest, dict]): The publish user interaction request
|
|
65
|
+
use_publish_limiter: Whether GenerationService should throttle the
|
|
66
|
+
post-write learning pipeline.
|
|
67
|
+
publish_limiter_wait_forever: Whether GenerationService should queue
|
|
68
|
+
indefinitely for that post-write learning limiter.
|
|
69
|
+
defer_learning: Whether to enqueue post-persist learning instead of
|
|
70
|
+
running it inline.
|
|
61
71
|
|
|
62
72
|
Returns:
|
|
63
73
|
PublishUserInteractionResponse: Response containing success status and message
|
|
@@ -87,7 +97,12 @@ class InteractionsMixin(ReflexioBase):
|
|
|
87
97
|
# Convert dict to PublishUserInteractionRequest if needed
|
|
88
98
|
if isinstance(request, dict):
|
|
89
99
|
request = PublishUserInteractionRequest(**request)
|
|
90
|
-
result = generation_service.run(
|
|
100
|
+
result = generation_service.run(
|
|
101
|
+
request,
|
|
102
|
+
use_publish_limiter=use_publish_limiter,
|
|
103
|
+
publish_limiter_wait_forever=publish_limiter_wait_forever,
|
|
104
|
+
defer_learning=defer_learning,
|
|
105
|
+
)
|
|
91
106
|
after_profiles = _safe_count(storage.count_all_profiles)
|
|
92
107
|
after_playbooks = _safe_count(storage.count_user_playbooks)
|
|
93
108
|
# Don't concatenate warnings into the message field — they
|
|
@@ -414,26 +414,22 @@ class ProfilesMixin(ReflexioBase):
|
|
|
414
414
|
status_filter = [None] # Default to current profiles
|
|
415
415
|
|
|
416
416
|
profiles = self._get_storage().get_user_profile(
|
|
417
|
-
request.user_id,
|
|
417
|
+
request.user_id,
|
|
418
|
+
status_filter=status_filter,
|
|
419
|
+
tags=request.tags,
|
|
420
|
+
profile_id=request.profile_id,
|
|
421
|
+
query=request.query,
|
|
422
|
+
source=request.source,
|
|
423
|
+
profile_time_to_live=request.profile_time_to_live,
|
|
424
|
+
start_time=(
|
|
425
|
+
int(request.start_time.timestamp()) if request.start_time else None
|
|
426
|
+
),
|
|
427
|
+
end_time=int(request.end_time.timestamp()) if request.end_time else None,
|
|
418
428
|
)
|
|
419
429
|
profiles = sorted(
|
|
420
430
|
profiles, key=lambda x: x.last_modified_timestamp, reverse=True
|
|
421
431
|
)
|
|
422
432
|
|
|
423
|
-
# Apply time filters
|
|
424
|
-
if request.start_time:
|
|
425
|
-
profiles = [
|
|
426
|
-
p
|
|
427
|
-
for p in profiles
|
|
428
|
-
if p.last_modified_timestamp >= int(request.start_time.timestamp())
|
|
429
|
-
]
|
|
430
|
-
if request.end_time:
|
|
431
|
-
profiles = [
|
|
432
|
-
p
|
|
433
|
-
for p in profiles
|
|
434
|
-
if p.last_modified_timestamp <= int(request.end_time.timestamp())
|
|
435
|
-
]
|
|
436
|
-
|
|
437
433
|
# Apply top_k limit
|
|
438
434
|
if request.top_k:
|
|
439
435
|
profiles = sorted(
|
|
@@ -450,6 +446,13 @@ class ProfilesMixin(ReflexioBase):
|
|
|
450
446
|
self,
|
|
451
447
|
limit: int = 100,
|
|
452
448
|
status_filter: list[Status | None] | None = None,
|
|
449
|
+
user_id: str | None = None,
|
|
450
|
+
profile_id: str | None = None,
|
|
451
|
+
query: str | None = None,
|
|
452
|
+
source: str | None = None,
|
|
453
|
+
profile_time_to_live: str | None = None,
|
|
454
|
+
start_time: int | None = None,
|
|
455
|
+
end_time: int | None = None,
|
|
453
456
|
) -> GetUserProfilesResponse:
|
|
454
457
|
"""Get all user profiles across all users.
|
|
455
458
|
|
|
@@ -467,7 +470,15 @@ class ProfilesMixin(ReflexioBase):
|
|
|
467
470
|
if status_filter is None:
|
|
468
471
|
status_filter = [None] # Default to current profiles
|
|
469
472
|
profiles = self._get_storage().get_all_profiles(
|
|
470
|
-
limit=limit,
|
|
473
|
+
limit=limit,
|
|
474
|
+
status_filter=status_filter,
|
|
475
|
+
user_id=user_id,
|
|
476
|
+
profile_id=profile_id,
|
|
477
|
+
query=query,
|
|
478
|
+
source=source,
|
|
479
|
+
profile_time_to_live=profile_time_to_live,
|
|
480
|
+
start_time=start_time,
|
|
481
|
+
end_time=end_time,
|
|
471
482
|
)
|
|
472
483
|
profiles = sorted(
|
|
473
484
|
profiles, key=lambda x: x.last_modified_timestamp, reverse=True
|
|
@@ -41,9 +41,27 @@ class SearchMixin(ReflexioBase):
|
|
|
41
41
|
request = GetAgentSuccessEvaluationResultsRequest(**request)
|
|
42
42
|
|
|
43
43
|
try:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
if request.start_time or request.end_time:
|
|
45
|
+
results = (
|
|
46
|
+
self._get_storage().get_agent_success_evaluation_results_in_window(
|
|
47
|
+
from_ts=(
|
|
48
|
+
int(request.start_time.timestamp())
|
|
49
|
+
if request.start_time
|
|
50
|
+
else 0
|
|
51
|
+
),
|
|
52
|
+
to_ts=(
|
|
53
|
+
int(request.end_time.timestamp())
|
|
54
|
+
if request.end_time
|
|
55
|
+
else 2**31 - 1
|
|
56
|
+
),
|
|
57
|
+
limit=request.limit or 100,
|
|
58
|
+
agent_version=request.agent_version,
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
results = self._get_storage().get_agent_success_evaluation_results(
|
|
63
|
+
limit=request.limit or 100, agent_version=request.agent_version
|
|
64
|
+
)
|
|
47
65
|
return GetAgentSuccessEvaluationResultsResponse(
|
|
48
66
|
success=True,
|
|
49
67
|
agent_success_evaluation_results=results,
|
|
@@ -79,6 +97,7 @@ class SearchMixin(ReflexioBase):
|
|
|
79
97
|
user_id=request.user_id,
|
|
80
98
|
request_id=request.request_id,
|
|
81
99
|
session_id=request.session_id,
|
|
100
|
+
source=request.source,
|
|
82
101
|
start_time=(
|
|
83
102
|
int(request.start_time.timestamp()) if request.start_time else None
|
|
84
103
|
),
|
|
@@ -104,10 +123,11 @@ class SearchMixin(ReflexioBase):
|
|
|
104
123
|
Session(session_id=group_name, requests=request_data_list)
|
|
105
124
|
)
|
|
106
125
|
|
|
107
|
-
#
|
|
126
|
+
# Pagination is session-based (top_k counts sessions): there may be
|
|
127
|
+
# more pages when this page filled the session limit.
|
|
108
128
|
total_returned = sum(len(s.requests) for s in sessions)
|
|
109
129
|
effective_limit = request.top_k or 100
|
|
110
|
-
has_more =
|
|
130
|
+
has_more = len(sessions) >= effective_limit
|
|
111
131
|
|
|
112
132
|
return GetRequestsResponse(
|
|
113
133
|
success=True,
|
|
@@ -162,6 +182,7 @@ class SearchMixin(ReflexioBase):
|
|
|
162
182
|
storage = self._get_storage()
|
|
163
183
|
prompt_manager = self.request_context.prompt_manager
|
|
164
184
|
retrieval_floor = config.retrieval_floor if config else None
|
|
185
|
+
recency = getattr(storage, "recency", None)
|
|
165
186
|
|
|
166
187
|
from reflexio.server.services.unified_search_service import run_unified_search
|
|
167
188
|
|
|
@@ -173,4 +194,5 @@ class SearchMixin(ReflexioBase):
|
|
|
173
194
|
prompt_manager=prompt_manager,
|
|
174
195
|
pre_retrieval_model_name=pre_retrieval_model_name,
|
|
175
196
|
retrieval_floor=retrieval_floor,
|
|
197
|
+
recency=recency,
|
|
176
198
|
)
|
|
@@ -53,10 +53,19 @@ class UserPlaybookMixin(ReflexioBase):
|
|
|
53
53
|
try:
|
|
54
54
|
user_playbooks = self._get_storage().get_user_playbooks(
|
|
55
55
|
limit=request.limit or 100,
|
|
56
|
+
user_playbook_id=request.user_playbook_id,
|
|
56
57
|
user_id=request.user_id,
|
|
58
|
+
request_id=request.request_id,
|
|
59
|
+
query=request.query,
|
|
57
60
|
playbook_name=request.playbook_name,
|
|
58
61
|
agent_version=request.agent_version,
|
|
59
62
|
status_filter=request.status_filter,
|
|
63
|
+
start_time=(
|
|
64
|
+
int(request.start_time.timestamp()) if request.start_time else None
|
|
65
|
+
),
|
|
66
|
+
end_time=(
|
|
67
|
+
int(request.end_time.timestamp()) if request.end_time else None
|
|
68
|
+
),
|
|
60
69
|
tags=request.tags,
|
|
61
70
|
)
|
|
62
71
|
return GetUserPlaybooksResponse(
|
|
@@ -46,6 +46,7 @@ class Status(str, Enum): # noqa: UP042 - CURRENT=None is not compatible with St
|
|
|
46
46
|
SUPERSEDED = (
|
|
47
47
|
"superseded" # tombstone: replaced by a new version (superseded_by set)
|
|
48
48
|
)
|
|
49
|
+
EXPIRED = "expired" # tombstone: TTL elapsed while active (see expiry reclamation)
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
class OperationStatus(StrEnum):
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
AuditActorType = Literal["api_token", "jwt", "system"]
|
|
9
|
+
AuditOperation = Literal["READ", "EXPORT", "ERASE", "CREATE", "UPDATE", "DELETE"]
|
|
10
|
+
AuditEntityType = Literal[
|
|
11
|
+
"profile",
|
|
12
|
+
"user_playbook",
|
|
13
|
+
"agent_playbook",
|
|
14
|
+
"interaction",
|
|
15
|
+
"request",
|
|
16
|
+
"session",
|
|
17
|
+
"agent_success_evaluation_result",
|
|
18
|
+
"playbook_retrieval_log",
|
|
19
|
+
"org",
|
|
20
|
+
]
|
|
21
|
+
AuditStatus = Literal["ok", "error"]
|
|
22
|
+
PurgeOperationType = Literal["user_erasure", "org_purge"]
|
|
23
|
+
PurgeScopeType = Literal["user", "org"]
|
|
24
|
+
PurgeStatus = Literal["pending", "running", "failed", "complete"]
|
|
25
|
+
PurgeTargetStatus = Literal["pending", "running", "failed", "complete"]
|
|
26
|
+
SubjectBarrierStatus = Literal["erasing", "erased", "failed"]
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"AuditActorType",
|
|
30
|
+
"AuditOperation",
|
|
31
|
+
"AuditEntityType",
|
|
32
|
+
"AuditStatus",
|
|
33
|
+
"PurgeOperationType",
|
|
34
|
+
"PurgeScopeType",
|
|
35
|
+
"PurgeStatus",
|
|
36
|
+
"PurgeTargetStatus",
|
|
37
|
+
"SubjectBarrierStatus",
|
|
38
|
+
"AuditEvent",
|
|
39
|
+
"PurgeOperation",
|
|
40
|
+
"PurgeOperationTarget",
|
|
41
|
+
"SubjectWriteBarrier",
|
|
42
|
+
"UserExportResult",
|
|
43
|
+
"UserEraseResult",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _now_epoch() -> int:
|
|
48
|
+
return int(datetime.now(UTC).timestamp())
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AuditEvent(BaseModel):
|
|
52
|
+
org_id: str
|
|
53
|
+
actor_type: AuditActorType = "system"
|
|
54
|
+
actor_ref: str | None = None
|
|
55
|
+
operation: AuditOperation
|
|
56
|
+
entity_type: AuditEntityType
|
|
57
|
+
entity_id: str | None = None
|
|
58
|
+
subject_ref: str | None = None
|
|
59
|
+
request_ref: str
|
|
60
|
+
idempotency_key: str | None = None
|
|
61
|
+
status: AuditStatus = "ok"
|
|
62
|
+
detail: dict[str, Any] | None = None
|
|
63
|
+
created_at: int = Field(default_factory=_now_epoch)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class PurgeOperation(BaseModel):
|
|
67
|
+
purge_id: str
|
|
68
|
+
org_id: str
|
|
69
|
+
operation_type: PurgeOperationType
|
|
70
|
+
scope_type: PurgeScopeType
|
|
71
|
+
subject_ref: str | None = None
|
|
72
|
+
request_ref: str
|
|
73
|
+
idempotency_key: str
|
|
74
|
+
status: PurgeStatus = "pending"
|
|
75
|
+
error_code: str | None = None
|
|
76
|
+
error_detail: str | None = None
|
|
77
|
+
created_at: int = Field(default_factory=_now_epoch)
|
|
78
|
+
updated_at: int = Field(default_factory=_now_epoch)
|
|
79
|
+
completed_at: int | None = None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class PurgeOperationTarget(BaseModel):
|
|
83
|
+
purge_id: str
|
|
84
|
+
target_name: str
|
|
85
|
+
target_ref: str = ""
|
|
86
|
+
phase: str
|
|
87
|
+
status: PurgeTargetStatus = "pending"
|
|
88
|
+
detail: dict[str, Any] | None = None
|
|
89
|
+
deleted_count: int = 0
|
|
90
|
+
error_detail: str | None = None
|
|
91
|
+
started_at: int | None = None
|
|
92
|
+
completed_at: int | None = None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class SubjectWriteBarrier(BaseModel):
|
|
96
|
+
org_id: str
|
|
97
|
+
subject_ref: str
|
|
98
|
+
purge_id: str
|
|
99
|
+
status: SubjectBarrierStatus
|
|
100
|
+
error_code: str | None = None
|
|
101
|
+
error_detail: str | None = None
|
|
102
|
+
created_at: int = Field(default_factory=_now_epoch)
|
|
103
|
+
updated_at: int = Field(default_factory=_now_epoch)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class UserExportResult(BaseModel):
|
|
107
|
+
subject_ref: str
|
|
108
|
+
export_id: str
|
|
109
|
+
bundle: dict[str, Any]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class UserEraseResult(BaseModel):
|
|
113
|
+
subject_ref: str
|
|
114
|
+
purge_id: str
|
|
115
|
+
status: PurgeStatus
|
|
116
|
+
deleted_counts: dict[str, int] = Field(default_factory=dict)
|
|
117
|
+
rebuilt_agent_playbook_ids: list[int] = Field(default_factory=list)
|
|
@@ -187,9 +187,13 @@ class GetInteractionsResponse(BaseModel):
|
|
|
187
187
|
|
|
188
188
|
class GetUserProfilesRequest(BaseModel):
|
|
189
189
|
user_id: NonEmptyStr
|
|
190
|
+
profile_id: str | None = None
|
|
191
|
+
query: str | None = None
|
|
190
192
|
start_time: datetime | None = None
|
|
191
193
|
end_time: datetime | None = None
|
|
192
194
|
top_k: int | None = Field(default=30, gt=0)
|
|
195
|
+
source: str | None = None
|
|
196
|
+
profile_time_to_live: str | None = None
|
|
193
197
|
status_filter: list[Status | None] | None = None
|
|
194
198
|
tags: list[str] | None = None
|
|
195
199
|
|
|
@@ -222,12 +226,23 @@ class SetConfigResponse(BaseModel):
|
|
|
222
226
|
|
|
223
227
|
class GetUserPlaybooksRequest(BaseModel):
|
|
224
228
|
limit: int | None = Field(default=100, gt=0)
|
|
229
|
+
user_playbook_id: int | None = Field(default=None, gt=0)
|
|
225
230
|
user_id: str | None = None
|
|
231
|
+
request_id: str | None = None
|
|
232
|
+
query: str | None = None
|
|
226
233
|
playbook_name: str | None = None
|
|
227
234
|
agent_version: str | None = None
|
|
235
|
+
start_time: datetime | None = None
|
|
236
|
+
end_time: datetime | None = None
|
|
228
237
|
status_filter: list[Status | None] | None = None
|
|
229
238
|
tags: list[str] | None = None
|
|
230
239
|
|
|
240
|
+
@model_validator(mode="after")
|
|
241
|
+
def check_time_range(self) -> Self:
|
|
242
|
+
"""Validate that end_time is after start_time."""
|
|
243
|
+
TimeRangeValidatorMixin.validate_time_range(self.start_time, self.end_time)
|
|
244
|
+
return self
|
|
245
|
+
|
|
231
246
|
|
|
232
247
|
class GetUserPlaybooksResponse(BaseModel):
|
|
233
248
|
success: bool
|
|
@@ -237,8 +252,12 @@ class GetUserPlaybooksResponse(BaseModel):
|
|
|
237
252
|
|
|
238
253
|
class GetAgentPlaybooksRequest(BaseModel):
|
|
239
254
|
limit: int | None = Field(default=100, gt=0)
|
|
255
|
+
agent_playbook_id: int | None = Field(default=None, gt=0)
|
|
256
|
+
query: str | None = None
|
|
240
257
|
playbook_name: str | None = None
|
|
241
258
|
agent_version: str | None = None
|
|
259
|
+
start_time: datetime | None = None
|
|
260
|
+
end_time: datetime | None = None
|
|
242
261
|
status_filter: list[Status | None] | None = None
|
|
243
262
|
playbook_status_filter: PlaybookStatus | None = None
|
|
244
263
|
tags: list[str] | None = None
|
|
@@ -247,6 +266,12 @@ class GetAgentPlaybooksRequest(BaseModel):
|
|
|
247
266
|
request_id: str | None = None
|
|
248
267
|
session_id: str | None = None
|
|
249
268
|
|
|
269
|
+
@model_validator(mode="after")
|
|
270
|
+
def check_time_range(self) -> Self:
|
|
271
|
+
"""Validate that end_time is after start_time."""
|
|
272
|
+
TimeRangeValidatorMixin.validate_time_range(self.start_time, self.end_time)
|
|
273
|
+
return self
|
|
274
|
+
|
|
250
275
|
|
|
251
276
|
class GetAgentPlaybooksResponse(BaseModel):
|
|
252
277
|
success: bool
|
|
@@ -392,6 +417,14 @@ class SearchAgentPlaybookResponse(BaseModel):
|
|
|
392
417
|
class GetAgentSuccessEvaluationResultsRequest(BaseModel):
|
|
393
418
|
limit: int | None = Field(default=100, gt=0)
|
|
394
419
|
agent_version: str | None = None
|
|
420
|
+
start_time: datetime | None = None
|
|
421
|
+
end_time: datetime | None = None
|
|
422
|
+
|
|
423
|
+
@model_validator(mode="after")
|
|
424
|
+
def check_time_range(self) -> Self:
|
|
425
|
+
"""Validate that end_time is after start_time."""
|
|
426
|
+
TimeRangeValidatorMixin.validate_time_range(self.start_time, self.end_time)
|
|
427
|
+
return self
|
|
395
428
|
|
|
396
429
|
|
|
397
430
|
class GetAgentSuccessEvaluationResultsResponse(BaseModel):
|
|
@@ -404,10 +437,18 @@ class GetRequestsRequest(BaseModel):
|
|
|
404
437
|
user_id: str | None = None
|
|
405
438
|
request_id: str | None = None
|
|
406
439
|
session_id: str | None = None
|
|
440
|
+
source: str | None = None
|
|
407
441
|
start_time: datetime | None = None
|
|
408
442
|
end_time: datetime | None = None
|
|
409
|
-
top_k: int | None = Field(
|
|
410
|
-
|
|
443
|
+
top_k: int | None = Field(
|
|
444
|
+
default=30,
|
|
445
|
+
gt=0,
|
|
446
|
+
description="Maximum number of sessions to return. Pagination is "
|
|
447
|
+
"per-session: every returned session includes all of its requests.",
|
|
448
|
+
)
|
|
449
|
+
offset: int | None = Field(
|
|
450
|
+
default=0, ge=0, description="Number of sessions to skip for pagination."
|
|
451
|
+
)
|
|
411
452
|
|
|
412
453
|
@model_validator(mode="after")
|
|
413
454
|
def check_time_range(self) -> Self:
|
|
@@ -497,7 +538,8 @@ class TimeSeriesDataPoint(BaseModel):
|
|
|
497
538
|
"""A single data point in a time series."""
|
|
498
539
|
|
|
499
540
|
timestamp: int = Field(gt=0) # Unix timestamp
|
|
500
|
-
value:
|
|
541
|
+
value: float = Field(ge=0) # Count or metric value
|
|
542
|
+
count: int | None = Field(default=None, ge=0) # Optional weight for rate metrics
|
|
501
543
|
|
|
502
544
|
|
|
503
545
|
class PeriodStats(BaseModel):
|
|
@@ -708,6 +708,34 @@ class LineageGCConfig(BaseModel):
|
|
|
708
708
|
poll_interval_seconds: int = Field(default=86400, gt=0)
|
|
709
709
|
|
|
710
710
|
|
|
711
|
+
class ExpiryReclamationConfig(BaseModel):
|
|
712
|
+
"""Direct-delete reclamation of expired plain rows (non-audited).
|
|
713
|
+
|
|
714
|
+
Independent of ``lineage_gc``: these rows carry no PII/audit/grace obligation,
|
|
715
|
+
so they can be reclaimed whenever this is enabled even if tombstone GC is off.
|
|
716
|
+
|
|
717
|
+
Opt-in by default (``enabled=False``) so operators control a staged rollout
|
|
718
|
+
of the direct-delete Class B sweeps and are not surprised by deletions on
|
|
719
|
+
upgrade. ``lineage_gc.enabled`` (which defaults to True) is unaffected and
|
|
720
|
+
continues to drive the Class A profile-expiry and tombstone-GC paths.
|
|
721
|
+
"""
|
|
722
|
+
|
|
723
|
+
enabled: bool = False
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
class GovernanceRetentionConfig(BaseModel):
|
|
727
|
+
"""Audit-event retention policy. **Enterprise-only:** reclamation is performed
|
|
728
|
+
by an enterprise per-org reclamation sweep registered via
|
|
729
|
+
``register_per_org_sweep``. In an OSS-only deployment these knobs are accepted
|
|
730
|
+
but inert (the OSS lineage scheduler does not reclaim audit events) and the
|
|
731
|
+
server logs a startup warning when retention is enabled.
|
|
732
|
+
"""
|
|
733
|
+
|
|
734
|
+
audit_events_retention_enabled: bool = False
|
|
735
|
+
audit_events_retention_days: int = Field(default=365, gt=0)
|
|
736
|
+
audit_events_delete_batch_limit: int = Field(default=500, gt=0)
|
|
737
|
+
|
|
738
|
+
|
|
711
739
|
@dataclass(frozen=True)
|
|
712
740
|
class EffectivePendingToolCallConfig:
|
|
713
741
|
"""Resolved pending-tool-call settings after applying tool overrides."""
|
|
@@ -847,6 +875,13 @@ class Config(BaseModel):
|
|
|
847
875
|
)
|
|
848
876
|
# Tombstone GC job gate (opt-in, off by default — see LineageGCConfig)
|
|
849
877
|
lineage_gc: LineageGCConfig = Field(default_factory=LineageGCConfig)
|
|
878
|
+
# Direct-delete reclamation of expired plain rows (share links, pending tool calls)
|
|
879
|
+
expiry_reclamation: ExpiryReclamationConfig = Field(
|
|
880
|
+
default_factory=ExpiryReclamationConfig
|
|
881
|
+
)
|
|
882
|
+
governance_retention: GovernanceRetentionConfig = Field(
|
|
883
|
+
default_factory=GovernanceRetentionConfig
|
|
884
|
+
)
|
|
850
885
|
# Optional non-blocking async information tools for classic extraction.
|
|
851
886
|
pending_tool_call_config: PendingToolCallConfig = Field(
|
|
852
887
|
default_factory=PendingToolCallConfig
|
|
@@ -905,6 +940,8 @@ class Config(BaseModel):
|
|
|
905
940
|
"reflection_config",
|
|
906
941
|
"playbook_optimizer_config",
|
|
907
942
|
"lineage_gc",
|
|
943
|
+
"expiry_reclamation",
|
|
944
|
+
"governance_retention",
|
|
908
945
|
"pending_tool_call_config",
|
|
909
946
|
"retrieval_floor",
|
|
910
947
|
):
|